@typespec/openapi3 0.51.0-dev.1 → 0.51.0-dev.10

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,11 +1,13 @@
1
- import { compilerAssert, emitFile, getAllTags, getAnyExtensionFromPath, getDiscriminatedUnion, getDiscriminator, getDoc, getEncode, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMaxValueExclusive, getMinItems, getMinLength, getMinValue, getMinValueExclusive, getNamespaceFullName, getPattern, getService, getSummary, ignoreDiagnostics, interpolatePath, isArrayModelType, isDeprecated, isErrorType, isGlobalNamespace, isNeverType, isNullType, isRecordModelType, isSecret, isTemplateDeclaration, isVoidType, listServices, navigateTypesInNamespace, projectProgram, resolvePath, TwoLevelMap, } from "@typespec/compiler";
2
- import { createMetadataInfo, getAuthentication, getHttpService, getServers, getStatusCodeDescription, getVisibilitySuffix, isContentTypeHeader, isOverloadSameEndpoint, reportIfNoRoutes, resolveRequestVisibility, Visibility, } from "@typespec/http";
3
- import { checkDuplicateTypeName, getExtensions, getExternalDocs, getInfo, getOpenAPITypeName, getParameterKey, isDefaultResponse, isReadonlyProperty, resolveOperationId, shouldInline, } from "@typespec/openapi";
1
+ import { compilerAssert, emitFile, getAllTags, getAnyExtensionFromPath, getDoc, getEncode, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMaxValueExclusive, getMinItems, getMinLength, getMinValue, getMinValueExclusive, getNamespaceFullName, getPattern, getService, getSummary, ignoreDiagnostics, interpolatePath, isArrayModelType, isDeprecated, isGlobalNamespace, isNeverType, isNullType, isSecret, isVoidType, listServices, navigateTypesInNamespace, projectProgram, resolvePath, } from "@typespec/compiler";
2
+ import { createMetadataInfo, getAuthentication, getHttpService, getServers, getStatusCodeDescription, isContentTypeHeader, isOverloadSameEndpoint, reportIfNoRoutes, resolveRequestVisibility, Visibility, } from "@typespec/http";
3
+ import { getExtensions, getExternalDocs, getInfo, getOpenAPITypeName, getParameterKey, isDefaultResponse, isReadonlyProperty, resolveOperationId, shouldInline, } from "@typespec/openapi";
4
4
  import { buildVersionProjections } from "@typespec/versioning";
5
5
  import { stringify } from "yaml";
6
- import { getOneOf, getRef } from "./decorators.js";
6
+ import { getRef } from "./decorators.js";
7
7
  import { reportDiagnostic } from "./lib.js";
8
+ import { OpenAPI3SchemaEmitter } from "./schema-emitter.js";
8
9
  import { deepEquals } from "./util.js";
10
+ import { resolveVisibilityUsage } from "./visibility-usage.js";
9
11
  const defaultFileType = "yaml";
