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