@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.
@@ -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