@azure-tools/typespec-autorest 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/src/decorators.d.ts +28 -0
- package/dist/src/decorators.d.ts.map +1 -0
- package/dist/src/decorators.js +100 -0
- package/dist/src/decorators.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib.d.ts +179 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +125 -0
- package/dist/src/lib.js.map +1 -0
- package/dist/src/openapi.d.ts +21 -0
- package/dist/src/openapi.d.ts.map +1 -0
- package/dist/src/openapi.js +1529 -0
- package/dist/src/openapi.js.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +24 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/types.d.ts +474 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +31 -0
- package/dist/src/utils.js.map +1 -0
- package/lib/autorest.tsp +2 -0
- package/lib/decorators.tsp +22 -0
- package/package.json +82 -0
|
@@ -0,0 +1,1529 @@
|
|
|
1
|
+
import { getPagedResult, isFixed } from "@azure-tools/typespec-azure-core";
|
|
2
|
+
import { compilerAssert, createProjectedNameProgram, emitFile, getAllTags, getDirectoryPath, getDiscriminator, getDoc, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMinItems, getMinLength, getMinValue, getNamespaceFullName, getPattern, getProperty, getPropertyType, getService, getSummary, getVisibility, ignoreDiagnostics, isDeprecated, isErrorModel, isErrorType, isGlobalNamespace, isNeverType, isNullType, isNumericType, isSecret, isService, isStringType, isTemplateDeclaration, isTemplateDeclarationOrInstance, listServices, navigateTypesInNamespace, NoTarget, projectProgram, resolvePath, SyntaxKind, TwoLevelMap, } from "@typespec/compiler";
|
|
3
|
+
import { createMetadataInfo, getAllHttpServices, getAuthentication, getHeaderFieldOptions, getQueryParamOptions, getRequestVisibility, getServers, getStatusCodeDescription, getVisibilitySuffix, isContentTypeHeader, reportIfNoRoutes, Visibility, } from "@typespec/http";
|
|
4
|
+
import { checkDuplicateTypeName, getExtensions, getExternalDocs, getOpenAPITypeName, getParameterKey, isReadonlyProperty, resolveOperationId, setExtension, shouldInline, } from "@typespec/openapi";
|
|
5
|
+
import { buildVersionProjections } from "@typespec/versioning";
|
|
6
|
+
import { pascalCase } from "change-case";
|
|
7
|
+
import { getCollectionFormat, getExamples, getRef } from "./decorators.js";
|
|
8
|
+
import { getTracer, reportDiagnostic } from "./lib.js";
|
|
9
|
+
import { comparePaths } from "./utils.js";
|
|
10
|
+
const defaultOptions = {
|
|
11
|
+
"output-file": "openapi.json",
|
|
12
|
+
"new-line": "lf",
|
|
13
|
+
};
|
|
14
|
+
export async function $onEmit(context) {
|
|
15
|
+
const resolvedOptions = { ...defaultOptions, ...context.options };
|
|
16
|
+
const options = {
|
|
17
|
+
outputFile: resolvedOptions["output-file"],
|
|
18
|
+
outputDir: context.emitterOutputDir,
|
|
19
|
+
azureResourceProviderFolder: resolvedOptions["azure-resource-provider-folder"],
|
|
20
|
+
examplesDirectory: resolvedOptions["examples-directory"],
|
|
21
|
+
version: resolvedOptions["version"],
|
|
22
|
+
newLine: resolvedOptions["new-line"],
|
|
23
|
+
omitUnreachableTypes: resolvedOptions["omit-unreachable-types"],
|
|
24
|
+
};
|
|
25
|
+
const emitter = createOAPIEmitter(context.program, options);
|
|
26
|
+
await emitter.emitOpenAPI();
|
|
27
|
+
}
|
|
28
|
+
function getEmitterDetails(program) {
|
|
29
|
+
return [{ emitter: "@azure-tools/typespec-autorest" }];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Represents a node that will hold a JSON reference. The value is computed
|
|
33
|
+
* at the end so that we can defer decisions about the name that is
|
|
34
|
+
* referenced.
|
|
35
|
+
*/
|
|
36
|
+
class Ref {
|
|
37
|
+
toJSON() {
|
|
38
|
+
compilerAssert(this.value, "Reference value never set.");
|
|
39
|
+
return this.value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function createOAPIEmitter(program, options) {
|
|
43
|
+
const tracer = getTracer(program);
|
|
44
|
+
tracer.trace("options", JSON.stringify(options, null, 2));
|
|
45
|
+
const typeNameOptions = {
|
|
46
|
+
// shorten type names by removing TypeSpec and service namespace
|
|
47
|
+
namespaceFilter(ns) {
|
|
48
|
+
const name = getNamespaceFullName(ns);
|
|
49
|
+
return name !== "TypeSpec" && !isService(program, ns);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
let root;
|
|
53
|
+
let currentPath;
|
|
54
|
+
let currentEndpoint;
|
|
55
|
+
let currentConsumes;
|
|
56
|
+
let currentProduces;
|
|
57
|
+
let metadataInfo;
|
|
58
|
+
// Keep a map of all Types+Visibility combinations that were encountered
|
|
59
|
+
// that need schema definitions.
|
|
60
|
+
let pendingSchemas = new TwoLevelMap();
|
|
61
|
+
// Reuse a single ref object per Type+Visibility combination.
|
|
62
|
+
let refs = new TwoLevelMap();
|
|
63
|
+
// Keep track of inline types still in the process of having their schema computed
|
|
64
|
+
// This is used to detect cycles in inline types, which is an
|
|
65
|
+
let inProgressInlineTypes = new Set();
|
|
66
|
+
// Map model properties that represent shared parameters to their parameter
|
|
67
|
+
// definition that will go in #/parameters. Inlined parameters do not go in
|
|
68
|
+
// this map.
|
|
69
|
+
let params;
|
|
70
|
+
// Keep track of models that have had properties spread into parameters. We won't
|
|
71
|
+
// consider these unreferenced when emitting unreferenced types.
|
|
72
|
+
let paramModels;
|
|
73
|
+
// De-dupe the per-endpoint tags that will be added into the #/tags
|
|
74
|
+
let tags;
|
|
75
|
+
// The set of produces/consumes values found in all operations
|
|
76
|
+
const globalProduces = new Set(["application/json"]);
|
|
77
|
+
const globalConsumes = new Set(["application/json"]);
|
|
78
|
+
let operationExamplesMap;
|
|
79
|
+
let operationIdsWithExample;
|
|
80
|
+
let jsonView;
|
|
81
|
+
async function emitOpenAPI() {
|
|
82
|
+
const services = listServices(program);
|
|
83
|
+
if (services.length === 0) {
|
|
84
|
+
services.push({ type: program.getGlobalNamespaceType() });
|
|
85
|
+
}
|
|
86
|
+
for (const service of services) {
|
|
87
|
+
const originalProgram = program;
|
|
88
|
+
const versions = buildVersionProjections(program, service.type).filter((v) => !options.version || options.version === v.version);
|
|
89
|
+
for (const record of versions) {
|
|
90
|
+
let projectedProgram;
|
|
91
|
+
if (record.projections.length > 0) {
|
|
92
|
+
projectedProgram = program = projectProgram(originalProgram, record.projections);
|
|
93
|
+
}
|
|
94
|
+
jsonView = createProjectedNameProgram(program, "json");
|
|
95
|
+
const projectedServiceNs = projectedProgram
|
|
96
|
+
? projectedProgram.projector.projectedTypes.get(service.type)
|
|
97
|
+
: service.type;
|
|
98
|
+
await emitOpenAPIFromVersion(projectedServiceNs === program.getGlobalNamespaceType()
|
|
99
|
+
? { type: program.getGlobalNamespaceType() }
|
|
100
|
+
: getService(program, projectedServiceNs), services.length > 1, record.version);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { emitOpenAPI };
|
|
105
|
+
function initializeEmitter(service, version) {
|
|
106
|
+
var _a, _b, _c;
|
|
107
|
+
const auth = processAuth(service.type);
|
|
108
|
+
root = {
|
|
109
|
+
swagger: "2.0",
|
|
110
|
+
info: {
|
|
111
|
+
title: (_a = service.title) !== null && _a !== void 0 ? _a : "(title)",
|
|
112
|
+
version: (_b = version !== null && version !== void 0 ? version : service.version) !== null && _b !== void 0 ? _b : "0000-00-00",
|
|
113
|
+
description: getDoc(program, service.type),
|
|
114
|
+
"x-typespec-generated": getEmitterDetails(program),
|
|
115
|
+
},
|
|
116
|
+
schemes: ["https"],
|
|
117
|
+
...resolveHost(program, service.type),
|
|
118
|
+
externalDocs: getExternalDocs(program, service.type),
|
|
119
|
+
produces: [],
|
|
120
|
+
consumes: [],
|
|
121
|
+
security: auth === null || auth === void 0 ? void 0 : auth.security,
|
|
122
|
+
securityDefinitions: (_c = auth === null || auth === void 0 ? void 0 : auth.securitySchemes) !== null && _c !== void 0 ? _c : {},
|
|
123
|
+
tags: [],
|
|
124
|
+
paths: {},
|
|
125
|
+
"x-ms-paths": {},
|
|
126
|
+
definitions: {},
|
|
127
|
+
parameters: {},
|
|
128
|
+
};
|
|
129
|
+
pendingSchemas = new TwoLevelMap();
|
|
130
|
+
refs = new TwoLevelMap();
|
|
131
|
+
metadataInfo = createMetadataInfo(program, {
|
|
132
|
+
canShareProperty: canSharePropertyUsingReadonlyOrXMSMutability,
|
|
133
|
+
});
|
|
134
|
+
inProgressInlineTypes = new Set();
|
|
135
|
+
currentPath = root.paths;
|
|
136
|
+
params = new Map();
|
|
137
|
+
paramModels = new Set();
|
|
138
|
+
tags = new Set();
|
|
139
|
+
operationExamplesMap = new Map();
|
|
140
|
+
operationIdsWithExample = new Set();
|
|
141
|
+
}
|
|
142
|
+
function resolveHost(program, namespace) {
|
|
143
|
+
const servers = getServers(program, namespace);
|
|
144
|
+
if (servers === undefined) {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
// If there is more than one server we then just make a custom host with a parameter asking for the full url.
|
|
148
|
+
if (servers.length > 1) {
|
|
149
|
+
return {
|
|
150
|
+
"x-ms-parameterized-host": {
|
|
151
|
+
hostTemplate: "{url}",
|
|
152
|
+
useSchemePrefix: false,
|
|
153
|
+
parameters: [
|
|
154
|
+
{
|
|
155
|
+
name: "url",
|
|
156
|
+
in: "path",
|
|
157
|
+
description: "Url",
|
|
158
|
+
type: "string",
|
|
159
|
+
format: "uri",
|
|
160
|
+
"x-ms-skip-url-encoding": true,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const server = servers[0];
|
|
167
|
+
if (server.parameters.size === 0) {
|
|
168
|
+
const [scheme, host] = server.url.split("://");
|
|
169
|
+
return {
|
|
170
|
+
host,
|
|
171
|
+
schemes: [scheme],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const parameters = [];
|
|
175
|
+
for (const prop of server.parameters.values()) {
|
|
176
|
+
const param = getOpenAPI2Parameter(prop, "path", Visibility.All);
|
|
177
|
+
if (getFormat(program, prop) === "uri" ||
|
|
178
|
+
getFormat(program, prop.type) === "uri" ||
|
|
179
|
+
getFormat(program, prop) === "url" ||
|
|
180
|
+
getFormat(program, prop.type) === "url") {
|
|
181
|
+
param["x-ms-skip-url-encoding"] = true;
|
|
182
|
+
}
|
|
183
|
+
parameters.push(param);
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
"x-ms-parameterized-host": {
|
|
187
|
+
hostTemplate: server.url,
|
|
188
|
+
useSchemePrefix: false,
|
|
189
|
+
parameters,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async function emitOpenAPIFromVersion(service, multipleService, version) {
|
|
194
|
+
initializeEmitter(service, version);
|
|
195
|
+
try {
|
|
196
|
+
await loadExamples(version);
|
|
197
|
+
const services = ignoreDiagnostics(getAllHttpServices(program));
|
|
198
|
+
const routes = services[0].operations;
|
|
199
|
+
reportIfNoRoutes(program, routes);
|
|
200
|
+
routes.forEach(emitOperation);
|
|
201
|
+
emitParameters();
|
|
202
|
+
emitSchemas(service.type);
|
|
203
|
+
emitTags();
|
|
204
|
+
// Finalize global produces/consumes
|
|
205
|
+
if (globalProduces.size > 0) {
|
|
206
|
+
root.produces = [...globalProduces.values()];
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
delete root.produces;
|
|
210
|
+
}
|
|
211
|
+
if (globalConsumes.size > 0) {
|
|
212
|
+
root.consumes = [...globalConsumes.values()];
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
delete root.consumes;
|
|
216
|
+
}
|
|
217
|
+
// Clean up empty entries
|
|
218
|
+
if (root["x-ms-paths"] && Object.keys(root["x-ms-paths"]).length === 0) {
|
|
219
|
+
delete root["x-ms-paths"];
|
|
220
|
+
}
|
|
221
|
+
if (root.security && Object.keys(root.security).length === 0) {
|
|
222
|
+
delete root["security"];
|
|
223
|
+
}
|
|
224
|
+
if (root.securityDefinitions && Object.keys(root.securityDefinitions).length === 0) {
|
|
225
|
+
delete root["securityDefinitions"];
|
|
226
|
+
}
|
|
227
|
+
if (!program.compilerOptions.noEmit && !program.hasError()) {
|
|
228
|
+
// Sort the document
|
|
229
|
+
const sortedRoot = sortOpenAPIDocument(root);
|
|
230
|
+
const outputFile = await getOutputfolderForVersion(program, service, multipleService, options, version);
|
|
231
|
+
// Write out the OpenAPI document to the output path
|
|
232
|
+
await emitFile(program, {
|
|
233
|
+
path: resolvePath(outputFile),
|
|
234
|
+
content: prettierOutput(JSON.stringify(sortedRoot, null, 2)),
|
|
235
|
+
newLine: options.newLine,
|
|
236
|
+
});
|
|
237
|
+
// Copy examples to the output directory
|
|
238
|
+
if (options.examplesDirectory && operationIdsWithExample.size > 0) {
|
|
239
|
+
const examplesPath = resolvePath(getDirectoryPath(outputFile), "examples");
|
|
240
|
+
const exampleDir = version
|
|
241
|
+
? resolvePath(options.examplesDirectory, version)
|
|
242
|
+
: resolvePath(options.examplesDirectory);
|
|
243
|
+
await program.host.mkdirp(examplesPath);
|
|
244
|
+
for (const operationId of operationIdsWithExample) {
|
|
245
|
+
const examples = operationExamplesMap.get(operationId);
|
|
246
|
+
if (examples) {
|
|
247
|
+
for (const [_, fileName] of Object.entries(examples)) {
|
|
248
|
+
const content = await program.host.readFile(resolvePath(exampleDir, fileName));
|
|
249
|
+
await emitFile(program, {
|
|
250
|
+
path: resolvePath(examplesPath, fileName),
|
|
251
|
+
content: content.text,
|
|
252
|
+
newLine: options.newLine,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
if (err instanceof ErrorTypeFoundError) {
|
|
262
|
+
// Return early, there must be a parse error if an ErrorType was
|
|
263
|
+
// inserted into the TypeSpec output
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function pascalCaseForOperationId(name) {
|
|
272
|
+
return name
|
|
273
|
+
.split("_")
|
|
274
|
+
.map((s) => pascalCase(s))
|
|
275
|
+
.join("_");
|
|
276
|
+
}
|
|
277
|
+
function parseNextLinkName(paged) {
|
|
278
|
+
var _a;
|
|
279
|
+
const pathComponents = (_a = paged.nextLinkPath) === null || _a === void 0 ? void 0 : _a.split(".");
|
|
280
|
+
if (pathComponents) {
|
|
281
|
+
// TODO: This logic breaks down if there actually is a dotted path.
|
|
282
|
+
return pathComponents[pathComponents.length - 1];
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
function extractPagedMetadataNested(program, type) {
|
|
287
|
+
// This only works for `is Page<T>` not `extends Page<T>`.
|
|
288
|
+
let paged = getPagedResult(program, type);
|
|
289
|
+
if (paged) {
|
|
290
|
+
return paged;
|
|
291
|
+
}
|
|
292
|
+
if (type.baseModel) {
|
|
293
|
+
paged = getPagedResult(program, type.baseModel);
|
|
294
|
+
}
|
|
295
|
+
if (paged) {
|
|
296
|
+
return paged;
|
|
297
|
+
}
|
|
298
|
+
const templateArguments = type.templateArguments;
|
|
299
|
+
if (templateArguments) {
|
|
300
|
+
for (const argument of templateArguments) {
|
|
301
|
+
const modelArgument = argument;
|
|
302
|
+
if (modelArgument) {
|
|
303
|
+
paged = extractPagedMetadataNested(program, modelArgument);
|
|
304
|
+
if (paged) {
|
|
305
|
+
return paged;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return paged;
|
|
311
|
+
}
|
|
312
|
+
function extractPagedMetadata(program, operation) {
|
|
313
|
+
for (const response of operation.responses) {
|
|
314
|
+
const paged = extractPagedMetadataNested(program, response.type);
|
|
315
|
+
if (paged) {
|
|
316
|
+
const nextLinkName = parseNextLinkName(paged);
|
|
317
|
+
if (nextLinkName) {
|
|
318
|
+
currentEndpoint["x-ms-pageable"] = {
|
|
319
|
+
nextLinkName,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
// Once we find paged metadata, we don't need to processes any further.
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function emitOperation(operation) {
|
|
328
|
+
const { path: fullPath, operation: op, verb, parameters } = operation;
|
|
329
|
+
// If path contains a literal query string parameter, add it to x-ms-paths instead
|
|
330
|
+
const pathsObject = fullPath.indexOf("?") < 0 ? root.paths : root["x-ms-paths"];
|
|
331
|
+
if (!pathsObject[fullPath]) {
|
|
332
|
+
pathsObject[fullPath] = {};
|
|
333
|
+
}
|
|
334
|
+
currentPath = pathsObject[fullPath];
|
|
335
|
+
if (!currentPath[verb]) {
|
|
336
|
+
currentPath[verb] = {};
|
|
337
|
+
}
|
|
338
|
+
currentEndpoint = currentPath[verb];
|
|
339
|
+
currentConsumes = new Set();
|
|
340
|
+
currentProduces = new Set();
|
|
341
|
+
const currentTags = getAllTags(program, op);
|
|
342
|
+
if (currentTags) {
|
|
343
|
+
currentEndpoint.tags = currentTags;
|
|
344
|
+
for (const tag of currentTags) {
|
|
345
|
+
// Add to root tags if not already there
|
|
346
|
+
tags.add(tag);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
currentEndpoint.operationId = pascalCaseForOperationId(resolveOperationId(program, op));
|
|
350
|
+
applyExternalDocs(op, currentEndpoint);
|
|
351
|
+
// Set up basic endpoint fields
|
|
352
|
+
currentEndpoint.summary = getSummary(program, op);
|
|
353
|
+
currentEndpoint.description = getDoc(program, op);
|
|
354
|
+
currentEndpoint.parameters = [];
|
|
355
|
+
currentEndpoint.responses = {};
|
|
356
|
+
// Extract paged metadata from Azure.Core.Page
|
|
357
|
+
extractPagedMetadata(program, operation);
|
|
358
|
+
const visibility = getRequestVisibility(verb);
|
|
359
|
+
emitEndpointParameters(parameters, visibility);
|
|
360
|
+
emitResponses(operation.responses);
|
|
361
|
+
applyEndpointConsumes();
|
|
362
|
+
applyEndpointProduces();
|
|
363
|
+
if (isDeprecated(program, op)) {
|
|
364
|
+
currentEndpoint.deprecated = true;
|
|
365
|
+
}
|
|
366
|
+
const examples = getExamples(program, op);
|
|
367
|
+
if (examples) {
|
|
368
|
+
currentEndpoint["x-ms-examples"] = examples.reduce((acc, example) => ({ ...acc, [example.title]: { $ref: example.pathOrUri } }), {});
|
|
369
|
+
}
|
|
370
|
+
if (options.examplesDirectory) {
|
|
371
|
+
const examples = operationExamplesMap.get(currentEndpoint.operationId);
|
|
372
|
+
if (examples && currentEndpoint.operationId) {
|
|
373
|
+
operationIdsWithExample.add(currentEndpoint.operationId);
|
|
374
|
+
currentEndpoint["x-ms-examples"] = currentEndpoint["x-ms-examples"] || {};
|
|
375
|
+
for (const [title, fileName] of Object.entries(examples)) {
|
|
376
|
+
currentEndpoint["x-ms-examples"][title] = { $ref: `./examples/${fileName}` };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Attach additional extensions after main fields
|
|
381
|
+
attachExtensions(op, currentEndpoint);
|
|
382
|
+
}
|
|
383
|
+
function applyEndpointProduces() {
|
|
384
|
+
if (currentProduces.size > 0 && !checkLocalAndGlobalEqual(globalProduces, currentProduces)) {
|
|
385
|
+
currentEndpoint.produces = [...currentProduces];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function applyEndpointConsumes() {
|
|
389
|
+
if (currentConsumes.size > 0 && !checkLocalAndGlobalEqual(globalConsumes, currentConsumes)) {
|
|
390
|
+
currentEndpoint.consumes = [...currentConsumes];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function checkLocalAndGlobalEqual(global, local) {
|
|
394
|
+
if (global.size !== local.size) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
for (const entry of local) {
|
|
398
|
+
if (!global.has(entry)) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
function isBinaryPayload(body, contentType) {
|
|
405
|
+
const types = new Set(typeof contentType === "string" ? [contentType] : contentType);
|
|
406
|
+
return (body.kind === "Scalar" &&
|
|
407
|
+
body.name === "bytes" &&
|
|
408
|
+
!types.has("application/json") &&
|
|
409
|
+
!types.has("text/plain"));
|
|
410
|
+
}
|
|
411
|
+
function emitResponses(responses) {
|
|
412
|
+
for (const response of responses) {
|
|
413
|
+
emitResponseObject(response);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function getOpenAPIStatuscode(response) {
|
|
417
|
+
switch (response.statusCode) {
|
|
418
|
+
case "*":
|
|
419
|
+
return "default";
|
|
420
|
+
default:
|
|
421
|
+
return response.statusCode;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function getResponseDescriptionForStatusCode(statusCode) {
|
|
425
|
+
var _a;
|
|
426
|
+
if (statusCode === "default") {
|
|
427
|
+
return "An unexpected error response.";
|
|
428
|
+
}
|
|
429
|
+
return (_a = getStatusCodeDescription(statusCode)) !== null && _a !== void 0 ? _a : "unknown";
|
|
430
|
+
}
|
|
431
|
+
function emitResponseObject(response) {
|
|
432
|
+
var _a, _b, _c;
|
|
433
|
+
const statusCode = getOpenAPIStatuscode(response);
|
|
434
|
+
const openapiResponse = (_a = currentEndpoint.responses[statusCode]) !== null && _a !== void 0 ? _a : {
|
|
435
|
+
description: (_b = response.description) !== null && _b !== void 0 ? _b : getResponseDescriptionForStatusCode(statusCode),
|
|
436
|
+
};
|
|
437
|
+
if (isErrorModel(program, response.type) && statusCode !== "default") {
|
|
438
|
+
openapiResponse["x-ms-error-response"] = true;
|
|
439
|
+
}
|
|
440
|
+
const contentTypes = [];
|
|
441
|
+
let body;
|
|
442
|
+
for (const data of response.responses) {
|
|
443
|
+
if (data.headers && Object.keys(data.headers).length > 0) {
|
|
444
|
+
(_c = openapiResponse.headers) !== null && _c !== void 0 ? _c : (openapiResponse.headers = {});
|
|
445
|
+
for (const [key, value] of Object.entries(data.headers)) {
|
|
446
|
+
openapiResponse.headers[key] = getResponseHeader(value);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (data.body) {
|
|
450
|
+
if (body && body !== data.body.type) {
|
|
451
|
+
reportDiagnostic(program, {
|
|
452
|
+
code: "duplicate-body-types",
|
|
453
|
+
target: response.type,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
body = data.body.type;
|
|
457
|
+
contentTypes.push(...data.body.contentTypes);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (body) {
|
|
461
|
+
const isBinary = contentTypes.every((t) => isBinaryPayload(body, t));
|
|
462
|
+
openapiResponse.schema = isBinary ? { type: "file" } : getSchemaOrRef(body, Visibility.Read);
|
|
463
|
+
}
|
|
464
|
+
for (const contentType of contentTypes) {
|
|
465
|
+
currentProduces.add(contentType);
|
|
466
|
+
}
|
|
467
|
+
currentEndpoint.responses[statusCode] = openapiResponse;
|
|
468
|
+
}
|
|
469
|
+
function getResponseHeader(prop) {
|
|
470
|
+
const header = {};
|
|
471
|
+
populateParameter(header, prop, "header", Visibility.Read);
|
|
472
|
+
delete header.in;
|
|
473
|
+
delete header.name;
|
|
474
|
+
delete header.required;
|
|
475
|
+
return header;
|
|
476
|
+
}
|
|
477
|
+
function getSchemaOrRef(type, visibility) {
|
|
478
|
+
var _a;
|
|
479
|
+
const refUrl = getRef(program, type);
|
|
480
|
+
if (refUrl) {
|
|
481
|
+
return {
|
|
482
|
+
$ref: refUrl,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
if (type.kind === "Scalar" && program.checker.isStdType(type)) {
|
|
486
|
+
return getSchemaForScalar(type);
|
|
487
|
+
}
|
|
488
|
+
if (type.kind === "String" || type.kind === "Number" || type.kind === "Boolean") {
|
|
489
|
+
// For literal types, we just want to emit them directly as well.
|
|
490
|
+
return mapTypeSpecTypeToOpenAPI(type, visibility);
|
|
491
|
+
}
|
|
492
|
+
if (type.kind === "Intrinsic" && type.name === "unknown") {
|
|
493
|
+
return getSchemaForIntrinsicType(type);
|
|
494
|
+
}
|
|
495
|
+
if (type.kind === "EnumMember") {
|
|
496
|
+
// Enum members are just the OA representation of their values.
|
|
497
|
+
if (typeof type.value === "number") {
|
|
498
|
+
return { type: "number", enum: [type.value] };
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
return { type: "string", enum: [(_a = type.value) !== null && _a !== void 0 ? _a : type.name] };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (type.kind === "ModelProperty") {
|
|
505
|
+
return resolveProperty(type, visibility);
|
|
506
|
+
}
|
|
507
|
+
type = metadataInfo.getEffectivePayloadType(type, visibility);
|
|
508
|
+
const name = getOpenAPITypeName(program, type, typeNameOptions);
|
|
509
|
+
if (shouldInline(program, type)) {
|
|
510
|
+
const schema = getSchemaForInlineType(type, name, visibility);
|
|
511
|
+
if (schema === undefined && isErrorType(type)) {
|
|
512
|
+
// Exit early so that syntax errors are exposed. This error will
|
|
513
|
+
// be caught and handled in emitOpenAPI.
|
|
514
|
+
throw new ErrorTypeFoundError();
|
|
515
|
+
}
|
|
516
|
+
// helps to read output and correlate to TypeSpec
|
|
517
|
+
if (schema) {
|
|
518
|
+
schema["x-typespec-name"] = name;
|
|
519
|
+
}
|
|
520
|
+
return schema;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
// Use shared schema when type is not transformed by visibility.
|
|
524
|
+
if (!metadataInfo.isTransformed(type, visibility)) {
|
|
525
|
+
visibility = Visibility.All;
|
|
526
|
+
}
|
|
527
|
+
const pending = pendingSchemas.getOrAdd(type, visibility, () => ({
|
|
528
|
+
type,
|
|
529
|
+
visibility,
|
|
530
|
+
ref: refs.getOrAdd(type, visibility, () => new Ref()),
|
|
531
|
+
}));
|
|
532
|
+
return { $ref: pending.ref };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function getSchemaForInlineType(type, name, visibility) {
|
|
536
|
+
if (inProgressInlineTypes.has(type)) {
|
|
537
|
+
reportDiagnostic(program, {
|
|
538
|
+
code: "inline-cycle",
|
|
539
|
+
format: { type: name },
|
|
540
|
+
target: type,
|
|
541
|
+
});
|
|
542
|
+
return {};
|
|
543
|
+
}
|
|
544
|
+
inProgressInlineTypes.add(type);
|
|
545
|
+
const schema = getSchemaForType(type, visibility);
|
|
546
|
+
inProgressInlineTypes.delete(type);
|
|
547
|
+
return schema;
|
|
548
|
+
}
|
|
549
|
+
function getParamPlaceholder(property) {
|
|
550
|
+
let spreadParam = false;
|
|
551
|
+
if (property.sourceProperty) {
|
|
552
|
+
// chase our sources all the way back to the first place this property
|
|
553
|
+
// was defined.
|
|
554
|
+
spreadParam = true;
|
|
555
|
+
property = property.sourceProperty;
|
|
556
|
+
while (property.sourceProperty) {
|
|
557
|
+
property = property.sourceProperty;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const refUrl = getRef(program, property);
|
|
561
|
+
if (refUrl) {
|
|
562
|
+
return {
|
|
563
|
+
$ref: refUrl,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const parameter = params.get(property);
|
|
567
|
+
if (parameter) {
|
|
568
|
+
return parameter;
|
|
569
|
+
}
|
|
570
|
+
const placeholder = {};
|
|
571
|
+
// only parameters inherited by spreading from non-inlined type are shared in #/parameters
|
|
572
|
+
if (spreadParam && property.model && !shouldInline(program, property.model)) {
|
|
573
|
+
params.set(property, placeholder);
|
|
574
|
+
paramModels.add(property.model);
|
|
575
|
+
}
|
|
576
|
+
return placeholder;
|
|
577
|
+
}
|
|
578
|
+
function emitEndpointParameters(methodParams, visibility) {
|
|
579
|
+
var _a, _b;
|
|
580
|
+
const consumes = (_b = (_a = methodParams.body) === null || _a === void 0 ? void 0 : _a.contentTypes) !== null && _b !== void 0 ? _b : [];
|
|
581
|
+
for (const httpOpParam of methodParams.parameters) {
|
|
582
|
+
const shared = params.get(httpOpParam.param);
|
|
583
|
+
if (shared) {
|
|
584
|
+
currentEndpoint.parameters.push(shared);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (httpOpParam.type === "header" && isContentTypeHeader(program, httpOpParam.param)) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
emitParameter(httpOpParam.param, httpOpParam.type, visibility, httpOpParam.name);
|
|
591
|
+
}
|
|
592
|
+
if (consumes.length === 0 && methodParams.body) {
|
|
593
|
+
// we didn't find an explicit content type anywhere, so infer from body.
|
|
594
|
+
if (getModelOrScalarTypeIfNullable(methodParams.body.type)) {
|
|
595
|
+
consumes.push("application/json");
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
for (const consume of consumes) {
|
|
599
|
+
currentConsumes.add(consume);
|
|
600
|
+
}
|
|
601
|
+
if (methodParams.body) {
|
|
602
|
+
const isBinary = isBinaryPayload(methodParams.body.type, consumes);
|
|
603
|
+
const schema = isBinary
|
|
604
|
+
? { type: "string", format: "binary" }
|
|
605
|
+
: getSchemaOrRef(methodParams.body.type, visibility);
|
|
606
|
+
if (currentConsumes.has("multipart/form-data")) {
|
|
607
|
+
const bodyModelType = methodParams.body.type;
|
|
608
|
+
// Assert, this should never happen. Rest library guard against that.
|
|
609
|
+
compilerAssert(bodyModelType.kind === "Model", "Body should always be a Model.");
|
|
610
|
+
if (bodyModelType) {
|
|
611
|
+
for (const param of bodyModelType.properties.values()) {
|
|
612
|
+
emitParameter(param, "formData", visibility, jsonView.getProjectedName(param));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else if (methodParams.body.parameter) {
|
|
617
|
+
emitParameter(methodParams.body.parameter, "body", visibility, jsonView.getProjectedName(methodParams.body.parameter), schema);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
currentEndpoint.parameters.push({
|
|
621
|
+
name: "body",
|
|
622
|
+
in: "body",
|
|
623
|
+
schema,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function getModelOrScalarTypeIfNullable(type) {
|
|
629
|
+
if (type.kind === "Model" || type.kind === "Scalar") {
|
|
630
|
+
return type;
|
|
631
|
+
}
|
|
632
|
+
else if (type.kind === "Union") {
|
|
633
|
+
// Remove all `null` types and make sure there's a single model type
|
|
634
|
+
const nonNulls = [...type.variants.values()]
|
|
635
|
+
.map((x) => x.type)
|
|
636
|
+
.filter((variant) => !isNullType(variant));
|
|
637
|
+
if (nonNulls.every((t) => t.kind === "Model" || t.kind === "Scalar")) {
|
|
638
|
+
return nonNulls.length === 1 ? nonNulls[0] : undefined;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
function emitParameter(param, kind, visibility, name, typeOverride) {
|
|
644
|
+
if (isNeverType(param.type)) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const ph = getParamPlaceholder(param);
|
|
648
|
+
currentEndpoint.parameters.push(ph);
|
|
649
|
+
// If the parameter already has a $ref, don't bother populating it
|
|
650
|
+
if (!("$ref" in ph)) {
|
|
651
|
+
populateParameter(ph, param, kind, visibility, name, typeOverride);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function getOpenAPI2Parameter(param, kind, visibility, name, bodySchema) {
|
|
655
|
+
var _a, _b;
|
|
656
|
+
const ph = {
|
|
657
|
+
name: name !== null && name !== void 0 ? name : param.name,
|
|
658
|
+
in: kind,
|
|
659
|
+
required: !param.optional,
|
|
660
|
+
description: getDoc(program, param),
|
|
661
|
+
};
|
|
662
|
+
if (param.name !== ph.name) {
|
|
663
|
+
ph["x-ms-client-name"] = param.name;
|
|
664
|
+
}
|
|
665
|
+
if (param.default) {
|
|
666
|
+
ph.default = getDefaultValue(param.default);
|
|
667
|
+
}
|
|
668
|
+
// Apply decorators to a copy of the parameter definition. We use
|
|
669
|
+
// Object.assign here because applyIntrinsicDecorators returns a new object
|
|
670
|
+
// based on the target object and we need to apply its changes back to the
|
|
671
|
+
// original parameter.
|
|
672
|
+
Object.assign(ph, applyIntrinsicDecorators(param, {}));
|
|
673
|
+
if (ph.in === "body") {
|
|
674
|
+
compilerAssert(bodySchema, "bodySchema argument is required to populate body parameter");
|
|
675
|
+
ph.schema = bodySchema;
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
const schema = getSchemaForType(param.type, visibility);
|
|
679
|
+
const collectionFormat = (_a = getCollectionFormat(program, param)) !== null && _a !== void 0 ? _a : (_b = (kind === "query"
|
|
680
|
+
? getQueryParamOptions(program, param)
|
|
681
|
+
: kind === "header"
|
|
682
|
+
? getHeaderFieldOptions(program, param)
|
|
683
|
+
: undefined)) === null || _b === void 0 ? void 0 : _b.format;
|
|
684
|
+
if (collectionFormat === "multi" && !["query", "header", "formData"].includes(ph.in)) {
|
|
685
|
+
reportDiagnostic(program, { code: "invalid-multi-collection-format", target: param });
|
|
686
|
+
}
|
|
687
|
+
if (collectionFormat) {
|
|
688
|
+
ph.collectionFormat = collectionFormat;
|
|
689
|
+
}
|
|
690
|
+
if (schema) {
|
|
691
|
+
for (const property in schema) {
|
|
692
|
+
ph[property] = schema[property];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
attachExtensions(param, ph);
|
|
697
|
+
return ph;
|
|
698
|
+
}
|
|
699
|
+
function populateParameter(ph, param, kind, visibility, name, bodySchema) {
|
|
700
|
+
Object.assign(ph, getOpenAPI2Parameter(param, kind, visibility, name, bodySchema));
|
|
701
|
+
}
|
|
702
|
+
function emitParameters() {
|
|
703
|
+
for (const [property, param] of params) {
|
|
704
|
+
// Add an extension which tells AutoRest that this is a shared operation
|
|
705
|
+
// parameter definition
|
|
706
|
+
if (param["x-ms-parameter-location"] === undefined) {
|
|
707
|
+
param["x-ms-parameter-location"] = "method";
|
|
708
|
+
}
|
|
709
|
+
const key = getParameterKey(program, property, param, root.parameters, typeNameOptions);
|
|
710
|
+
root.parameters[key] = { ...param };
|
|
711
|
+
const refedParam = param;
|
|
712
|
+
for (const key of Object.keys(param)) {
|
|
713
|
+
delete refedParam[key];
|
|
714
|
+
}
|
|
715
|
+
refedParam["$ref"] = "#/parameters/" + encodeURIComponent(key);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function emitSchemas(serviceNamespace) {
|
|
719
|
+
const processedSchemas = new TwoLevelMap();
|
|
720
|
+
processSchemas();
|
|
721
|
+
if (!options.omitUnreachableTypes) {
|
|
722
|
+
processUnreferencedSchemas();
|
|
723
|
+
}
|
|
724
|
+
// Emit the processed schemas. Only now can we compute the names as it
|
|
725
|
+
// depends on whether we have produced multiple schemas for a single
|
|
726
|
+
// TYPESPEC type.
|
|
727
|
+
for (const group of processedSchemas.values()) {
|
|
728
|
+
for (const [visibility, processed] of group) {
|
|
729
|
+
let name = getOpenAPITypeName(program, processed.type, typeNameOptions);
|
|
730
|
+
if (group.size > 1) {
|
|
731
|
+
name += getVisibilitySuffix(visibility);
|
|
732
|
+
}
|
|
733
|
+
checkDuplicateTypeName(program, processed.type, name, root.definitions);
|
|
734
|
+
processed.ref.value = "#/definitions/" + encodeURIComponent(name);
|
|
735
|
+
if (processed.schema) {
|
|
736
|
+
root.definitions[name] = processed.schema;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function processSchemas() {
|
|
741
|
+
// Process pending schemas. Note that getSchemaForType may pull in new
|
|
742
|
+
// pending schemas so we iterate until there are no pending schemas
|
|
743
|
+
// remaining.
|
|
744
|
+
while (pendingSchemas.size > 0) {
|
|
745
|
+
for (const [type, group] of pendingSchemas) {
|
|
746
|
+
for (const [visibility, pending] of group) {
|
|
747
|
+
processedSchemas.getOrAdd(type, visibility, () => ({
|
|
748
|
+
...pending,
|
|
749
|
+
schema: getSchemaForType(type, visibility),
|
|
750
|
+
}));
|
|
751
|
+
}
|
|
752
|
+
pendingSchemas.delete(type);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function processUnreferencedSchemas() {
|
|
757
|
+
const addSchema = (type) => {
|
|
758
|
+
if (!processedSchemas.has(type) && !paramModels.has(type) && !shouldInline(program, type)) {
|
|
759
|
+
getSchemaOrRef(type, Visibility.All);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
const skipSubNamespaces = isGlobalNamespace(program, serviceNamespace);
|
|
763
|
+
navigateTypesInNamespace(serviceNamespace, {
|
|
764
|
+
model: addSchema,
|
|
765
|
+
scalar: addSchema,
|
|
766
|
+
enum: addSchema,
|
|
767
|
+
union: addSchema,
|
|
768
|
+
}, { skipSubNamespaces });
|
|
769
|
+
processSchemas();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function emitTags() {
|
|
773
|
+
for (const tag of tags) {
|
|
774
|
+
root.tags.push({ name: tag });
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function loadExamples(version) {
|
|
778
|
+
if (options.examplesDirectory) {
|
|
779
|
+
const exampleDir = version
|
|
780
|
+
? resolvePath(options.examplesDirectory, version)
|
|
781
|
+
: resolvePath(options.examplesDirectory);
|
|
782
|
+
try {
|
|
783
|
+
if (!(await program.host.stat(exampleDir)).isDirectory())
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
reportDiagnostic(program, {
|
|
788
|
+
code: "example-loading",
|
|
789
|
+
messageId: "noDirectory",
|
|
790
|
+
format: { directory: exampleDir },
|
|
791
|
+
target: NoTarget,
|
|
792
|
+
});
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const exampleFiles = await program.host.readDir(exampleDir);
|
|
796
|
+
for (const fileName of exampleFiles) {
|
|
797
|
+
try {
|
|
798
|
+
const exampleFile = await program.host.readFile(resolvePath(exampleDir, fileName));
|
|
799
|
+
const example = JSON.parse(exampleFile.text);
|
|
800
|
+
if (!example.operationId || !example.title) {
|
|
801
|
+
reportDiagnostic(program, {
|
|
802
|
+
code: "example-loading",
|
|
803
|
+
messageId: "noOperationId",
|
|
804
|
+
format: { filename: fileName },
|
|
805
|
+
target: NoTarget,
|
|
806
|
+
});
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (!operationExamplesMap.has(example.operationId)) {
|
|
810
|
+
operationExamplesMap.set(example.operationId, {});
|
|
811
|
+
}
|
|
812
|
+
operationExamplesMap.get(example.operationId)[example.title] = fileName;
|
|
813
|
+
}
|
|
814
|
+
catch (err) {
|
|
815
|
+
reportDiagnostic(program, {
|
|
816
|
+
code: "example-loading",
|
|
817
|
+
messageId: "default",
|
|
818
|
+
format: { filename: fileName },
|
|
819
|
+
target: NoTarget,
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
function getSchemaForType(type, visibility) {
|
|
826
|
+
const builtinType = mapTypeSpecTypeToOpenAPI(type, visibility);
|
|
827
|
+
if (builtinType !== undefined) {
|
|
828
|
+
// add in description elements for types derived from primitive types (SecureString, etc.)
|
|
829
|
+
const doc = getDoc(program, type);
|
|
830
|
+
if (doc) {
|
|
831
|
+
builtinType.description = doc;
|
|
832
|
+
}
|
|
833
|
+
return builtinType;
|
|
834
|
+
}
|
|
835
|
+
switch (type.kind) {
|
|
836
|
+
case "Intrinsic":
|
|
837
|
+
return getSchemaForIntrinsicType(type);
|
|
838
|
+
case "Model":
|
|
839
|
+
return getSchemaForModel(type, visibility);
|
|
840
|
+
case "ModelProperty":
|
|
841
|
+
return getSchemaForType(type.type, visibility);
|
|
842
|
+
case "Scalar":
|
|
843
|
+
return getSchemaForScalar(type);
|
|
844
|
+
case "Union":
|
|
845
|
+
return getSchemaForUnion(type, visibility);
|
|
846
|
+
case "UnionVariant":
|
|
847
|
+
return getSchemaForUnionVariant(type, visibility);
|
|
848
|
+
case "Enum":
|
|
849
|
+
return getSchemaForEnum(type);
|
|
850
|
+
case "Tuple":
|
|
851
|
+
return { type: "array", items: {} };
|
|
852
|
+
}
|
|
853
|
+
reportDiagnostic(program, {
|
|
854
|
+
code: "invalid-schema",
|
|
855
|
+
format: { type: type.kind },
|
|
856
|
+
target: type,
|
|
857
|
+
});
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
function getSchemaForIntrinsicType(type) {
|
|
861
|
+
switch (type.name) {
|
|
862
|
+
case "unknown":
|
|
863
|
+
return {};
|
|
864
|
+
}
|
|
865
|
+
reportDiagnostic(program, {
|
|
866
|
+
code: "invalid-schema",
|
|
867
|
+
format: { type: type.name },
|
|
868
|
+
target: type,
|
|
869
|
+
});
|
|
870
|
+
return {};
|
|
871
|
+
}
|
|
872
|
+
function getSchemaForEnum(e) {
|
|
873
|
+
var _a;
|
|
874
|
+
const values = [];
|
|
875
|
+
const type = getEnumMemberType(e.members.values().next().value);
|
|
876
|
+
for (const option of e.members.values()) {
|
|
877
|
+
if (type !== getEnumMemberType(option)) {
|
|
878
|
+
reportDiagnostic(program, { code: "union-unsupported", target: e });
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
values.push((_a = option.value) !== null && _a !== void 0 ? _a : option.name);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const schema = { type, description: getDoc(program, e) };
|
|
885
|
+
if (values.length > 0) {
|
|
886
|
+
schema.enum = values;
|
|
887
|
+
addXMSEnum(e, schema);
|
|
888
|
+
}
|
|
889
|
+
return schema;
|
|
890
|
+
function getEnumMemberType(member) {
|
|
891
|
+
if (typeof member.value === "number") {
|
|
892
|
+
return "number";
|
|
893
|
+
}
|
|
894
|
+
return "string";
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function getSchemaForUnion(union, visibility) {
|
|
898
|
+
const nonNullOptions = [...union.variants.values()]
|
|
899
|
+
.map((x) => x.type)
|
|
900
|
+
.filter((t) => !isNullType(t));
|
|
901
|
+
const nullable = union.variants.size !== nonNullOptions.length;
|
|
902
|
+
if (nonNullOptions.length === 0) {
|
|
903
|
+
reportDiagnostic(program, { code: "union-null", target: union });
|
|
904
|
+
return {};
|
|
905
|
+
}
|
|
906
|
+
let type;
|
|
907
|
+
const kind = nonNullOptions[0].kind;
|
|
908
|
+
switch (kind) {
|
|
909
|
+
case "String":
|
|
910
|
+
type = "string";
|
|
911
|
+
break;
|
|
912
|
+
case "Number":
|
|
913
|
+
type = "number";
|
|
914
|
+
break;
|
|
915
|
+
case "Boolean":
|
|
916
|
+
type = "boolean";
|
|
917
|
+
break;
|
|
918
|
+
case "Model":
|
|
919
|
+
case "Scalar":
|
|
920
|
+
// Model unions can only ever be a model type with 'null'
|
|
921
|
+
if (nonNullOptions.length === 1) {
|
|
922
|
+
// Get the schema for the model type
|
|
923
|
+
const schema = getSchemaOrRef(nonNullOptions[0], visibility);
|
|
924
|
+
if (schema.$ref) {
|
|
925
|
+
return { type: "object", allOf: [schema], "x-nullable": nullable };
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
schema["x-nullable"] = nullable;
|
|
929
|
+
return schema;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
reportDiagnostic(program, {
|
|
934
|
+
code: "union-unsupported",
|
|
935
|
+
messageId: "null",
|
|
936
|
+
target: union,
|
|
937
|
+
});
|
|
938
|
+
return {};
|
|
939
|
+
}
|
|
940
|
+
default:
|
|
941
|
+
reportInvalidUnionForOpenAPIV2();
|
|
942
|
+
return {};
|
|
943
|
+
}
|
|
944
|
+
const values = [];
|
|
945
|
+
for (const option of nonNullOptions) {
|
|
946
|
+
if (option.kind !== kind) {
|
|
947
|
+
reportInvalidUnionForOpenAPIV2();
|
|
948
|
+
}
|
|
949
|
+
// We already know it's not a model type
|
|
950
|
+
values.push(option.value);
|
|
951
|
+
}
|
|
952
|
+
const schema = { type };
|
|
953
|
+
if (values.length > 0) {
|
|
954
|
+
schema.enum = values;
|
|
955
|
+
addXMSEnum(union, schema);
|
|
956
|
+
}
|
|
957
|
+
if (nullable) {
|
|
958
|
+
schema["x-nullable"] = true;
|
|
959
|
+
}
|
|
960
|
+
return schema;
|
|
961
|
+
function reportInvalidUnionForOpenAPIV2() {
|
|
962
|
+
reportDiagnostic(program, {
|
|
963
|
+
code: "union-unsupported",
|
|
964
|
+
target: union,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function ifArrayItemContainsIdentifier(program, array) {
|
|
969
|
+
var _a;
|
|
970
|
+
if (((_a = array.indexer.value) === null || _a === void 0 ? void 0 : _a.kind) !== "Model") {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
return (getExtensions(program, array).has("x-ms-identifiers") ||
|
|
974
|
+
getProperty(array.indexer.value, "id"));
|
|
975
|
+
}
|
|
976
|
+
function getSchemaForUnionVariant(variant, visibility) {
|
|
977
|
+
return getSchemaForType(variant.type, visibility);
|
|
978
|
+
}
|
|
979
|
+
function getDefaultValue(type) {
|
|
980
|
+
var _a;
|
|
981
|
+
switch (type.kind) {
|
|
982
|
+
case "String":
|
|
983
|
+
return type.value;
|
|
984
|
+
case "Number":
|
|
985
|
+
return type.value;
|
|
986
|
+
case "Boolean":
|
|
987
|
+
return type.value;
|
|
988
|
+
case "Tuple":
|
|
989
|
+
return type.values.map(getDefaultValue);
|
|
990
|
+
case "EnumMember":
|
|
991
|
+
return (_a = type.value) !== null && _a !== void 0 ? _a : type.name;
|
|
992
|
+
default:
|
|
993
|
+
reportDiagnostic(program, {
|
|
994
|
+
code: "invalid-default",
|
|
995
|
+
format: { type: type.kind },
|
|
996
|
+
target: type,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function includeDerivedModel(model) {
|
|
1001
|
+
var _a, _b;
|
|
1002
|
+
return (!isTemplateDeclaration(model) &&
|
|
1003
|
+
(((_a = model.templateMapper) === null || _a === void 0 ? void 0 : _a.args) === undefined ||
|
|
1004
|
+
((_b = model.templateMapper) === null || _b === void 0 ? void 0 : _b.args.length) === 0 ||
|
|
1005
|
+
model.derivedModels.length > 0));
|
|
1006
|
+
}
|
|
1007
|
+
function getSchemaForModel(model, visibility) {
|
|
1008
|
+
var _a;
|
|
1009
|
+
let modelSchema = {
|
|
1010
|
+
type: "object",
|
|
1011
|
+
properties: {},
|
|
1012
|
+
description: getDoc(program, model),
|
|
1013
|
+
};
|
|
1014
|
+
const derivedModels = model.derivedModels.filter(includeDerivedModel);
|
|
1015
|
+
// getSchemaOrRef on all children to push them into components.schemas
|
|
1016
|
+
for (const child of derivedModels) {
|
|
1017
|
+
getSchemaOrRef(child, visibility);
|
|
1018
|
+
}
|
|
1019
|
+
const discriminator = getDiscriminator(program, model);
|
|
1020
|
+
if (discriminator) {
|
|
1021
|
+
const { propertyName } = discriminator;
|
|
1022
|
+
for (const child of derivedModels) {
|
|
1023
|
+
// Set x-ms-discriminator-value if only one value for the discriminator property
|
|
1024
|
+
const prop = getProperty(child, propertyName);
|
|
1025
|
+
if (prop) {
|
|
1026
|
+
const values = getStringValues(prop.type);
|
|
1027
|
+
if (values.length === 1) {
|
|
1028
|
+
const extensions = getExtensions(program, child);
|
|
1029
|
+
if (!extensions.has("x-ms-discriminator-value")) {
|
|
1030
|
+
setExtension(program, child, "x-ms-discriminator-value", values[0]);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
modelSchema.discriminator = propertyName;
|
|
1036
|
+
// Push discriminator into base type, but only if it is not already there
|
|
1037
|
+
if (!((_a = model.properties) === null || _a === void 0 ? void 0 : _a.get(propertyName))) {
|
|
1038
|
+
modelSchema.properties[propertyName] = {
|
|
1039
|
+
type: "string",
|
|
1040
|
+
description: `Discriminator property for ${model.name}.`,
|
|
1041
|
+
};
|
|
1042
|
+
modelSchema.required = [propertyName];
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
applyExternalDocs(model, modelSchema);
|
|
1046
|
+
for (const [clientName, prop] of model.properties) {
|
|
1047
|
+
if (!metadataInfo.isPayloadProperty(prop, visibility)) {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (isNeverType(prop.type)) {
|
|
1051
|
+
// If the property has a type of 'never', don't include it in the schema
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
const jsonName = jsonView.getProjectedName(prop);
|
|
1055
|
+
const description = getDoc(program, prop);
|
|
1056
|
+
// if this property is a discriminator property, remove it to keep autorest validation happy
|
|
1057
|
+
if (model.baseModel) {
|
|
1058
|
+
const { propertyName } = getDiscriminator(program, model.baseModel) || {};
|
|
1059
|
+
if (jsonName === propertyName) {
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (!metadataInfo.isOptional(prop, visibility)) {
|
|
1064
|
+
if (!modelSchema.required) {
|
|
1065
|
+
modelSchema.required = [];
|
|
1066
|
+
}
|
|
1067
|
+
modelSchema.required.push(jsonName);
|
|
1068
|
+
}
|
|
1069
|
+
// Apply decorators on the property to the type's schema
|
|
1070
|
+
modelSchema.properties[jsonName] = resolveProperty(prop, visibility);
|
|
1071
|
+
const property = modelSchema.properties[jsonName];
|
|
1072
|
+
if (jsonName !== clientName) {
|
|
1073
|
+
property["x-ms-client-name"] = clientName;
|
|
1074
|
+
}
|
|
1075
|
+
if (description) {
|
|
1076
|
+
property.description = description;
|
|
1077
|
+
}
|
|
1078
|
+
if (prop.default) {
|
|
1079
|
+
property.default = getDefaultValue(prop.default);
|
|
1080
|
+
}
|
|
1081
|
+
if (isReadonlyProperty(program, prop)) {
|
|
1082
|
+
property.readOnly = true;
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
const vis = getVisibility(program, prop);
|
|
1086
|
+
if (vis) {
|
|
1087
|
+
const mutability = [];
|
|
1088
|
+
if (vis.includes("read")) {
|
|
1089
|
+
mutability.push("read");
|
|
1090
|
+
}
|
|
1091
|
+
if (vis.includes("update")) {
|
|
1092
|
+
mutability.push("update");
|
|
1093
|
+
}
|
|
1094
|
+
if (vis.includes("create")) {
|
|
1095
|
+
mutability.push("create");
|
|
1096
|
+
}
|
|
1097
|
+
if (mutability.length > 0) {
|
|
1098
|
+
property["x-ms-mutability"] = mutability;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Attach any additional OpenAPI extensions
|
|
1103
|
+
attachExtensions(prop, property);
|
|
1104
|
+
}
|
|
1105
|
+
// Special case: if a model type extends a single *templated* base type and
|
|
1106
|
+
// has no properties of its own, absorb the definition of the base model
|
|
1107
|
+
// into this schema definition. The assumption here is that any model type
|
|
1108
|
+
// defined like this is just meant to rename the underlying instance of a
|
|
1109
|
+
// templated type.
|
|
1110
|
+
if (model.baseModel &&
|
|
1111
|
+
isTemplateDeclarationOrInstance(model.baseModel) &&
|
|
1112
|
+
Object.keys(modelSchema.properties).length === 0) {
|
|
1113
|
+
// Take the base model schema but carry across the documentation property
|
|
1114
|
+
// that we set before
|
|
1115
|
+
const baseSchema = getSchemaForType(model.baseModel, visibility);
|
|
1116
|
+
modelSchema = {
|
|
1117
|
+
...baseSchema,
|
|
1118
|
+
description: modelSchema.description,
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
else if (model.baseModel) {
|
|
1122
|
+
const baseSchema = getSchemaOrRef(model.baseModel, visibility);
|
|
1123
|
+
modelSchema.allOf = [baseSchema];
|
|
1124
|
+
modelSchema.additionalProperties = baseSchema.additionalProperties;
|
|
1125
|
+
if (modelSchema.additionalProperties) {
|
|
1126
|
+
validateAdditionalProperties(model);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
// Attach any OpenAPI extensions
|
|
1130
|
+
attachExtensions(model, modelSchema);
|
|
1131
|
+
return modelSchema;
|
|
1132
|
+
}
|
|
1133
|
+
function canSharePropertyUsingReadonlyOrXMSMutability(prop) {
|
|
1134
|
+
const sharedVisibilities = ["read", "create", "update", "write"];
|
|
1135
|
+
const visibilities = getVisibility(program, prop);
|
|
1136
|
+
if (visibilities) {
|
|
1137
|
+
for (const visibility of visibilities) {
|
|
1138
|
+
if (!sharedVisibilities.includes(visibility)) {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
function resolveProperty(prop, visibility) {
|
|
1146
|
+
const propSchema = getSchemaOrRef(prop.type, visibility);
|
|
1147
|
+
return applyIntrinsicDecorators(prop, propSchema);
|
|
1148
|
+
}
|
|
1149
|
+
function attachExtensions(type, emitObject) {
|
|
1150
|
+
// Attach any OpenAPI extensions
|
|
1151
|
+
const extensions = getExtensions(program, type);
|
|
1152
|
+
if (extensions) {
|
|
1153
|
+
for (const key of extensions.keys()) {
|
|
1154
|
+
emitObject[key] = extensions.get(key);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
// Return any string literal values for type
|
|
1159
|
+
function getStringValues(type) {
|
|
1160
|
+
if (type.kind === "String") {
|
|
1161
|
+
return [type.value];
|
|
1162
|
+
}
|
|
1163
|
+
else if (type.kind === "Union") {
|
|
1164
|
+
return type.options.flatMap(getStringValues).filter((v) => v);
|
|
1165
|
+
}
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1168
|
+
function applyIntrinsicDecorators(typespecType, target) {
|
|
1169
|
+
const newTarget = { ...target };
|
|
1170
|
+
const docStr = getDoc(program, typespecType);
|
|
1171
|
+
const isString = isStringType(program, getPropertyType(typespecType));
|
|
1172
|
+
const isNumeric = isNumericType(program, getPropertyType(typespecType));
|
|
1173
|
+
if (!target.description && docStr) {
|
|
1174
|
+
newTarget.description = docStr;
|
|
1175
|
+
}
|
|
1176
|
+
const formatStr = getFormat(program, typespecType);
|
|
1177
|
+
if (isString && !target.format && formatStr) {
|
|
1178
|
+
newTarget.format = formatStr;
|
|
1179
|
+
}
|
|
1180
|
+
const pattern = getPattern(program, typespecType);
|
|
1181
|
+
if (isString && !target.pattern && pattern) {
|
|
1182
|
+
newTarget.pattern = pattern;
|
|
1183
|
+
}
|
|
1184
|
+
const minLength = getMinLength(program, typespecType);
|
|
1185
|
+
if (isString && !target.minLength && minLength !== undefined) {
|
|
1186
|
+
newTarget.minLength = minLength;
|
|
1187
|
+
}
|
|
1188
|
+
const maxLength = getMaxLength(program, typespecType);
|
|
1189
|
+
if (isString && !target.maxLength && maxLength !== undefined) {
|
|
1190
|
+
newTarget.maxLength = maxLength;
|
|
1191
|
+
}
|
|
1192
|
+
const minValue = getMinValue(program, typespecType);
|
|
1193
|
+
if (isNumeric && !target.minimum && minValue !== undefined) {
|
|
1194
|
+
newTarget.minimum = minValue;
|
|
1195
|
+
}
|
|
1196
|
+
const maxValue = getMaxValue(program, typespecType);
|
|
1197
|
+
if (isNumeric && !target.maximum && maxValue !== undefined) {
|
|
1198
|
+
newTarget.maximum = maxValue;
|
|
1199
|
+
}
|
|
1200
|
+
const minItems = getMinItems(program, typespecType);
|
|
1201
|
+
if (!target.minItems && minItems !== undefined) {
|
|
1202
|
+
newTarget.minItems = minItems;
|
|
1203
|
+
}
|
|
1204
|
+
const maxItems = getMaxItems(program, typespecType);
|
|
1205
|
+
if (!target.maxItems && maxItems !== undefined) {
|
|
1206
|
+
newTarget.maxItems = maxItems;
|
|
1207
|
+
}
|
|
1208
|
+
if (isSecret(program, typespecType)) {
|
|
1209
|
+
newTarget.format = "password";
|
|
1210
|
+
newTarget["x-ms-secret"] = true;
|
|
1211
|
+
}
|
|
1212
|
+
if (isString) {
|
|
1213
|
+
const values = getKnownValues(program, typespecType);
|
|
1214
|
+
if (values) {
|
|
1215
|
+
const enumSchema = { ...newTarget, ...getSchemaForEnum(values) };
|
|
1216
|
+
enumSchema["x-ms-enum"].modelAsString = true;
|
|
1217
|
+
enumSchema["x-ms-enum"].name = getPropertyType(typespecType).name;
|
|
1218
|
+
return enumSchema;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
attachExtensions(typespecType, newTarget);
|
|
1222
|
+
return newTarget;
|
|
1223
|
+
}
|
|
1224
|
+
function applyExternalDocs(typespecType, target) {
|
|
1225
|
+
const externalDocs = getExternalDocs(program, typespecType);
|
|
1226
|
+
if (externalDocs) {
|
|
1227
|
+
target.externalDocs = externalDocs;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
function addXMSEnum(type, schema) {
|
|
1231
|
+
var _a;
|
|
1232
|
+
if (type.node && type.node.parent && type.node.parent.kind === SyntaxKind.ModelStatement) {
|
|
1233
|
+
schema["x-ms-enum"] = {
|
|
1234
|
+
name: type.node.parent.id.sv,
|
|
1235
|
+
modelAsString: true,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
else if (type.kind === "String") {
|
|
1239
|
+
schema["x-ms-enum"] = {
|
|
1240
|
+
modelAsString: false,
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
else if (type.kind === "Enum") {
|
|
1244
|
+
schema["x-ms-enum"] = {
|
|
1245
|
+
name: type.name,
|
|
1246
|
+
modelAsString: isFixed(program, type) ? false : true,
|
|
1247
|
+
};
|
|
1248
|
+
const values = [];
|
|
1249
|
+
let foundCustom = false;
|
|
1250
|
+
for (const member of type.members.values()) {
|
|
1251
|
+
const description = getDoc(program, member);
|
|
1252
|
+
values.push({
|
|
1253
|
+
name: member.name,
|
|
1254
|
+
value: (_a = member.value) !== null && _a !== void 0 ? _a : member.name,
|
|
1255
|
+
description,
|
|
1256
|
+
});
|
|
1257
|
+
if (description || member.value !== undefined) {
|
|
1258
|
+
foundCustom = true;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (foundCustom) {
|
|
1262
|
+
schema["x-ms-enum"].values = values;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return schema;
|
|
1266
|
+
}
|
|
1267
|
+
function getIndexer(model) {
|
|
1268
|
+
const indexer = model.indexer;
|
|
1269
|
+
if (indexer) {
|
|
1270
|
+
return indexer;
|
|
1271
|
+
}
|
|
1272
|
+
else if (model.baseModel) {
|
|
1273
|
+
return getIndexer(model.baseModel);
|
|
1274
|
+
}
|
|
1275
|
+
return undefined;
|
|
1276
|
+
}
|
|
1277
|
+
function validateAdditionalProperties(model) {
|
|
1278
|
+
var _a;
|
|
1279
|
+
const propType = (_a = getIndexer(model)) === null || _a === void 0 ? void 0 : _a.value;
|
|
1280
|
+
if (!propType) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
for (const [_, prop] of model.properties) {
|
|
1284
|
+
// ensure that the record type is compatible with any listed properties
|
|
1285
|
+
const [_, diagnostics] = program.checker.isTypeAssignableTo(prop.type, propType, prop);
|
|
1286
|
+
for (const diag of diagnostics) {
|
|
1287
|
+
program.reportDiagnostic(diag);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Returns appropriate additional properties for Record types.
|
|
1293
|
+
*/
|
|
1294
|
+
function processAdditionalProperties(model, visibility) {
|
|
1295
|
+
var _a;
|
|
1296
|
+
const propType = (_a = getIndexer(model)) === null || _a === void 0 ? void 0 : _a.value;
|
|
1297
|
+
if (!propType) {
|
|
1298
|
+
return undefined;
|
|
1299
|
+
}
|
|
1300
|
+
switch (propType.kind) {
|
|
1301
|
+
case "Intrinsic":
|
|
1302
|
+
if (propType.name === "unknown") {
|
|
1303
|
+
return true;
|
|
1304
|
+
}
|
|
1305
|
+
break;
|
|
1306
|
+
case "Scalar":
|
|
1307
|
+
case "Model":
|
|
1308
|
+
return getSchemaOrRef(propType, visibility);
|
|
1309
|
+
}
|
|
1310
|
+
return undefined;
|
|
1311
|
+
}
|
|
1312
|
+
// Map an TypeSpec type to an OA schema. Returns undefined when the resulting
|
|
1313
|
+
// OA schema is just a regular object schema.
|
|
1314
|
+
function mapTypeSpecTypeToOpenAPI(typespecType, visibility) {
|
|
1315
|
+
switch (typespecType.kind) {
|
|
1316
|
+
case "Number":
|
|
1317
|
+
return { type: "number", enum: [typespecType.value] };
|
|
1318
|
+
case "String":
|
|
1319
|
+
return addXMSEnum(typespecType, { type: "string", enum: [typespecType.value] });
|
|
1320
|
+
case "Boolean":
|
|
1321
|
+
return { type: "boolean", enum: [typespecType.value] };
|
|
1322
|
+
case "Model":
|
|
1323
|
+
return mapTypeSpecIntrinsicModelToOpenAPI(typespecType, visibility);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Map TypeSpec intrinsic models to open api definitions
|
|
1328
|
+
*/
|
|
1329
|
+
function mapTypeSpecIntrinsicModelToOpenAPI(typespecType, visibility) {
|
|
1330
|
+
if (typespecType.indexer !== undefined) {
|
|
1331
|
+
if (isNeverType(typespecType.indexer.key)) {
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
const name = typespecType.indexer.key.name;
|
|
1335
|
+
if (name === "string") {
|
|
1336
|
+
return {
|
|
1337
|
+
type: "object",
|
|
1338
|
+
additionalProperties: processAdditionalProperties(typespecType, visibility),
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
else if (name === "integer") {
|
|
1342
|
+
const schema = {
|
|
1343
|
+
type: "array",
|
|
1344
|
+
items: getSchemaOrRef(typespecType.indexer.value, visibility | Visibility.Item),
|
|
1345
|
+
};
|
|
1346
|
+
if (!ifArrayItemContainsIdentifier(program, typespecType)) {
|
|
1347
|
+
schema["x-ms-identifiers"] = [];
|
|
1348
|
+
}
|
|
1349
|
+
return schema;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function getSchemaForScalar(scalar) {
|
|
1355
|
+
let result = {};
|
|
1356
|
+
if (program.checker.isStdType(scalar)) {
|
|
1357
|
+
result = getSchemaForStdScalars(scalar);
|
|
1358
|
+
}
|
|
1359
|
+
else if (scalar.baseScalar) {
|
|
1360
|
+
result = getSchemaForScalar(scalar.baseScalar);
|
|
1361
|
+
}
|
|
1362
|
+
return applyIntrinsicDecorators(scalar, result);
|
|
1363
|
+
}
|
|
1364
|
+
function getSchemaForStdScalars(scalar) {
|
|
1365
|
+
switch (scalar.name) {
|
|
1366
|
+
case "bytes":
|
|
1367
|
+
return { type: "string", format: "byte" };
|
|
1368
|
+
case "int8":
|
|
1369
|
+
return { type: "integer", format: "int8" };
|
|
1370
|
+
case "int16":
|
|
1371
|
+
return { type: "integer", format: "int16" };
|
|
1372
|
+
case "int32":
|
|
1373
|
+
return { type: "integer", format: "int32" };
|
|
1374
|
+
case "int64":
|
|
1375
|
+
return { type: "integer", format: "int64" };
|
|
1376
|
+
case "safeint":
|
|
1377
|
+
return { type: "integer", format: "int64" };
|
|
1378
|
+
case "uint8":
|
|
1379
|
+
return { type: "integer", format: "uint8" };
|
|
1380
|
+
case "uint16":
|
|
1381
|
+
return { type: "integer", format: "uint16" };
|
|
1382
|
+
case "uint32":
|
|
1383
|
+
return { type: "integer", format: "uint32" };
|
|
1384
|
+
case "uint64":
|
|
1385
|
+
return { type: "integer", format: "uint64" };
|
|
1386
|
+
case "float64":
|
|
1387
|
+
return { type: "number", format: "double" };
|
|
1388
|
+
case "float32":
|
|
1389
|
+
return { type: "number", format: "float" };
|
|
1390
|
+
case "string":
|
|
1391
|
+
return { type: "string" };
|
|
1392
|
+
case "boolean":
|
|
1393
|
+
return { type: "boolean" };
|
|
1394
|
+
case "plainDate":
|
|
1395
|
+
return { type: "string", format: "date" };
|
|
1396
|
+
case "zonedDateTime":
|
|
1397
|
+
return { type: "string", format: "date-time" };
|
|
1398
|
+
case "plainTime":
|
|
1399
|
+
return { type: "string", format: "time" };
|
|
1400
|
+
case "duration":
|
|
1401
|
+
return { type: "string", format: "duration" };
|
|
1402
|
+
case "url":
|
|
1403
|
+
return { type: "string", format: "uri" };
|
|
1404
|
+
case "integer":
|
|
1405
|
+
case "numeric":
|
|
1406
|
+
case "float":
|
|
1407
|
+
return {}; // Waiting on design for more precise type https://github.com/microsoft/typespec/issues/1260
|
|
1408
|
+
default:
|
|
1409
|
+
const _assertNever = scalar.name;
|
|
1410
|
+
return {};
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
function processAuth(serviceNamespace) {
|
|
1414
|
+
const authentication = getAuthentication(program, serviceNamespace);
|
|
1415
|
+
if (authentication) {
|
|
1416
|
+
return processServiceAuthentication(authentication);
|
|
1417
|
+
}
|
|
1418
|
+
return undefined;
|
|
1419
|
+
}
|
|
1420
|
+
function processServiceAuthentication(authentication) {
|
|
1421
|
+
const oaiSchemes = {};
|
|
1422
|
+
const security = [];
|
|
1423
|
+
for (const option of authentication.options) {
|
|
1424
|
+
const oai3SecurityOption = {};
|
|
1425
|
+
for (const scheme of option.schemes) {
|
|
1426
|
+
const result = getOpenAPI3Scheme(scheme);
|
|
1427
|
+
if (result !== undefined) {
|
|
1428
|
+
const [oaiScheme, scopes] = result;
|
|
1429
|
+
oaiSchemes[scheme.id] = oaiScheme;
|
|
1430
|
+
oai3SecurityOption[scheme.id] = scopes;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
security.push(oai3SecurityOption);
|
|
1434
|
+
}
|
|
1435
|
+
return { securitySchemes: oaiSchemes, security };
|
|
1436
|
+
}
|
|
1437
|
+
function getOpenAPI3Scheme(auth) {
|
|
1438
|
+
switch (auth.type) {
|
|
1439
|
+
case "http":
|
|
1440
|
+
return [{ type: "basic", description: auth.description }, []];
|
|
1441
|
+
case "apiKey":
|
|
1442
|
+
if (auth.in === "cookie") {
|
|
1443
|
+
return undefined;
|
|
1444
|
+
}
|
|
1445
|
+
return [
|
|
1446
|
+
{ type: "apiKey", description: auth.description, in: auth.in, name: auth.name },
|
|
1447
|
+
[],
|
|
1448
|
+
];
|
|
1449
|
+
case "oauth2":
|
|
1450
|
+
const flow = auth.flows[0];
|
|
1451
|
+
if (flow === undefined) {
|
|
1452
|
+
return undefined;
|
|
1453
|
+
}
|
|
1454
|
+
const oaiFlowName = getOpenAPI2Flow(flow.type);
|
|
1455
|
+
return [
|
|
1456
|
+
{
|
|
1457
|
+
type: "oauth2",
|
|
1458
|
+
description: auth.description,
|
|
1459
|
+
flow: oaiFlowName,
|
|
1460
|
+
authorizationUrl: flow.authorizationUrl,
|
|
1461
|
+
tokenUrl: flow.tokenUrl,
|
|
1462
|
+
scopes: Object.fromEntries(flow.scopes.map((x) => { var _a; return [x.value, (_a = x.description) !== null && _a !== void 0 ? _a : ""]; })),
|
|
1463
|
+
},
|
|
1464
|
+
flow.scopes.map((x) => x.value),
|
|
1465
|
+
];
|
|
1466
|
+
default:
|
|
1467
|
+
const _assertNever = auth;
|
|
1468
|
+
compilerAssert(false, "Unreachable");
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function getOpenAPI2Flow(flow) {
|
|
1472
|
+
switch (flow) {
|
|
1473
|
+
case "authorizationCode":
|
|
1474
|
+
return "accessCode";
|
|
1475
|
+
case "clientCredentials":
|
|
1476
|
+
return "application";
|
|
1477
|
+
case "implicit":
|
|
1478
|
+
return "implicit";
|
|
1479
|
+
case "password":
|
|
1480
|
+
return "password";
|
|
1481
|
+
default:
|
|
1482
|
+
const _assertNever = flow;
|
|
1483
|
+
compilerAssert(false, "Unreachable");
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function prettierOutput(output) {
|
|
1488
|
+
return output + "\n";
|
|
1489
|
+
}
|
|
1490
|
+
class ErrorTypeFoundError extends Error {
|
|
1491
|
+
constructor() {
|
|
1492
|
+
super("Error type found in evaluated TypeSpec output");
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
function sortObjectByKeys(obj, compareFn = undefined) {
|
|
1496
|
+
return Object.keys(obj)
|
|
1497
|
+
.sort(compareFn)
|
|
1498
|
+
.reduce((sortedObj, key) => {
|
|
1499
|
+
sortedObj[key] = obj[key];
|
|
1500
|
+
return sortedObj;
|
|
1501
|
+
}, {});
|
|
1502
|
+
}
|
|
1503
|
+
function sortOpenAPIDocument(doc) {
|
|
1504
|
+
doc.paths = sortObjectByKeys(doc.paths, comparePaths);
|
|
1505
|
+
doc.definitions = sortObjectByKeys(doc.definitions);
|
|
1506
|
+
doc.parameters = sortObjectByKeys(doc.parameters);
|
|
1507
|
+
return doc;
|
|
1508
|
+
}
|
|
1509
|
+
async function getOutputfolderForVersion(p, service, multipleServices, options, version) {
|
|
1510
|
+
var _a, _b, _c;
|
|
1511
|
+
const resourceProviderFolder = options.azureResourceProviderFolder;
|
|
1512
|
+
const namespace = getNamespaceFullName(service.type);
|
|
1513
|
+
const outputFileName = options.outputFile;
|
|
1514
|
+
let outputPath = (_b = (_a = options.outputDir) !== null && _a !== void 0 ? _a : p.compilerOptions.outputDir) !== null && _b !== void 0 ? _b : "./tsp-output";
|
|
1515
|
+
if (resourceProviderFolder) {
|
|
1516
|
+
version = (_c = version !== null && version !== void 0 ? version : service.version) !== null && _c !== void 0 ? _c : "0000-00-00";
|
|
1517
|
+
outputPath = resolvePath(outputPath, resourceProviderFolder, namespace, version.includes("preview") ? "preview" : "stable", version);
|
|
1518
|
+
return resolvePath(outputPath, outputFileName);
|
|
1519
|
+
}
|
|
1520
|
+
const subFolders = [];
|
|
1521
|
+
if (multipleServices) {
|
|
1522
|
+
subFolders.push(getNamespaceFullName(service.type));
|
|
1523
|
+
}
|
|
1524
|
+
if (version) {
|
|
1525
|
+
subFolders.push(version);
|
|
1526
|
+
}
|
|
1527
|
+
return resolvePath(outputPath, ...subFolders, outputFileName);
|
|
1528
|
+
}
|
|
1529
|
+
//# sourceMappingURL=openapi.js.map
|