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