@azure-tools/typespec-autorest-canonical 0.3.0-dev.4 → 0.3.0-dev.6

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