@typespec/openapi3 0.41.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.
@@ -0,0 +1,1247 @@
1
+ import { compilerAssert, emitFile, getAllTags, getAnyExtensionFromPath, getDiscriminatedUnion, getDiscriminator, getDoc, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMaxValueExclusive, getMinItems, getMinLength, getMinValue, getMinValueExclusive, getNamespaceFullName, getPattern, getPropertyType, getService, getSummary, ignoreDiagnostics, isDeprecated, isErrorType, isGlobalNamespace, isNeverType, isNullType, isNumericType, isSecret, isStringType, isTemplateDeclaration, isTemplateDeclarationOrInstance, listServices, navigateTypesInNamespace, projectProgram, resolvePath, TwoLevelMap, } from "@typespec/compiler";
2
+ import * as http from "@typespec/http";
3
+ import { createMetadataInfo, getAuthentication, getHttpService, getRequestVisibility, getStatusCodeDescription, getVisibilitySuffix, isContentTypeHeader, isOverloadSameEndpoint, reportIfNoRoutes, Visibility, } from "@typespec/http";
4
+ import { checkDuplicateTypeName, getExtensions, getExternalDocs, getOpenAPITypeName, getParameterKey, isReadonlyProperty, resolveOperationId, shouldInline, } from "@typespec/openapi";
5
+ import { buildVersionProjections } from "@typespec/versioning";
6
+ import yaml from "js-yaml";
7
+ import { getOneOf, getRef } from "./decorators.js";
8
+ import { reportDiagnostic } from "./lib.js";
9
+ const defaultFileType = "yaml";
10
+ const defaultOptions = {
11
+ "new-line": "lf",
12
+ "omit-unreachable-types": false,
13
+ };
14
+ export async function $onEmit(context) {
15
+ const options = resolveOptions(context);
16
+ const emitter = createOAPIEmitter(context.program, options);
17
+ await emitter.emitOpenAPI();
18
+ }
19
+ function findFileTypeFromFilename(filename) {
20
+ if (filename === undefined) {
21
+ return defaultFileType;
22
+ }
23
+ switch (getAnyExtensionFromPath(filename)) {
24
+ case ".yaml":
25
+ case ".yml":
26
+ return "yaml";
27
+ case ".json":
28
+ return "json";
29
+ default:
30
+ return defaultFileType;
31
+ }
32
+ }
33
+ export function resolveOptions(context) {
34
+ var _a, _b;
35
+ const resolvedOptions = { ...defaultOptions, ...context.options };
36
+ const fileType = (_a = resolvedOptions["file-type"]) !== null && _a !== void 0 ? _a : findFileTypeFromFilename(resolvedOptions["output-file"]);
37
+ const outputFile = (_b = resolvedOptions["output-file"]) !== null && _b !== void 0 ? _b : `openapi.${fileType}`;
38
+ return {
39
+ fileType,
40
+ newLine: resolvedOptions["new-line"],
41
+ omitUnreachableTypes: resolvedOptions["omit-unreachable-types"],
42
+ outputFile: resolvePath(context.emitterOutputDir, outputFile),
43
+ };
44
+ }
45
+ /**
46
+ * Represents a node that will hold a JSON reference. The value is computed
47
+ * at the end so that we can defer decisions about the name that is
48
+ * referenced.
49
+ */
50
+ class Ref {
51
+ toJSON() {
52
+ compilerAssert(this.value, "Reference value never set.");
53
+ return this.value;
54
+ }
55
+ }
56
+ function createOAPIEmitter(program, options) {
57
+ let root;
58
+ // Get the service namespace string for use in name shortening
59
+ let serviceNamespace;
60
+ let currentPath;
61
+ let currentEndpoint;
62
+ let metadataInfo;
63
+ // Keep a map of all Types+Visibility combinations that were encountered
64
+ // that need schema definitions.
65
+ let pendingSchemas = new TwoLevelMap();
66
+ // Reuse a single ref object per Type+Visibility combination.
67
+ let refs = new TwoLevelMap();
68
+ // Keep track of inline types still in the process of having their schema computed
69
+ // This is used to detect cycles in inline types, which is an
70
+ let inProgressInlineTypes = new Set();
71
+ // Map model properties that represent shared parameters to their parameter
72
+ // definition that will go in #/components/parameters. Inlined parameters do not go in
73
+ // this map.
74
+ let params;
75
+ // Keep track of models that have had properties spread into parameters. We won't
76
+ // consider these unreferenced when emitting unreferenced types.
77
+ let paramModels;
78
+ // De-dupe the per-endpoint tags that will be added into the #/tags
79
+ let tags;
80
+ const typeNameOptions = {
81
+ // shorten type names by removing TypeSpec and service namespace
82
+ namespaceFilter(ns) {
83
+ const name = getNamespaceFullName(ns);
84
+ return name !== "TypeSpec" && name !== serviceNamespace;
85
+ },
86
+ };
87
+ return { emitOpenAPI };
88
+ function initializeEmitter(service, version) {
89
+ var _a, _b, _c;
90
+ const auth = processAuth(service.type);
91
+ root = {
92
+ openapi: "3.0.0",
93
+ info: {
94
+ title: (_a = service.title) !== null && _a !== void 0 ? _a : "(title)",
95
+ version: (_b = version !== null && version !== void 0 ? version : service.version) !== null && _b !== void 0 ? _b : "0000-00-00",
96
+ description: getDoc(program, service.type),
97
+ },
98
+ externalDocs: getExternalDocs(program, service.type),
99
+ tags: [],
100
+ paths: {},
101
+ security: auth === null || auth === void 0 ? void 0 : auth.security,
102
+ components: {
103
+ parameters: {},
104
+ requestBodies: {},
105
+ responses: {},
106
+ schemas: {},
107
+ examples: {},
108
+ securitySchemes: (_c = auth === null || auth === void 0 ? void 0 : auth.securitySchemes) !== null && _c !== void 0 ? _c : {},
109
+ },
110
+ };
111
+ const servers = http.getServers(program, service.type);
112
+ if (servers) {
113
+ root.servers = resolveServers(servers);
114
+ }
115
+ serviceNamespace = getNamespaceFullName(service.type);
116
+ currentPath = root.paths;
117
+ pendingSchemas = new TwoLevelMap();
118
+ refs = new TwoLevelMap();
119
+ metadataInfo = createMetadataInfo(program, {
120
+ canShareProperty: (p) => isReadonlyProperty(program, p),
121
+ });
122
+ inProgressInlineTypes = new Set();
123
+ params = new Map();
124
+ paramModels = new Set();
125
+ tags = new Set();
126
+ }
127
+ function isValidServerVariableType(program, type) {
128
+ var _a;
129
+ switch (type.kind) {
130
+ case "String":
131
+ case "Union":
132
+ case "Scalar":
133
+ return ignoreDiagnostics(program.checker.isTypeAssignableTo((_a = type.projectionBase) !== null && _a !== void 0 ? _a : type, program.checker.getStdType("string"), type));
134
+ case "Enum":
135
+ for (const member of type.members.values()) {
136
+ if (member.value && typeof member.value !== "string") {
137
+ return false;
138
+ }
139
+ }
140
+ return true;
141
+ default:
142
+ return false;
143
+ }
144
+ }
145
+ function validateValidServerVariable(program, prop) {
146
+ const isValid = isValidServerVariableType(program, prop.type);
147
+ if (!isValid) {
148
+ reportDiagnostic(program, {
149
+ code: "invalid-server-variable",
150
+ format: { propName: prop.name },
151
+ target: prop,
152
+ });
153
+ }
154
+ return isValid;
155
+ }
156
+ function resolveServers(servers) {
157
+ return servers.map((server) => {
158
+ const variables = {};
159
+ for (const [name, prop] of server.parameters) {
160
+ if (!validateValidServerVariable(program, prop)) {
161
+ continue;
162
+ }
163
+ const variable = {
164
+ default: prop.default ? getDefaultValue(prop.default) : "",
165
+ description: getDoc(program, prop),
166
+ };
167
+ if (prop.type.kind === "Enum") {
168
+ variable.enum = getSchemaForEnum(prop.type).enum;
169
+ }
170
+ else if (prop.type.kind === "Union") {
171
+ variable.enum = getSchemaForUnion(prop.type, Visibility.All).enum;
172
+ }
173
+ else if (prop.type.kind === "String") {
174
+ variable.enum = [prop.type.value];
175
+ }
176
+ attachExtensions(program, prop, variable);
177
+ variables[name] = variable;
178
+ }
179
+ return {
180
+ url: server.url,
181
+ description: server.description,
182
+ variables,
183
+ };
184
+ });
185
+ }
186
+ async function emitOpenAPI() {
187
+ const services = listServices(program);
188
+ if (services.length === 0) {
189
+ services.push({ type: program.getGlobalNamespaceType() });
190
+ }
191
+ for (const service of services) {
192
+ const commonProjections = [
193
+ {
194
+ projectionName: "target",
195
+ arguments: ["json"],
196
+ },
197
+ ];
198
+ const originalProgram = program;
199
+ const versions = buildVersionProjections(program, service.type);
200
+ for (const record of versions) {
201
+ const projectedProgram = (program = projectProgram(originalProgram, [
202
+ ...commonProjections,
203
+ ...record.projections,
204
+ ]));
205
+ const projectedServiceNs = projectedProgram.projector.projectedTypes.get(service.type);
206
+ await emitOpenAPIFromVersion(projectedServiceNs === projectedProgram.getGlobalNamespaceType()
207
+ ? { type: projectedProgram.getGlobalNamespaceType() }
208
+ : getService(program, projectedServiceNs), services.length > 1, record.version);
209
+ }
210
+ }
211
+ }
212
+ function resolveOutputFile(service, multipleService, version) {
213
+ const suffix = [];
214
+ if (multipleService) {
215
+ suffix.push(getNamespaceFullName(service.type));
216
+ }
217
+ if (version) {
218
+ suffix.push(version);
219
+ }
220
+ if (suffix.length === 0) {
221
+ return options.outputFile;
222
+ }
223
+ const extension = getAnyExtensionFromPath(options.outputFile);
224
+ const filenameWithoutExtension = options.outputFile.slice(0, -extension.length);
225
+ return `${filenameWithoutExtension}.${suffix.join(".")}${extension}`;
226
+ }
227
+ async function emitOpenAPIFromVersion(service, multipleService, version) {
228
+ initializeEmitter(service, version);
229
+ try {
230
+ const httpService = ignoreDiagnostics(getHttpService(program, service.type));
231
+ reportIfNoRoutes(program, httpService.operations);
232
+ for (const operation of httpService.operations) {
233
+ if (operation.overloading !== undefined && isOverloadSameEndpoint(operation)) {
234
+ continue;
235
+ }
236
+ else {
237
+ emitOperation(operation);
238
+ }
239
+ }
240
+ emitParameters();
241
+ emitSchemas(service.type);
242
+ emitTags();
243
+ // Clean up empty entries
244
+ if (root.components) {
245
+ for (const elem of Object.keys(root.components)) {
246
+ if (Object.keys(root.components[elem]).length === 0) {
247
+ delete root.components[elem];
248
+ }
249
+ }
250
+ }
251
+ if (!program.compilerOptions.noEmit && !program.hasError()) {
252
+ // Write out the OpenAPI document to the output path
253
+ await emitFile(program, {
254
+ path: resolveOutputFile(service, multipleService, version),
255
+ content: serializeDocument(root, options.fileType),
256
+ newLine: options.newLine,
257
+ });
258
+ }
259
+ }
260
+ catch (err) {
261
+ if (err instanceof ErrorTypeFoundError) {
262
+ // Return early, there must be a parse error if an ErrorType was
263
+ // inserted into the TypeSpec output
264
+ return;
265
+ }
266
+ else {
267
+ throw err;
268
+ }
269
+ }
270
+ }
271
+ function emitOperation(operation) {
272
+ const { path: fullPath, operation: op, verb, parameters } = operation;
273
+ // If path contains a query string, issue msg and don't emit this endpoint
274
+ if (fullPath.indexOf("?") > 0) {
275
+ reportDiagnostic(program, { code: "path-query", target: op });
276
+ return;
277
+ }
278
+ if (!root.paths[fullPath]) {
279
+ root.paths[fullPath] = {};
280
+ }
281
+ currentPath = root.paths[fullPath];
282
+ if (!currentPath[verb]) {
283
+ currentPath[verb] = {};
284
+ }
285
+ currentEndpoint = currentPath[verb];
286
+ const currentTags = getAllTags(program, op);
287
+ if (currentTags) {
288
+ currentEndpoint.tags = currentTags;
289
+ for (const tag of currentTags) {
290
+ // Add to root tags if not already there
291
+ tags.add(tag);
292
+ }
293
+ }
294
+ currentEndpoint.operationId = resolveOperationId(program, op);
295
+ applyExternalDocs(op, currentEndpoint);
296
+ // Set up basic endpoint fields
297
+ currentEndpoint.summary = getSummary(program, op);
298
+ currentEndpoint.description = getDoc(program, op);
299
+ currentEndpoint.parameters = [];
300
+ currentEndpoint.responses = {};
301
+ const visibility = getRequestVisibility(verb);
302
+ emitEndpointParameters(parameters.parameters, visibility);
303
+ emitRequestBody(parameters, visibility);
304
+ emitResponses(operation.responses);
305
+ if (isDeprecated(program, op)) {
306
+ currentEndpoint.deprecated = true;
307
+ }
308
+ attachExtensions(program, op, currentEndpoint);
309
+ }
310
+ function emitResponses(responses) {
311
+ for (const response of responses) {
312
+ emitResponseObject(response);
313
+ }
314
+ }
315
+ function isBinaryPayload(body, contentType) {
316
+ return (body.kind === "Scalar" &&
317
+ body.name === "bytes" &&
318
+ contentType !== "application/json" &&
319
+ contentType !== "text/plain");
320
+ }
321
+ function getOpenAPIStatuscode(response) {
322
+ switch (response.statusCode) {
323
+ case "*":
324
+ return "default";
325
+ default:
326
+ return response.statusCode;
327
+ }
328
+ }
329
+ function emitResponseObject(response) {
330
+ var _a, _b, _c, _d;
331
+ const statusCode = getOpenAPIStatuscode(response);
332
+ const openapiResponse = (_a = currentEndpoint.responses[statusCode]) !== null && _a !== void 0 ? _a : {
333
+ description: (_b = response.description) !== null && _b !== void 0 ? _b : getResponseDescriptionForStatusCode(statusCode),
334
+ };
335
+ for (const data of response.responses) {
336
+ if (data.headers && Object.keys(data.headers).length > 0) {
337
+ (_c = openapiResponse.headers) !== null && _c !== void 0 ? _c : (openapiResponse.headers = {});
338
+ // OpenAPI can't represent different headers per content type.
339
+ // So we merge headers here, and report any duplicates.
340
+ // It may be possible in principle to not error for identically declared
341
+ // headers.
342
+ for (const [key, value] of Object.entries(data.headers)) {
343
+ if (openapiResponse.headers[key]) {
344
+ reportDiagnostic(program, {
345
+ code: "duplicate-header",
346
+ format: { header: key },
347
+ target: response.type,
348
+ });
349
+ continue;
350
+ }
351
+ openapiResponse.headers[key] = getResponseHeader(value);
352
+ }
353
+ }
354
+ if (data.body !== undefined) {
355
+ (_d = openapiResponse.content) !== null && _d !== void 0 ? _d : (openapiResponse.content = {});
356
+ for (const contentType of data.body.contentTypes) {
357
+ const isBinary = isBinaryPayload(data.body.type, contentType);
358
+ const schema = isBinary
359
+ ? { type: "string", format: "binary" }
360
+ : getSchemaOrRef(data.body.type, Visibility.Read);
361
+ openapiResponse.content[contentType] = { schema };
362
+ }
363
+ }
364
+ }
365
+ currentEndpoint.responses[statusCode] = openapiResponse;
366
+ }
367
+ function getResponseDescriptionForStatusCode(statusCode) {
368
+ var _a;
369
+ if (statusCode === "default") {
370
+ return "An unexpected error response.";
371
+ }
372
+ return (_a = getStatusCodeDescription(statusCode)) !== null && _a !== void 0 ? _a : "unknown";
373
+ }
374
+ function getResponseHeader(prop) {
375
+ return getOpenAPIParameterBase(prop, Visibility.Read);
376
+ }
377
+ function getSchemaOrRef(type, visibility) {
378
+ var _a;
379
+ const refUrl = getRef(program, type);
380
+ if (refUrl) {
381
+ return {
382
+ $ref: refUrl,
383
+ };
384
+ }
385
+ if (type.kind === "Scalar" && program.checker.isStdType(type)) {
386
+ return getSchemaForScalar(type);
387
+ }
388
+ if (type.kind === "String" || type.kind === "Number" || type.kind === "Boolean") {
389
+ // For literal types, we just want to emit them directly as well.
390
+ return mapTypeSpecTypeToOpenAPI(type, visibility);
391
+ }
392
+ if (type.kind === "Intrinsic" && type.name === "unknown") {
393
+ return getSchemaForIntrinsicType(type);
394
+ }
395
+ if (type.kind === "EnumMember") {
396
+ // Enum members are just the OA representation of their values.
397
+ if (typeof type.value === "number") {
398
+ return { type: "number", enum: [type.value] };
399
+ }
400
+ else {
401
+ return { type: "string", enum: [(_a = type.value) !== null && _a !== void 0 ? _a : type.name] };
402
+ }
403
+ }
404
+ if (type.kind === "ModelProperty") {
405
+ return resolveProperty(type, visibility);
406
+ }
407
+ type = metadataInfo.getEffectivePayloadType(type, visibility);
408
+ const name = getOpenAPITypeName(program, type, typeNameOptions);
409
+ if (shouldInline(program, type)) {
410
+ const schema = getSchemaForInlineType(type, visibility, name);
411
+ if (schema === undefined && isErrorType(type)) {
412
+ // Exit early so that syntax errors are exposed. This error will
413
+ // be caught and handled in emitOpenAPI.
414
+ throw new ErrorTypeFoundError();
415
+ }
416
+ // helps to read output and correlate to TypeSpec
417
+ if (schema) {
418
+ schema["x-typespec-name"] = name;
419
+ }
420
+ return schema;
421
+ }
422
+ else {
423
+ // Use shared schema when type is not transformed by visibility.
424
+ if (!metadataInfo.isTransformed(type, visibility)) {
425
+ visibility = Visibility.All;
426
+ }
427
+ const pending = pendingSchemas.getOrAdd(type, visibility, () => ({
428
+ type,
429
+ visibility,
430
+ ref: refs.getOrAdd(type, visibility, () => new Ref()),
431
+ }));
432
+ return {
433
+ $ref: pending.ref,
434
+ };
435
+ }
436
+ }
437
+ function getSchemaForInlineType(type, visibility, name) {
438
+ if (inProgressInlineTypes.has(type)) {
439
+ reportDiagnostic(program, {
440
+ code: "inline-cycle",
441
+ format: { type: name },
442
+ target: type,
443
+ });
444
+ return {};
445
+ }
446
+ inProgressInlineTypes.add(type);
447
+ const schema = getSchemaForType(type, visibility);
448
+ inProgressInlineTypes.delete(type);
449
+ return schema;
450
+ }
451
+ function getParamPlaceholder(property) {
452
+ let spreadParam = false;
453
+ if (property.sourceProperty) {
454
+ // chase our sources all the way back to the first place this property
455
+ // was defined.
456
+ spreadParam = true;
457
+ property = property.sourceProperty;
458
+ while (property.sourceProperty) {
459
+ property = property.sourceProperty;
460
+ }
461
+ }
462
+ const refUrl = getRef(program, property);
463
+ if (refUrl) {
464
+ return {
465
+ $ref: refUrl,
466
+ };
467
+ }
468
+ if (params.has(property)) {
469
+ return params.get(property);
470
+ }
471
+ const placeholder = {};
472
+ // only parameters inherited by spreading from non-inlined type are shared in #/components/parameters
473
+ if (spreadParam && property.model && !shouldInline(program, property.model)) {
474
+ params.set(property, placeholder);
475
+ paramModels.add(property.model);
476
+ }
477
+ return placeholder;
478
+ }
479
+ function emitEndpointParameters(parameters, visibility) {
480
+ for (const httpOpParam of parameters) {
481
+ if (params.has(httpOpParam.param)) {
482
+ currentEndpoint.parameters.push(params.get(httpOpParam.param));
483
+ continue;
484
+ }
485
+ if (httpOpParam.type === "header" && isContentTypeHeader(program, httpOpParam.param)) {
486
+ continue;
487
+ }
488
+ emitParameter(httpOpParam, visibility);
489
+ }
490
+ }
491
+ function emitRequestBody(parameters, visibility) {
492
+ const body = parameters.body;
493
+ if (body === undefined) {
494
+ return;
495
+ }
496
+ const requestBody = {
497
+ description: body.parameter ? getDoc(program, body.parameter) : undefined,
498
+ content: {},
499
+ };
500
+ const contentTypes = body.contentTypes.length > 0 ? body.contentTypes : ["application/json"];
501
+ for (const contentType of contentTypes) {
502
+ const isBinary = isBinaryPayload(body.type, contentType);
503
+ const bodySchema = isBinary
504
+ ? { type: "string", format: "binary" }
505
+ : getSchemaOrRef(body.type, visibility);
506
+ const contentEntry = {
507
+ schema: bodySchema,
508
+ };
509
+ requestBody.content[contentType] = contentEntry;
510
+ }
511
+ currentEndpoint.requestBody = requestBody;
512
+ }
513
+ function emitParameter(parameter, visibility) {
514
+ if (isNeverType(parameter.param.type)) {
515
+ return;
516
+ }
517
+ const ph = getParamPlaceholder(parameter.param);
518
+ currentEndpoint.parameters.push(ph);
519
+ // If the parameter already has a $ref, don't bother populating it
520
+ if (!("$ref" in ph)) {
521
+ populateParameter(ph, parameter, visibility);
522
+ }
523
+ }
524
+ function getOpenAPIParameterBase(param, visibility) {
525
+ const typeSchema = getSchemaForType(param.type, visibility);
526
+ if (!typeSchema) {
527
+ return undefined;
528
+ }
529
+ const schema = applyIntrinsicDecorators(param, typeSchema);
530
+ if (param.default) {
531
+ schema.default = getDefaultValue(param.default);
532
+ }
533
+ // Description is already provided in the parameter itself.
534
+ delete schema.description;
535
+ const oaiParam = {
536
+ required: !param.optional,
537
+ description: getDoc(program, param),
538
+ schema,
539
+ };
540
+ attachExtensions(program, param, oaiParam);
541
+ return oaiParam;
542
+ }
543
+ function populateParameter(ph, parameter, visibility) {
544
+ ph.name = parameter.name;
545
+ ph.in = parameter.type;
546
+ if (parameter.type === "query") {
547
+ if (parameter.format === "csv") {
548
+ ph.style = "simple";
549
+ }
550
+ else if (parameter.format === "multi") {
551
+ ph.style = "form";
552
+ ph.explode = true;
553
+ }
554
+ }
555
+ else if (parameter.type === "header") {
556
+ if (parameter.format === "csv") {
557
+ ph.style = "simple";
558
+ }
559
+ }
560
+ Object.assign(ph, getOpenAPIParameterBase(parameter.param, visibility));
561
+ }
562
+ function emitParameters() {
563
+ for (const [property, param] of params) {
564
+ const key = getParameterKey(program, property, param, root.components.parameters, typeNameOptions);
565
+ root.components.parameters[key] = { ...param };
566
+ for (const key of Object.keys(param)) {
567
+ delete param[key];
568
+ }
569
+ param.$ref = "#/components/parameters/" + encodeURIComponent(key);
570
+ }
571
+ }
572
+ function emitSchemas(serviceNamespace) {
573
+ const processedSchemas = new TwoLevelMap();
574
+ processSchemas();
575
+ if (!options.omitUnreachableTypes) {
576
+ processUnreferencedSchemas();
577
+ }
578
+ // Emit the processed schemas. Only now can we compute the names as it
579
+ // depends on whether we have produced multiple schemas for a single
580
+ // TYPESPEC type.
581
+ for (const group of processedSchemas.values()) {
582
+ for (const [visibility, processed] of group) {
583
+ let name = getOpenAPITypeName(program, processed.type, typeNameOptions);
584
+ if (group.size > 1) {
585
+ name += getVisibilitySuffix(visibility);
586
+ }
587
+ checkDuplicateTypeName(program, processed.type, name, root.components.schemas);
588
+ processed.ref.value = "#/components/schemas/" + encodeURIComponent(name);
589
+ if (processed.schema) {
590
+ root.components.schemas[name] = processed.schema;
591
+ }
592
+ }
593
+ }
594
+ function processSchemas() {
595
+ // Process pending schemas. Note that getSchemaForType may pull in new
596
+ // pending schemas so we iterate until there are no pending schemas
597
+ // remaining.
598
+ while (pendingSchemas.size > 0) {
599
+ for (const [type, group] of pendingSchemas) {
600
+ for (const [visibility, pending] of group) {
601
+ processedSchemas.getOrAdd(type, visibility, () => ({
602
+ ...pending,
603
+ schema: getSchemaForType(type, visibility),
604
+ }));
605
+ }
606
+ pendingSchemas.delete(type);
607
+ }
608
+ }
609
+ }
610
+ function processUnreferencedSchemas() {
611
+ const addSchema = (type) => {
612
+ if (!processedSchemas.has(type) && !paramModels.has(type) && !shouldInline(program, type)) {
613
+ getSchemaOrRef(type, Visibility.All);
614
+ }
615
+ };
616
+ const skipSubNamespaces = isGlobalNamespace(program, serviceNamespace);
617
+ navigateTypesInNamespace(serviceNamespace, {
618
+ model: addSchema,
619
+ scalar: addSchema,
620
+ enum: addSchema,
621
+ union: addSchema,
622
+ }, { skipSubNamespaces });
623
+ processSchemas();
624
+ }
625
+ }
626
+ function emitTags() {
627
+ for (const tag of tags) {
628
+ root.tags.push({ name: tag });
629
+ }
630
+ }
631
+ function getSchemaForType(type, visibility) {
632
+ const builtinType = mapTypeSpecTypeToOpenAPI(type, visibility);
633
+ if (builtinType !== undefined)
634
+ return builtinType;
635
+ switch (type.kind) {
636
+ case "Intrinsic":
637
+ return getSchemaForIntrinsicType(type);
638
+ case "Model":
639
+ return getSchemaForModel(type, visibility);
640
+ case "ModelProperty":
641
+ return getSchemaForType(type.type, visibility);
642
+ case "Scalar":
643
+ return getSchemaForScalar(type);
644
+ case "Union":
645
+ return getSchemaForUnion(type, visibility);
646
+ case "UnionVariant":
647
+ return getSchemaForUnionVariant(type, visibility);
648
+ case "Enum":
649
+ return getSchemaForEnum(type);
650
+ case "Tuple":
651
+ return { type: "array", items: {} };
652
+ case "TemplateParameter":
653
+ // Note: This should never happen if it does there is a bug in the compiler.
654
+ reportDiagnostic(program, {
655
+ code: "invalid-schema",
656
+ format: { type: `${type.node.id.sv} (template parameter)` },
657
+ target: type,
658
+ });
659
+ return undefined;
660
+ }
661
+ reportDiagnostic(program, {
662
+ code: "invalid-schema",
663
+ format: { type: type.kind },
664
+ target: type,
665
+ });
666
+ return undefined;
667
+ }
668
+ function getSchemaForIntrinsicType(type) {
669
+ switch (type.name) {
670
+ case "unknown":
671
+ return {};
672
+ }
673
+ reportDiagnostic(program, {
674
+ code: "invalid-schema",
675
+ format: { type: type.name },
676
+ target: type,
677
+ });
678
+ return {};
679
+ }
680
+ function getSchemaForEnum(e) {
681
+ var _a;
682
+ const values = [];
683
+ if (e.members.size === 0) {
684
+ reportUnsupportedUnion("empty");
685
+ return undefined;
686
+ }
687
+ const type = enumMemberType(e.members.values().next().value);
688
+ for (const option of e.members.values()) {
689
+ if (type !== enumMemberType(option)) {
690
+ reportUnsupportedUnion();
691
+ continue;
692
+ }
693
+ values.push((_a = option.value) !== null && _a !== void 0 ? _a : option.name);
694
+ }
695
+ const schema = { type, description: getDoc(program, e) };
696
+ if (values.length > 0) {
697
+ schema.enum = values;
698
+ }
699
+ return schema;
700
+ function enumMemberType(member) {
701
+ if (typeof member.value === "number") {
702
+ return "number";
703
+ }
704
+ return "string";
705
+ }
706
+ function reportUnsupportedUnion(messageId = "default") {
707
+ reportDiagnostic(program, { code: "union-unsupported", messageId, target: e });
708
+ }
709
+ }
710
+ /**
711
+ * A TypeSpec union maps to a variety of OA3 structures according to the following rules:
712
+ *
713
+ * * A union containing `null` makes a `nullable` schema comprised of the remaining
714
+ * union variants.
715
+ * * A union containing literal types are converted to OA3 enums. All literals of the
716
+ * same type are combined into single enums.
717
+ * * A union that contains multiple items (after removing null and combining like-typed
718
+ * literals into enums) is an `anyOf` union unless `oneOf` is applied to the union
719
+ * declaration.
720
+ */
721
+ function getSchemaForUnion(union, visibility) {
722
+ const variants = Array.from(union.variants.values());
723
+ const literalVariantEnumByType = {};
724
+ const ofType = getOneOf(program, union) ? "oneOf" : "anyOf";
725
+ const schemaMembers = [];
726
+ let nullable = false;
727
+ const discriminator = getDiscriminator(program, union);
728
+ for (const variant of variants) {
729
+ if (isNullType(variant.type)) {
730
+ nullable = true;
731
+ continue;
732
+ }
733
+ if (isLiteralType(variant.type)) {
734
+ if (!literalVariantEnumByType[variant.type.kind]) {
735
+ const enumSchema = mapTypeSpecTypeToOpenAPI(variant.type, visibility);
736
+ literalVariantEnumByType[variant.type.kind] = enumSchema;
737
+ schemaMembers.push({ schema: enumSchema, type: null });
738
+ }
739
+ else {
740
+ literalVariantEnumByType[variant.type.kind].enum.push(variant.type.value);
741
+ }
742
+ continue;
743
+ }
744
+ schemaMembers.push({ schema: getSchemaOrRef(variant.type, visibility), type: variant.type });
745
+ }
746
+ if (schemaMembers.length === 0) {
747
+ if (nullable) {
748
+ // This union is equivalent to just `null` but OA3 has no way to specify
749
+ // null as a value, so we throw an error.
750
+ reportDiagnostic(program, { code: "union-null", target: union });
751
+ return {};
752
+ }
753
+ else {
754
+ // completely empty union can maybe only happen with bugs?
755
+ compilerAssert(false, "Attempting to emit an empty union");
756
+ }
757
+ }
758
+ if (schemaMembers.length === 1) {
759
+ // we can just return the single schema member after applying nullable
760
+ const schema = schemaMembers[0].schema;
761
+ const type = schemaMembers[0].type;
762
+ if (nullable) {
763
+ if (schema.$ref) {
764
+ // but we can't make a ref "nullable", so wrap in an allOf (for models)
765
+ // or oneOf (for all other types)
766
+ if (type && type.kind === "Model") {
767
+ return { type: "object", allOf: [schema], nullable: true };
768
+ }
769
+ else {
770
+ return { oneOf: [schema], nullable: true };
771
+ }
772
+ }
773
+ else {
774
+ schema.nullable = true;
775
+ }
776
+ }
777
+ return schema;
778
+ }
779
+ const schema = {
780
+ [ofType]: schemaMembers.map((m) => m.schema),
781
+ };
782
+ if (nullable) {
783
+ schema.nullable = true;
784
+ }
785
+ if (discriminator) {
786
+ // the decorator validates that all the variants will be a model type
787
+ // with the discriminator field present.
788
+ schema.discriminator = discriminator;
789
+ // Diagnostic already reported in compiler for unions
790
+ const discriminatedUnion = ignoreDiagnostics(getDiscriminatedUnion(union, discriminator));
791
+ if (discriminatedUnion.variants.size > 0) {
792
+ schema.discriminator.mapping = getDiscriminatorMapping(discriminatedUnion, visibility);
793
+ }
794
+ }
795
+ return schema;
796
+ }
797
+ function getSchemaForUnionVariant(variant, visibility) {
798
+ const schema = getSchemaForType(variant.type, visibility);
799
+ return schema;
800
+ }
801
+ function isLiteralType(type) {
802
+ return type.kind === "Boolean" || type.kind === "String" || type.kind === "Number";
803
+ }
804
+ function getDefaultValue(type) {
805
+ var _a;
806
+ switch (type.kind) {
807
+ case "String":
808
+ return type.value;
809
+ case "Number":
810
+ return type.value;
811
+ case "Boolean":
812
+ return type.value;
813
+ case "Tuple":
814
+ return type.values.map(getDefaultValue);
815
+ case "EnumMember":
816
+ return (_a = type.value) !== null && _a !== void 0 ? _a : type.name;
817
+ default:
818
+ reportDiagnostic(program, {
819
+ code: "invalid-default",
820
+ format: { type: type.kind },
821
+ target: type,
822
+ });
823
+ }
824
+ }
825
+ function includeDerivedModel(model) {
826
+ var _a, _b;
827
+ return (!isTemplateDeclaration(model) &&
828
+ (((_a = model.templateMapper) === null || _a === void 0 ? void 0 : _a.args) === undefined ||
829
+ ((_b = model.templateMapper.args) === null || _b === void 0 ? void 0 : _b.length) === 0 ||
830
+ model.derivedModels.length > 0));
831
+ }
832
+ function getSchemaForModel(model, visibility) {
833
+ let modelSchema = {
834
+ type: "object",
835
+ properties: {},
836
+ description: getDoc(program, model),
837
+ };
838
+ const derivedModels = model.derivedModels.filter(includeDerivedModel);
839
+ // getSchemaOrRef on all children to push them into components.schemas
840
+ for (const child of derivedModels) {
841
+ getSchemaOrRef(child, visibility);
842
+ }
843
+ const discriminator = getDiscriminator(program, model);
844
+ if (discriminator) {
845
+ const [union] = getDiscriminatedUnion(model, discriminator);
846
+ const openApiDiscriminator = { ...discriminator };
847
+ if (union.variants.size > 0) {
848
+ openApiDiscriminator.mapping = getDiscriminatorMapping(union, visibility);
849
+ }
850
+ modelSchema.discriminator = openApiDiscriminator;
851
+ modelSchema.properties[discriminator.propertyName] = {
852
+ type: "string",
853
+ description: `Discriminator property for ${model.name}.`,
854
+ };
855
+ }
856
+ applyExternalDocs(model, modelSchema);
857
+ for (const [name, prop] of model.properties) {
858
+ if (!metadataInfo.isPayloadProperty(prop, visibility)) {
859
+ continue;
860
+ }
861
+ if (isNeverType(prop.type)) {
862
+ // If the property has a type of 'never', don't include it in the schema
863
+ continue;
864
+ }
865
+ if (!metadataInfo.isOptional(prop, visibility)) {
866
+ if (!modelSchema.required) {
867
+ modelSchema.required = [];
868
+ }
869
+ modelSchema.required.push(name);
870
+ }
871
+ modelSchema.properties[name] = resolveProperty(prop, visibility);
872
+ }
873
+ // Special case: if a model type extends a single *templated* base type and
874
+ // has no properties of its own, absorb the definition of the base model
875
+ // into this schema definition. The assumption here is that any model type
876
+ // defined like this is just meant to rename the underlying instance of a
877
+ // templated type.
878
+ if (model.baseModel &&
879
+ isTemplateDeclarationOrInstance(model.baseModel) &&
880
+ Object.keys(modelSchema.properties).length === 0) {
881
+ // Take the base model schema but carry across the documentation property
882
+ // that we set before
883
+ const baseSchema = getSchemaForType(model.baseModel, visibility);
884
+ modelSchema = {
885
+ ...baseSchema,
886
+ description: modelSchema.description,
887
+ };
888
+ }
889
+ else if (model.baseModel) {
890
+ const baseSchema = getSchemaOrRef(model.baseModel, visibility);
891
+ modelSchema.allOf = [baseSchema];
892
+ modelSchema.additionalProperties = baseSchema.additionalProperties;
893
+ if (modelSchema.additionalProperties) {
894
+ validateAdditionalProperties(model);
895
+ }
896
+ }
897
+ // Attach any OpenAPI extensions
898
+ attachExtensions(program, model, modelSchema);
899
+ return modelSchema;
900
+ }
901
+ function resolveProperty(prop, visibility) {
902
+ const description = getDoc(program, prop);
903
+ const schema = getSchemaOrRef(prop.type, visibility);
904
+ // Apply decorators on the property to the type's schema
905
+ const additionalProps = applyIntrinsicDecorators(prop, {});
906
+ if (description) {
907
+ additionalProps.description = description;
908
+ }
909
+ if (prop.default) {
910
+ additionalProps.default = getDefaultValue(prop.default);
911
+ }
912
+ if (isReadonlyProperty(program, prop)) {
913
+ additionalProps.readOnly = true;
914
+ }
915
+ // Attach any additional OpenAPI extensions
916
+ attachExtensions(program, prop, additionalProps);
917
+ if (schema && "$ref" in schema) {
918
+ if (Object.keys(additionalProps).length === 0) {
919
+ return schema;
920
+ }
921
+ else {
922
+ return {
923
+ allOf: [schema],
924
+ ...additionalProps,
925
+ };
926
+ }
927
+ }
928
+ else {
929
+ return { ...schema, ...additionalProps };
930
+ }
931
+ }
932
+ function attachExtensions(program, type, emitObject) {
933
+ // Attach any OpenAPI extensions
934
+ const extensions = getExtensions(program, type);
935
+ if (extensions) {
936
+ for (const key of extensions.keys()) {
937
+ emitObject[key] = extensions.get(key);
938
+ }
939
+ }
940
+ }
941
+ function getDiscriminatorMapping(union, visibility) {
942
+ const mapping = {};
943
+ for (const [key, model] of union.variants.entries()) {
944
+ mapping[key] = getSchemaOrRef(model, visibility).$ref;
945
+ }
946
+ return mapping;
947
+ }
948
+ function applyIntrinsicDecorators(typespecType, target) {
949
+ const newTarget = { ...target };
950
+ const docStr = getDoc(program, typespecType);
951
+ const isString = isStringType(program, getPropertyType(typespecType));
952
+ const isNumeric = isNumericType(program, getPropertyType(typespecType));
953
+ if (!target.description && docStr) {
954
+ newTarget.description = docStr;
955
+ }
956
+ const formatStr = getFormat(program, typespecType);
957
+ if (isString && !target.format && formatStr) {
958
+ newTarget.format = formatStr;
959
+ }
960
+ const pattern = getPattern(program, typespecType);
961
+ if (isString && !target.pattern && pattern) {
962
+ newTarget.pattern = pattern;
963
+ }
964
+ const minLength = getMinLength(program, typespecType);
965
+ if (isString && !target.minLength && minLength !== undefined) {
966
+ newTarget.minLength = minLength;
967
+ }
968
+ const maxLength = getMaxLength(program, typespecType);
969
+ if (isString && !target.maxLength && maxLength !== undefined) {
970
+ newTarget.maxLength = maxLength;
971
+ }
972
+ const minValue = getMinValue(program, typespecType);
973
+ if (isNumeric && !target.minimum && minValue !== undefined) {
974
+ newTarget.minimum = minValue;
975
+ }
976
+ const minValueExclusive = getMinValueExclusive(program, typespecType);
977
+ if (isNumeric && !target.exclusiveMinimum && minValueExclusive !== undefined) {
978
+ newTarget.exclusiveMinimum = minValueExclusive;
979
+ }
980
+ const maxValue = getMaxValue(program, typespecType);
981
+ if (isNumeric && !target.maximum && maxValue !== undefined) {
982
+ newTarget.maximum = maxValue;
983
+ }
984
+ const maxValueExclusive = getMaxValueExclusive(program, typespecType);
985
+ if (isNumeric && !target.exclusiveMaximum && maxValueExclusive !== undefined) {
986
+ newTarget.exclusiveMaximum = maxValueExclusive;
987
+ }
988
+ const minItems = getMinItems(program, typespecType);
989
+ if (!target.minItems && minItems !== undefined) {
990
+ newTarget.minItems = minItems;
991
+ }
992
+ const maxItems = getMaxItems(program, typespecType);
993
+ if (!target.maxItems && maxItems !== undefined) {
994
+ newTarget.maxItems = maxItems;
995
+ }
996
+ if (isSecret(program, typespecType)) {
997
+ newTarget.format = "password";
998
+ }
999
+ if (isString) {
1000
+ const values = getKnownValues(program, typespecType);
1001
+ if (values) {
1002
+ return {
1003
+ oneOf: [newTarget, getSchemaForEnum(values)],
1004
+ };
1005
+ }
1006
+ }
1007
+ attachExtensions(program, typespecType, newTarget);
1008
+ return newTarget;
1009
+ }
1010
+ function applyExternalDocs(typespecType, target) {
1011
+ const externalDocs = getExternalDocs(program, typespecType);
1012
+ if (externalDocs) {
1013
+ target.externalDocs = externalDocs;
1014
+ }
1015
+ }
1016
+ // Map an TypeSpec type to an OA schema. Returns undefined when the resulting
1017
+ // OA schema is just a regular object schema.
1018
+ function mapTypeSpecTypeToOpenAPI(typespecType, visibility) {
1019
+ switch (typespecType.kind) {
1020
+ case "Number":
1021
+ return { type: "number", enum: [typespecType.value] };
1022
+ case "String":
1023
+ return { type: "string", enum: [typespecType.value] };
1024
+ case "Boolean":
1025
+ return { type: "boolean", enum: [typespecType.value] };
1026
+ case "Model":
1027
+ return mapTypeSpecIntrinsicModelToOpenAPI(typespecType, visibility);
1028
+ }
1029
+ }
1030
+ function getIndexer(model) {
1031
+ const indexer = model.indexer;
1032
+ if (indexer) {
1033
+ return indexer;
1034
+ }
1035
+ else if (model.baseModel) {
1036
+ return getIndexer(model.baseModel);
1037
+ }
1038
+ return undefined;
1039
+ }
1040
+ function validateAdditionalProperties(model) {
1041
+ var _a;
1042
+ const propType = (_a = getIndexer(model)) === null || _a === void 0 ? void 0 : _a.value;
1043
+ if (!propType) {
1044
+ return;
1045
+ }
1046
+ for (const [_, prop] of model.properties) {
1047
+ // ensure that the record type is compatible with any listed properties
1048
+ const [_, diagnostics] = program.checker.isTypeAssignableTo(prop.type, propType, prop);
1049
+ for (const diag of diagnostics) {
1050
+ program.reportDiagnostic(diag);
1051
+ }
1052
+ }
1053
+ }
1054
+ /**
1055
+ * Returns appropriate additional properties for Record types.
1056
+ */
1057
+ function processAdditionalProperties(model, visibility) {
1058
+ var _a;
1059
+ const propType = (_a = getIndexer(model)) === null || _a === void 0 ? void 0 : _a.value;
1060
+ if (!propType) {
1061
+ return undefined;
1062
+ }
1063
+ switch (propType.kind) {
1064
+ case "Intrinsic":
1065
+ if (propType.name === "unknown") {
1066
+ return {};
1067
+ }
1068
+ break;
1069
+ case "Scalar":
1070
+ case "Model":
1071
+ return getSchemaOrRef(propType, visibility);
1072
+ }
1073
+ return undefined;
1074
+ }
1075
+ /**
1076
+ * Map TypeSpec intrinsic models to open api definitions
1077
+ */
1078
+ function mapTypeSpecIntrinsicModelToOpenAPI(typespecType, visibility) {
1079
+ if (typespecType.indexer) {
1080
+ if (isNeverType(typespecType.indexer.key)) {
1081
+ }
1082
+ else {
1083
+ const name = typespecType.indexer.key.name;
1084
+ if (name === "string") {
1085
+ return {
1086
+ type: "object",
1087
+ additionalProperties: processAdditionalProperties(typespecType, visibility),
1088
+ };
1089
+ }
1090
+ else if (name === "integer") {
1091
+ return {
1092
+ type: "array",
1093
+ items: getSchemaOrRef(typespecType.indexer.value, visibility | Visibility.Item),
1094
+ };
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ function getSchemaForScalar(scalar) {
1100
+ let result = {};
1101
+ if (program.checker.isStdType(scalar)) {
1102
+ result = getSchemaForStdScalars(scalar);
1103
+ }
1104
+ else if (scalar.baseScalar) {
1105
+ result = getSchemaForScalar(scalar.baseScalar);
1106
+ }
1107
+ return applyIntrinsicDecorators(scalar, result);
1108
+ }
1109
+ function getSchemaForStdScalars(scalar) {
1110
+ switch (scalar.name) {
1111
+ case "bytes":
1112
+ return { type: "string", format: "byte" };
1113
+ case "int8":
1114
+ return { type: "integer", format: "int8" };
1115
+ case "int16":
1116
+ return { type: "integer", format: "int16" };
1117
+ case "int32":
1118
+ return { type: "integer", format: "int32" };
1119
+ case "int64":
1120
+ return { type: "integer", format: "int64" };
1121
+ case "safeint":
1122
+ return { type: "integer", format: "int64" };
1123
+ case "uint8":
1124
+ return { type: "integer", format: "uint8" };
1125
+ case "uint16":
1126
+ return { type: "integer", format: "uint16" };
1127
+ case "uint32":
1128
+ return { type: "integer", format: "uint32" };
1129
+ case "uint64":
1130
+ return { type: "integer", format: "uint64" };
1131
+ case "float64":
1132
+ return { type: "number", format: "double" };
1133
+ case "float32":
1134
+ return { type: "number", format: "float" };
1135
+ case "string":
1136
+ return { type: "string" };
1137
+ case "boolean":
1138
+ return { type: "boolean" };
1139
+ case "plainDate":
1140
+ return { type: "string", format: "date" };
1141
+ case "zonedDateTime":
1142
+ return { type: "string", format: "date-time" };
1143
+ case "plainTime":
1144
+ return { type: "string", format: "time" };
1145
+ case "duration":
1146
+ return { type: "string", format: "duration" };
1147
+ case "url":
1148
+ return { type: "string", format: "uri" };
1149
+ case "integer":
1150
+ case "numeric":
1151
+ case "float":
1152
+ return {}; // Waiting on design for more precise type https://github.com/microsoft/typespec/issues/1260
1153
+ default:
1154
+ const _assertNever = scalar.name;
1155
+ return {};
1156
+ }
1157
+ }
1158
+ function processAuth(serviceNamespace) {
1159
+ const authentication = getAuthentication(program, serviceNamespace);
1160
+ if (authentication) {
1161
+ return processServiceAuthentication(authentication);
1162
+ }
1163
+ return undefined;
1164
+ }
1165
+ function processServiceAuthentication(authentication) {
1166
+ const oaiSchemes = {};
1167
+ const security = [];
1168
+ for (const option of authentication.options) {
1169
+ const oai3SecurityOption = {};
1170
+ for (const scheme of option.schemes) {
1171
+ const [oaiScheme, scopes] = getOpenAPI3Scheme(scheme);
1172
+ oaiSchemes[scheme.id] = oaiScheme;
1173
+ oai3SecurityOption[scheme.id] = scopes;
1174
+ }
1175
+ security.push(oai3SecurityOption);
1176
+ }
1177
+ return { securitySchemes: oaiSchemes, security };
1178
+ }
1179
+ function getOpenAPI3Scheme(auth) {
1180
+ switch (auth.type) {
1181
+ case "http":
1182
+ return [{ type: "http", scheme: auth.scheme, description: auth.description }, []];
1183
+ case "apiKey":
1184
+ return [
1185
+ { type: "apiKey", in: auth.in, name: auth.name, description: auth.description },
1186
+ [],
1187
+ ];
1188
+ case "oauth2":
1189
+ const flows = {};
1190
+ const scopes = [];
1191
+ for (const flow of auth.flows) {
1192
+ scopes.push(...flow.scopes.map((x) => x.value));
1193
+ flows[flow.type] = {
1194
+ authorizationUrl: flow.authorizationUrl,
1195
+ tokenUrl: flow.tokenUrl,
1196
+ refreshUrl: flow.refreshUrl,
1197
+ scopes: Object.fromEntries(flow.scopes.map((x) => { var _a; return [x.value, (_a = x.description) !== null && _a !== void 0 ? _a : ""]; })),
1198
+ };
1199
+ }
1200
+ return [{ type: "oauth2", flows, description: auth.description }, scopes];
1201
+ default:
1202
+ const _assertNever = auth;
1203
+ compilerAssert(false, "Unreachable");
1204
+ }
1205
+ }
1206
+ }
1207
+ function serializeDocument(root, fileType) {
1208
+ sortOpenAPIDocument(root);
1209
+ switch (fileType) {
1210
+ case "json":
1211
+ return prettierOutput(JSON.stringify(root, null, 2));
1212
+ case "yaml":
1213
+ return yaml.dump(root, {
1214
+ noRefs: true,
1215
+ replacer: function (key, value) {
1216
+ return value instanceof Ref ? value.toJSON() : value;
1217
+ },
1218
+ });
1219
+ }
1220
+ }
1221
+ function prettierOutput(output) {
1222
+ return output + "\n";
1223
+ }
1224
+ class ErrorTypeFoundError extends Error {
1225
+ constructor() {
1226
+ super("Error type found in evaluated TypeSpec output");
1227
+ }
1228
+ }
1229
+ function sortObjectByKeys(obj) {
1230
+ return Object.keys(obj)
1231
+ .sort()
1232
+ .reduce((sortedObj, key) => {
1233
+ sortedObj[key] = obj[key];
1234
+ return sortedObj;
1235
+ }, {});
1236
+ }
1237
+ function sortOpenAPIDocument(doc) {
1238
+ var _a, _b;
1239
+ doc.paths = sortObjectByKeys(doc.paths);
1240
+ if ((_a = doc.components) === null || _a === void 0 ? void 0 : _a.schemas) {
1241
+ doc.components.schemas = sortObjectByKeys(doc.components.schemas);
1242
+ }
1243
+ if ((_b = doc.components) === null || _b === void 0 ? void 0 : _b.parameters) {
1244
+ doc.components.parameters = sortObjectByKeys(doc.components.parameters);
1245
+ }
1246
+ }
1247
+ //# sourceMappingURL=openapi.js.map