10
12
  const defaultOptions = {
11
13
  "new-line": "lf",
@@ -14,7 +16,7 @@ const defaultOptions = {
14
16
  };
15
17
  export async function $onEmit(context) {
16
18
  const options = resolveOptions(context);
17
- const emitter = createOAPIEmitter(context.program, options);
19
+ const emitter = createOAPIEmitter(context, options);
18
20
  await emitter.emitOpenAPI();
19
21
  }
20
22
  function findFileTypeFromFilename(filename) {
@@ -44,32 +46,16 @@ export function resolveOptions(context) {
44
46
  outputFile: resolvePath(context.emitterOutputDir, outputFile),
45
47
  };
46
48
  }
47
- /**
48
- * Represents a node that will hold a JSON reference. The value is computed
49
- * at the end so that we can defer decisions about the name that is
50
- * referenced.
51
- */
52
- class Ref {
53
- toJSON() {
54
- compilerAssert(this.value, "Reference value never set.");
55
- return this.value;
56
- }
57
- }
58
- function createOAPIEmitter(program, options) {
49
+ function createOAPIEmitter(context, options) {
50
+ let program = context.program;
51
+ let schemaEmitter;
59
52
  let root;
60
53
  // Get the service namespace string for use in name shortening
61
54
  let serviceNamespace;
62
55
  let currentPath;
63
56
  let currentEndpoint;
64
57
  let metadataInfo;
65
- // Keep a map of all Types+Visibility combinations that were encountered
66
- // that need schema definitions.
67
- let pendingSchemas = new TwoLevelMap();
68
- // Reuse a single ref object per Type+Visibility combination.
69
- let refs = new TwoLevelMap();
70
- // Keep track of inline types still in the process of having their schema computed
71
- // This is used to detect cycles in inline types, which is an
72
- let inProgressInlineTypes = new Set();
58
+ let visibilityUsage;
73
59
  // Map model properties that represent shared parameters to their parameter
74
60
  // definition that will go in #/components/parameters. Inlined parameters do not go in
75
61
  // this map.
@@ -89,6 +75,16 @@ function createOAPIEmitter(program, options) {
89
75
  return { emitOpenAPI };
90
76
  function initializeEmitter(service, version) {
91
77
  var _a, _b, _c;
78
+ metadataInfo = createMetadataInfo(program, {
79
+ canonicalVisibility: Visibility.Read,
80
+ canShareProperty: (p) => isReadonlyProperty(program, p),
81
+ });
82
+ visibilityUsage = resolveVisibilityUsage(program, metadataInfo, service.type, options.omitUnreachableTypes);
83
+ schemaEmitter = context.getAssetEmitter(class extends OpenAPI3SchemaEmitter {
84
+ constructor(emitter) {
85
+ super(emitter, metadataInfo, visibilityUsage, options);
86
+ }
87
+ });
92
88
  const auth = processAuth(service.type);
93
89
  root = {
94
90
  openapi: "3.0.0",
@@ -117,13 +113,6 @@ function createOAPIEmitter(program, options) {
117
113
  }
118
114
  serviceNamespace = getNamespaceFullName(service.type);
119
115
  currentPath = root.paths;
120
- pendingSchemas = new TwoLevelMap();
121
- refs = new TwoLevelMap();
122
- metadataInfo = createMetadataInfo(program, {
123
- canonicalVisibility: Visibility.Read,
124
- canShareProperty: (p) => isReadonlyProperty(program, p),
125
- });
126
- inProgressInlineTypes = new Set();
127
116
  params = new Map();
128
117
  paramModels = new Set();
129
118
  tags = new Set();
@@ -169,10 +158,12 @@ function createOAPIEmitter(program, options) {
169
158
  description: getDoc(program, prop),
170
159
  };
171
160
  if (prop.type.kind === "Enum") {
172
- variable.enum = getSchemaForEnum(prop.type).enum;
161
+ variable.enum = getSchemaValue(prop.type, Visibility.Read, "application/json")
162
+ .enum;
173
163
  }
174
164
  else if (prop.type.kind === "Union") {
175
- variable.enum = getSchemaForUnion(prop.type, Visibility.Read).enum;
165
+ variable.enum = getSchemaValue(prop.type, Visibility.Read, "application/json")
166
+ .enum;
176
167
  }
177
168
  else if (prop.type.kind === "String") {
178
169
  variable.enum = [prop.type.value];
@@ -668,7 +659,7 @@ function createOAPIEmitter(program, options) {
668
659
  const isBinary = isBinaryPayload(data.body.type, contentType);
669
660
  const schema = isBinary
670
661
  ? { type: "string", format: "binary" }
671
- : getSchemaOrRef(data.body.type, Visibility.Read);
662
+ : getSchemaForBody(data.body.type, Visibility.Read, undefined);
672
663
  if (schemaMap.has(contentType)) {
673
664
  schemaMap.get(contentType).push(schema);
674
665
  }
@@ -698,79 +689,53 @@ function createOAPIEmitter(program, options) {
698
689
  function getResponseHeader(prop) {
699
690
  return getOpenAPIParameterBase(prop, Visibility.Read);
700
691
  }
701
- function getSchemaOrRef(type, visibility) {
702
- var _a;
703
- const refUrl = getRef(program, type);
704
- if (refUrl) {
705
- return {
706
- $ref: refUrl,
707
- };
708
- }
709
- if (type.kind === "Scalar" && program.checker.isStdType(type)) {
710
- return getSchemaForScalar(type);
711
- }
712
- if (type.kind === "String" || type.kind === "Number" || type.kind === "Boolean") {
713
- // For literal types, we just want to emit them directly as well.
714
- return getSchemaForLiterals(type);
715
- }
716
- if (type.kind === "Intrinsic" && type.name === "unknown") {
717
- return getSchemaForIntrinsicType(type);
718
- }
719
- if (type.kind === "EnumMember") {
720
- // Enum members are just the OA representation of their values.
721
- if (typeof type.value === "number") {
722
- return { type: "number", enum: [type.value] };
723
- }
724
- else {
725
- return { type: "string", enum: [(_a = type.value) !== null && _a !== void 0 ? _a : type.name] };
726
- }
727
- }
728
- if (type.kind === "ModelProperty") {
729
- return resolveProperty(type, visibility);
730
- }
731
- type = metadataInfo.getEffectivePayloadType(type, visibility);
732
- const name = getOpenAPITypeName(program, type, typeNameOptions);
733
- if (shouldInline(program, type)) {
734
- const schema = getSchemaForInlineType(type, visibility, name);
735
- if (schema === undefined && isErrorType(type)) {
736
- // Exit early so that syntax errors are exposed. This error will
737
- // be caught and handled in emitOpenAPI.
738
- throw new ErrorTypeFoundError();
739
- }
740
- // helps to read output and correlate to TypeSpec
741
- if (schema && options.includeXTypeSpecName !== "never") {
742
- schema["x-typespec-name"] = name;
743
- }
744
- return schema;
692
+ function callSchemaEmitter(type, visibility, contentType) {
693
+ const result = emitTypeWithSchemaEmitter(type, visibility, contentType);
694
+ switch (result.kind) {
695
+ case "code":
696
+ return result.value;
697
+ case "declaration":
698
+ return { $ref: `#/components/schemas/${result.name}` };
699
+ case "circular":
700
+ reportDiagnostic(program, {
701
+ code: "inline-cycle",
702
+ format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
703
+ target: type,
704
+ });
705
+ return {};
706
+ case "none":
707
+ return {};
745
708
  }
746
- else {
747
- // Use shared schema when type is not transformed by visibility from the canonical read visibility.
748
- if (!metadataInfo.isTransformed(type, visibility)) {
749
- visibility = Visibility.Read;
750
- }
751
- const pending = pendingSchemas.getOrAdd(type, visibility, () => ({
752
- type,
753
- visibility,
754
- ref: refs.getOrAdd(type, visibility, () => new Ref()),
755
- }));
756
- return {
757
- $ref: pending.ref,
758
- };
709
+ }
710
+ function getSchemaValue(type, visibility, contentType) {
711
+ const result = emitTypeWithSchemaEmitter(type, visibility, contentType);
712
+ switch (result.kind) {
713
+ case "code":
714
+ case "declaration":
715
+ return result.value;
716
+ case "circular":
717
+ reportDiagnostic(program, {
718
+ code: "inline-cycle",
719
+ format: { type: getOpenAPITypeName(program, type, typeNameOptions) },
720
+ target: type,
721
+ });
722
+ return {};
723
+ case "none":
724
+ return {};
759
725
  }
760
726
  }
761
- function getSchemaForInlineType(type, visibility, name) {
762
- if (inProgressInlineTypes.has(type)) {
763
- reportDiagnostic(program, {
764
- code: "inline-cycle",
765
- format: { type: name },
766
- target: type,
767
- });
768
- return {};
727
+ function emitTypeWithSchemaEmitter(type, visibility, contentType) {
728
+ if (!metadataInfo.isTransformed(type, visibility)) {
729
+ visibility = Visibility.Read;
769
730
  }
770
- inProgressInlineTypes.add(type);
771
- const schema = getSchemaForType(type, visibility);
772
- inProgressInlineTypes.delete(type);
773
- return schema;
731
+ contentType = contentType === "application/json" ? undefined : contentType;
732
+ return schemaEmitter.emitType(type, {
733
+ referenceContext: { visibility, serviceNamespaceName: serviceNamespace, contentType },
734
+ });
735
+ }
736
+ function getSchemaForBody(type, visibility, multipart) {
737
+ const effectiveType = metadataInfo.getEffectivePayloadType(type, visibility);
738
+ return callSchemaEmitter(effectiveType, visibility, multipart !== null && multipart !== void 0 ? multipart : "application/json");
774
739
  }
775
740
  function getParamPlaceholder(property) {
776
741
  let spreadParam = false;
@@ -833,7 +798,7 @@ function createOAPIEmitter(program, options) {
833
798
  const isBinary = isBinaryPayload(body.type, contentType);
834
799
  const bodySchema = isBinary
835
800
  ? { type: "string", format: "binary" }
836
- : getSchemaOrRef(body.type, visibility);
801
+ : getSchemaForBody(body.type, visibility, contentType.startsWith("multipart/") ? contentType : undefined);
837
802
  if (schemaMap.has(contentType)) {
838
803
  schemaMap.get(contentType).push(bodySchema);
839
804
  }
@@ -870,7 +835,7 @@ function createOAPIEmitter(program, options) {
870
835
  const isBinary = isBinaryPayload(body.type, contentType);
871
836
  const bodySchema = isBinary
872
837
  ? { type: "string", format: "binary" }
873
- : getSchemaOrRef(body.type, visibility);
838
+ : getSchemaForBody(body.type, visibility, contentType.startsWith("multipart/") ? contentType : undefined);
874
839
  const contentEntry = {
875
840
  schema: bodySchema,
876
841
  };
@@ -1010,47 +975,24 @@ function createOAPIEmitter(program, options) {
1010
975
  }
1011
976
  }
1012
977
  function emitSchemas(serviceNamespace) {
1013
- const processedSchemas = new TwoLevelMap();
1014
- processSchemas();
1015
978
  if (!options.omitUnreachableTypes) {
1016
979
  processUnreferencedSchemas();
1017
980
  }
1018
- // Emit the processed schemas. Only now can we compute the names as it
1019
- // depends on whether we have produced multiple schemas for a single
1020
- // TYPESPEC type.
1021
- for (const group of processedSchemas.values()) {
1022
- for (const [visibility, processed] of group) {
1023
- let name = getOpenAPITypeName(program, processed.type, typeNameOptions);
1024
- if (group.size > 1) {
1025
- name += getVisibilitySuffix(visibility, Visibility.Read);
1026
- }
1027
- checkDuplicateTypeName(program, processed.type, name, root.components.schemas);
1028
- processed.ref.value = "#/components/schemas/" + encodeURIComponent(name);
1029
- if (processed.schema) {
1030
- root.components.schemas[name] = processed.schema;
1031
- }
1032
- }
1033
- }
1034
- function processSchemas() {
1035
- // Process pending schemas. Note that getSchemaForType may pull in new
1036
- // pending schemas so we iterate until there are no pending schemas
1037
- // remaining.
1038
- while (pendingSchemas.size > 0) {
1039
- for (const [type, group] of pendingSchemas) {
1040
- for (const [visibility, pending] of group) {
1041
- processedSchemas.getOrAdd(type, visibility, () => ({
1042
- ...pending,
1043
- schema: getSchemaForType(type, visibility),
1044
- }));
1045
- }
1046
- pendingSchemas.delete(type);
1047
- }
981
+ const files = schemaEmitter.getSourceFiles();
982
+ if (files.length > 0) {
983
+ compilerAssert(files.length === 1, `Should only have a single file for now but got ${files.length}`);
984
+ const schemas = root.components.schemas;
985
+ const declarations = files[0].globalScope.declarations;
986
+ for (const declaration of declarations) {
987
+ schemas[declaration.name] = declaration.value;
1048
988
  }
1049
989
  }
1050
990
  function processUnreferencedSchemas() {
1051
991
  const addSchema = (type) => {
1052
- if (!processedSchemas.has(type) && !paramModels.has(type) && !shouldInline(program, type)) {
1053
- getSchemaOrRef(type, Visibility.Read);
992
+ if (visibilityUsage.isUnreachable(type) &&
993
+ !paramModels.has(type) &&
994
+ !shouldInline(program, type)) {
995
+ callSchemaEmitter(type, Visibility.All);
1054
996
  }
1055
997
  };
1056
998
  const skipSubNamespaces = isGlobalNamespace(program, serviceNamespace);
@@ -1060,7 +1002,6 @@ function createOAPIEmitter(program, options) {
1060
1002
  enum: addSchema,
1061
1003
  union: addSchema,
1062
1004
  }, { skipSubNamespaces });
1063
- processSchemas();
1064
1005
  }
1065
1006
  }
1066
1007
  function emitTags() {
@@ -1069,187 +1010,7 @@ function createOAPIEmitter(program, options) {
1069
1010
  }
1070
1011
  }
1071
1012
  function getSchemaForType(type, visibility) {
1072
- const builtinType = getSchemaForLiterals(type);
1073
- if (builtinType !== undefined)
1074
- return builtinType;
1075
- switch (type.kind) {
1076
- case "Intrinsic":
1077
- return getSchemaForIntrinsicType(type);
1078
- case "Model":
1079
- return getSchemaForModel(type, visibility);
1080
- case "ModelProperty":
1081
- return getSchemaForType(type.type, visibility);
1082
- case "Scalar":
1083
- return getSchemaForScalar(type);
1084
- case "Union":
1085
- return getSchemaForUnion(type, visibility);
1086
- case "UnionVariant":
1087
- return getSchemaForUnionVariant(type, visibility);
1088
- case "Enum":
1089
- return getSchemaForEnum(type);
1090
- case "Tuple":
1091
- return { type: "array", items: {} };
1092
- case "TemplateParameter":
1093
- // Note: This should never happen if it does there is a bug in the compiler.
1094
- reportDiagnostic(program, {
1095
- code: "invalid-schema",
1096
- format: { type: `${type.node.id.sv} (template parameter)` },
1097
- target: type,
1098
- });
1099
- return undefined;
1100
- }
1101
- reportDiagnostic(program, {
1102
- code: "invalid-schema",
1103
- format: { type: type.kind },
1104
- target: type,
1105
- });
1106
- return undefined;
1107
- }
1108
- function getSchemaForIntrinsicType(type) {
1109
- switch (type.name) {
1110
- case "unknown":
1111
- return {};
1112
- }
1113
- reportDiagnostic(program, {
1114
- code: "invalid-schema",
1115
- format: { type: type.name },
1116
- target: type,
1117
- });
1118
- return {};
1119
- }
1120
- function getSchemaForEnum(e) {
1121
- var _a;
1122
- const values = [];
1123
- if (e.members.size === 0) {
1124
- reportDiagnostic(program, { code: "empty-enum", target: e });
1125
- return {};
1126
- }
1127
- const type = enumMemberType(e.members.values().next().value);
1128
- for (const option of e.members.values()) {
1129
- if (type !== enumMemberType(option)) {
1130
- reportDiagnostic(program, { code: "enum-unique-type", target: e });
1131
- continue;
1132
- }
1133
- values.push((_a = option.value) !== null && _a !== void 0 ? _a : option.name);
1134
- }
1135
- const schema = { type, description: getDoc(program, e) };
1136
- if (values.length > 0) {
1137
- schema.enum = values;
1138
- }
1139
- const title = getSummary(program, e);
1140
- if (title) {
1141
- schema.title = title;
1142
- }
1143
- return schema;
1144
- function enumMemberType(member) {
1145
- if (typeof member.value === "number") {
1146
- return "number";
1147
- }
1148
- return "string";
1149
- }
1150
- }
1151
- /**
1152
- * A TypeSpec union maps to a variety of OA3 structures according to the following rules:
1153
- *
1154
- * * A union containing `null` makes a `nullable` schema comprised of the remaining
1155
- * union variants.
1156
- * * A union containing literal types are converted to OA3 enums. All literals of the
1157
- * same type are combined into single enums.
1158
- * * A union that contains multiple items (after removing null and combining like-typed
1159
- * literals into enums) is an `anyOf` union unless `oneOf` is applied to the union
1160
- * declaration.
1161
- */
1162
- function getSchemaForUnion(union, visibility) {
1163
- if (union.variants.size === 0) {
1164
- reportDiagnostic(program, { code: "empty-union", target: union });
1165
- return {};
1166
- }
1167
- const variants = Array.from(union.variants.values());
1168
- const literalVariantEnumByType = {};
1169
- const ofType = getOneOf(program, union) ? "oneOf" : "anyOf";
1170
- const schemaMembers = [];
1171
- let nullable = false;
1172
- const discriminator = getDiscriminator(program, union);
1173
- for (const variant of variants) {
1174
- if (isNullType(variant.type)) {
1175
- nullable = true;
1176
- continue;
1177
- }
1178
- if (isLiteralType(variant.type)) {
1179
- if (!literalVariantEnumByType[variant.type.kind]) {
1180
- const enumSchema = getSchemaForLiterals(variant.type);
1181
- literalVariantEnumByType[variant.type.kind] = enumSchema;
1182
- schemaMembers.push({ schema: enumSchema, type: null });
1183
- }
1184
- else {
1185
- literalVariantEnumByType[variant.type.kind].enum.push(variant.type.value);
1186
- }
1187
- continue;
1188
- }
1189
- schemaMembers.push({ schema: getSchemaOrRef(variant.type, visibility), type: variant.type });
1190
- }
1191
- if (schemaMembers.length === 0) {
1192
- if (nullable) {
1193
- // This union is equivalent to just `null` but OA3 has no way to specify
1194
- // null as a value, so we throw an error.
1195
- reportDiagnostic(program, { code: "union-null", target: union });
1196
- return {};
1197
- }
1198
- else {
1199
- // completely empty union can maybe only happen with bugs?
1200
- compilerAssert(false, "Attempting to emit an empty union");
1201
- }
1202
- }
1203
- if (schemaMembers.length === 1) {
1204
- // we can just return the single schema member after applying nullable
1205
- const schema = schemaMembers[0].schema;
1206
- applyIntrinsicDecorators(union, schema);
1207
- const title = getSummary(program, union);
1208
- if (title) {
1209
- schema.title = title;
1210
- }
1211
- const type = schemaMembers[0].type;
1212
- if (nullable) {
1213
- if (schema.$ref) {
1214
- // but we can't make a ref "nullable", so wrap in an allOf (for models)
1215
- // or oneOf (for all other types)
1216
- if (type && type.kind === "Model") {
1217
- return { type: "object", allOf: [schema], nullable: true };
1218
- }
1219
- else {
1220
- return { oneOf: [schema], nullable: true };
1221
- }
1222
- }
1223
- else {
1224
- schema.nullable = true;
1225
- }
1226
- }
1227
- return schema;
1228
- }
1229
- const schema = {
1230
- [ofType]: schemaMembers.map((m) => m.schema),
1231
- };
1232
- if (nullable) {
1233
- schema.nullable = true;
1234
- }
1235
- if (discriminator) {
1236
- // the decorator validates that all the variants will be a model type
1237
- // with the discriminator field present.
1238
- schema.discriminator = { ...discriminator };
1239
- // Diagnostic already reported in compiler for unions
1240
- const discriminatedUnion = ignoreDiagnostics(getDiscriminatedUnion(union, discriminator));
1241
- if (discriminatedUnion.variants.size > 0) {
1242
- schema.discriminator.mapping = getDiscriminatorMapping(discriminatedUnion, visibility);
1243
- }
1244
- }
1245
- return applyIntrinsicDecorators(union, schema);
1246
- }
1247
- function getSchemaForUnionVariant(variant, visibility) {
1248
- const schema = getSchemaForType(variant.type, visibility);
1249
- return schema;
1250
- }
1251
- function isLiteralType(type) {
1252
- return type.kind === "Boolean" || type.kind === "String" || type.kind === "Number";
1013
+ return callSchemaEmitter(type, visibility);
1253
1014
  }
1254
1015
  function getDefaultValue(type, defaultType) {
1255
1016
  var _a;
@@ -1286,111 +1047,6 @@ function createOAPIEmitter(program, options) {
1286
1047
  });
1287
1048
  }
1288
1049
  }
1289
- function includeDerivedModel(model) {
1290
- var _a, _b;
1291
- return (!isTemplateDeclaration(model) &&
1292
- (((_a = model.templateMapper) === null || _a === void 0 ? void 0 : _a.args) === undefined ||
1293
- ((_b = model.templateMapper.args) === null || _b === void 0 ? void 0 : _b.length) === 0 ||
1294
- model.derivedModels.length > 0));
1295
- }
1296
- function getSchemaForModel(model, visibility) {
1297
- const array = getArrayType(model, visibility);
1298
- if (array) {
1299
- return array;
1300
- }
1301
- const modelSchema = {
1302
- type: "object",
1303
- description: getDoc(program, model),
1304
- };
1305
- const properties = {};
1306
- if (isRecordModelType(program, model)) {
1307
- modelSchema.additionalProperties = getSchemaOrRef(model.indexer.value, visibility);
1308
- }
1309
- const derivedModels = model.derivedModels.filter(includeDerivedModel);
1310
- // getSchemaOrRef on all children to push them into components.schemas
1311
- for (const child of derivedModels) {
1312
- getSchemaOrRef(child, visibility);
1313
- }
1314
- const discriminator = getDiscriminator(program, model);
1315
- if (discriminator) {
1316
- const [union] = getDiscriminatedUnion(model, discriminator);
1317
- const openApiDiscriminator = { ...discriminator };
1318
- if (union.variants.size > 0) {
1319
- openApiDiscriminator.mapping = getDiscriminatorMapping(union, visibility);
1320
- }
1321
- modelSchema.discriminator = openApiDiscriminator;
1322
- properties[discriminator.propertyName] = {
1323
- type: "string",
1324
- description: `Discriminator property for ${model.name}.`,
1325
- };
1326
- }
1327
- applyExternalDocs(model, modelSchema);
1328
- for (const [name, prop] of model.properties) {
1329
- if (!metadataInfo.isPayloadProperty(prop, visibility)) {
1330
- continue;
1331
- }
1332
- if (isNeverType(prop.type)) {
1333
- // If the property has a type of 'never', don't include it in the schema
1334
- continue;
1335
- }
1336
- if (!metadataInfo.isOptional(prop, visibility)) {
1337
- if (!modelSchema.required) {
1338
- modelSchema.required = [];
1339
- }
1340
- modelSchema.required.push(name);
1341
- }
1342
- properties[name] = resolveProperty(prop, visibility);
1343
- }
1344
- if (model.baseModel) {
1345
- const baseSchema = getSchemaOrRef(model.baseModel, visibility);
1346
- modelSchema.allOf = [baseSchema];
1347
- }
1348
- if (Object.keys(properties).length > 0) {
1349
- modelSchema.properties = properties;
1350
- }
1351
- const title = getSummary(program, model);
1352
- if (title) {
1353
- modelSchema.title = title;
1354
- }
1355
- // Attach any OpenAPI extensions
1356
- attachExtensions(program, model, modelSchema);
1357
- return modelSchema;
1358
- }
1359
- function resolveProperty(prop, visibility) {
1360
- const description = getDoc(program, prop);
1361
- const schema = applyEncoding(prop, getSchemaOrRef(prop.type, visibility));
1362
- // Apply decorators on the property to the type's schema
1363
- const additionalProps = applyIntrinsicDecorators(prop, {});
1364
- if (description) {
1365
- additionalProps.description = description;
1366
- }
1367
- if (prop.default) {
1368
- additionalProps.default = getDefaultValue(prop.type, prop.default);
1369
- }
1370
- if (isReadonlyProperty(program, prop)) {
1371
- additionalProps.readOnly = true;
1372
- }
1373
- // Attach any additional OpenAPI extensions
1374
- attachExtensions(program, prop, additionalProps);
1375
- if (schema && "$ref" in schema) {
1376
- if (Object.keys(additionalProps).length === 0) {
1377
- return schema;
1378
- }
1379
- else {
1380
- return {
1381
- allOf: [schema],
1382
- ...additionalProps,
1383
- };
1384
- }
1385
- }
1386
- else {
1387
- if (getOneOf(program, prop) && schema.anyOf) {
1388
- schema.oneOf = schema.anyOf;
1389
- delete schema.anyOf;
1390
- }
1391
- return { ...schema, ...additionalProps };
1392
- }
1393
- }
1394
1050
  function attachExtensions(program, type, emitObject) {
1395
1051
  // Attach any OpenAPI extensions
1396
1052
  const extensions = getExtensions(program, type);
@@ -1400,13 +1056,6 @@ function createOAPIEmitter(program, options) {
1400
1056
  }
1401
1057
  }
1402
1058
  }
1403
- function getDiscriminatorMapping(union, visibility) {
1404
- const mapping = {};
1405
- for (const [key, model] of union.variants.entries()) {
1406
- mapping[key] = getSchemaOrRef(model, visibility).$ref;
1407
- }
1408
- return mapping;
1409
- }
1410
1059
  function applyIntrinsicDecorators(typespecType, target) {
1411
1060
  const newTarget = { ...target };
1412
1061
  const docStr = getDoc(program, typespecType);
@@ -1465,7 +1114,7 @@ function createOAPIEmitter(program, options) {
1465
1114
  const values = getKnownValues(program, typespecType);
1466
1115
  if (values) {
1467
1116
  return {
1468
- oneOf: [newTarget, getSchemaForEnum(values)],
1117
+ oneOf: [newTarget, callSchemaEmitter(values, Visibility.Read, "application/json")],
1469
1118
  };
1470
1119
  }
1471
1120
  attachExtensions(program, typespecType, newTarget);
@@ -1475,7 +1124,7 @@ function createOAPIEmitter(program, options) {
1475
1124
  const encodeData = getEncode(program, typespecType);
1476
1125
  if (encodeData) {
1477
1126
  const newTarget = { ...target };
1478
- const newType = getSchemaForScalar(encodeData.type);
1127
+ const newType = callSchemaEmitter(encodeData.type, Visibility.Read, "application/json");
1479
1128
  newTarget.type = newType.type;
1480
1129
  // If the target already has a format it takes priority. (e.g. int32)
1481
1130
  newTarget.format = mergeFormatAndEncoding(newTarget.format, encodeData.encoding, newType.format);
@@ -1515,103 +1164,6 @@ function createOAPIEmitter(program, options) {
1515
1164
  target.externalDocs = externalDocs;
1516
1165
  }
1517
1166
  }
1518
- function getSchemaForLiterals(typespecType) {
1519
- switch (typespecType.kind) {
1520
- case "Number":
1521
- return { type: "number", enum: [typespecType.value] };
1522
- case "String":
1523
- return { type: "string", enum: [typespecType.value] };
1524
- case "Boolean":
1525
- return { type: "boolean", enum: [typespecType.value] };
1526
- default:
1527
- return undefined;
1528
- }
1529
- }
1530
- /**
1531
- * Map TypeSpec intrinsic models to open api definitions
1532
- */
1533
- function getArrayType(typespecType, visibility) {
1534
- if (isArrayModelType(program, typespecType)) {
1535
- const array = {
1536
- type: "array",
1537
- items: getSchemaOrRef(typespecType.indexer.value, visibility | Visibility.Item),
1538
- };
1539
- return applyIntrinsicDecorators(typespecType, array);
1540
- }
1541
- return undefined;
1542
- }
1543
- function getSchemaForScalar(scalar) {
1544
- let result = {};
1545
- const isStd = program.checker.isStdType(scalar);
1546
- if (isStd) {
1547
- result = getSchemaForStdScalars(scalar);
1548
- }
1549
- else if (scalar.baseScalar) {
1550
- result = getSchemaForScalar(scalar.baseScalar);
1551
- }
1552
- const withDecorators = applyEncoding(scalar, applyIntrinsicDecorators(scalar, result));
1553
- if (isStd) {
1554
- // Standard types are going to be inlined in the spec and we don't want the description of the scalar to show up
1555
- delete withDecorators.description;
1556
- }
1557
- return withDecorators;
1558
- }
1559
- function getSchemaForStdScalars(scalar) {
1560
- switch (scalar.name) {
1561
- case "bytes":
1562
- return { type: "string", format: "byte" };
1563
- case "numeric":
1564
- return { type: "number" };
1565
- case "integer":
1566
- return { type: "integer" };
1567
- case "int8":
1568
- return { type: "integer", format: "int8" };
1569
- case "int16":
1570
- return { type: "integer", format: "int16" };
1571
- case "int32":
1572
- return { type: "integer", format: "int32" };
1573
- case "int64":
1574
- return { type: "integer", format: "int64" };
1575
- case "safeint":
1576
- return { type: "integer", format: "int64" };
1577
- case "uint8":
1578
- return { type: "integer", format: "uint8" };
1579
- case "uint16":
1580
- return { type: "integer", format: "uint16" };
1581
- case "uint32":
1582
- return { type: "integer", format: "uint32" };
1583
- case "uint64":
1584
- return { type: "integer", format: "uint64" };
1585
- case "float":
1586
- return { type: "number" };
1587
- case "float64":
1588
- return { type: "number", format: "double" };
1589
- case "float32":
1590
- return { type: "number", format: "float" };
1591
- case "decimal":
1592
- return { type: "number", format: "decimal" };
1593
- case "decimal128":
1594
- return { type: "number", format: "decimal128" };
1595
- case "string":
1596
- return { type: "string" };
1597
- case "boolean":
1598
- return { type: "boolean" };
1599
- case "plainDate":
1600
- return { type: "string", format: "date" };
1601
- case "utcDateTime":
1602
- case "offsetDateTime":
1603
- return { type: "string", format: "date-time" };
1604
- case "plainTime":
1605
- return { type: "string", format: "time" };
1606
- case "duration":
1607
- return { type: "string", format: "duration" };
1608
- case "url":
1609
- return { type: "string", format: "uri" };
1610
- default:
1611
- const _assertNever = scalar.name;
1612
- return {};
1613
- }
1614
- }
1615
1167
  function processAuth(serviceNamespace) {
1616
1168
  const authentication = getAuthentication(program, serviceNamespace);
1617
1169
  if (authentication) {
@@ -1667,9 +1219,7 @@ function serializeDocument(root, fileType) {
1667
1219
  case "json":
1668
1220
  return prettierOutput(JSON.stringify(root, null, 2));
1669
1221
  case "yaml":
1670
- return stringify(root, (key, value) => {
1671
- return value instanceof Ref ? value.toJSON() : value;
1672
- }, {
1222
+ return stringify(root, {
1673
1223
  singleQuote: true,
1674
1224
  aliasDuplicateObjects: false,
1675
1225
  lineWidth: 0,