microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
95bd6722745f267f58e668a54350df0264dbeb48

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/openapi/src/helpers.ts

159lines · modecode

1import {
2 getFriendlyName,
3 getTypeName,
4 getVisibility,
5 isGlobalNamespace,
6 isService,
7 isTemplateInstance,
8 ModelProperty,
9 Operation,
10 Program,
11 Type,
12 TypeNameOptions,
13} from "@typespec/compiler";
14import { getOperationId } from "./decorators.js";
15import { reportDiagnostic } from "./lib.js";
16
17/**
18 * Determines whether a type will be inlined in OpenAPI rather than defined
19 * as a schema and referenced.
20 *
21 * All anonymous types (anonymous models, arrays, tuples, etc.) are inlined.
22 *
23 * Template instantiations are inlined unless they have a friendly name.
24 *
25 * A friendly name can be provided by the user using `@friendlyName`
26 * decorator, or chosen by default in simple cases.
27 */
28export function shouldInline(program: Program, type: Type): boolean {
29 if (getFriendlyName(program, type)) {
30 return false;
31 }
32 switch (type.kind) {
33 case "Model":
34 return !type.name || isTemplateInstance(type);
35 case "Scalar":
36 return program.checker.isStdType(type) || isTemplateInstance(type);
37 case "Enum":
38 case "Union":
39 return !type.name;
40 default:
41 return true;
42 }
43}
44
45/**
46 * Gets the name of a type to be used in OpenAPI.
47 *
48 * For inlined types: this is the TypeSpec-native name written to `x-typespec-name`.
49 *
50 * For non-inlined types: this is either the friendly name or the TypeSpec-native name.
51 *
52 * TypeSpec-native names are shortened to exclude root `TypeSpec` namespace and service
53 * namespace using the provided `TypeNameOptions`.
54 */
55export function getOpenAPITypeName(
56 program: Program,
57 type: Type,
58 options: TypeNameOptions,
59 existing?: Record<string, any>
60): string {
61 const name = getFriendlyName(program, type) ?? getTypeName(type, options);
62
63 checkDuplicateTypeName(program, type, name, existing);
64 return name;
65}
66
67export function checkDuplicateTypeName(
68 program: Program,
69 type: Type,
70 name: string,
71 existing: Record<string, unknown> | undefined
72) {
73 if (existing && existing[name]) {
74 reportDiagnostic(program, {
75 code: "duplicate-type-name",
76 format: {
77 value: name,
78 },
79 target: type,
80 });
81 }
82}
83
84/**
85 * Gets the key that is used to define a parameter in OpenAPI.
86 */
87export function getParameterKey(
88 program: Program,
89 property: ModelProperty,
90 newParam: unknown,
91 existingParams: Record<string, unknown>,
92 options: TypeNameOptions
93): string {
94 const parent = property.model!;
95 let key = getOpenAPITypeName(program, parent, options);
96
97 if (parent.properties.size > 1) {
98 key += `.${property.name}`;
99 }
100
101 if (existingParams[key]) {
102 reportDiagnostic(program, {
103 code: "duplicate-type-name",
104 messageId: "parameter",
105 format: {
106 value: key,
107 },
108 target: property,
109 });
110 }
111
112 return key;
113}
114
115/**
116 * Resolve the OpenAPI operation ID for the given operation using the following logic:
117 * - If @operationId was specified use that value
118 * - If operation is defined at the root or under the service namespace return `<operation.name>`
119 * - Otherwise(operation is under another namespace or interface) return `<namespace/interface.name>_<operation.name>`
120 *
121 * @param program TypeSpec Program
122 * @param operation Operation
123 * @returns Operation ID in this format `<name>` or `<group>_<name>`
124 */
125export function resolveOperationId(program: Program, operation: Operation) {
126 const explicitOperationId = getOperationId(program, operation);
127 if (explicitOperationId) {
128 return explicitOperationId;
129 }
130
131 if (operation.interface) {
132 return `${operation.interface.name}_${operation.name}`;
133 }
134 const namespace = operation.namespace;
135 if (
136 namespace === undefined ||
137 isGlobalNamespace(program, namespace) ||
138 isService(program, namespace)
139 ) {
140 return operation.name;
141 }
142
143 return `${namespace.name}_${operation.name}`;
144}
145
146/**
147 * Determines if a property is read-only, which is defined as being
148 * decorated `@visibility("read")`.
149 *
150 * If there is more than 1 `@visibility` argument, then the property is not
151 * read-only. For example, `@visibility("read", "update")` does not
152 * designate a read-only property.
153 */
154export function isReadonlyProperty(program: Program, property: ModelProperty) {
155 const visibility = getVisibility(program, property);
156 // note: multiple visibilities that include read are not handled using
157 // readonly: true, but using separate schemas.
158 return visibility?.length === 1 && visibility[0] === "read";
159}