@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.
- package/dist/src/openapi.d.ts.map +1 -1
- package/dist/src/openapi.js +86 -536
- package/dist/src/openapi.js.map +1 -1
- package/dist/src/schema-emitter.d.ts +49 -0
- package/dist/src/schema-emitter.d.ts.map +1 -0
- package/dist/src/schema-emitter.js +714 -0
- package/dist/src/schema-emitter.js.map +1 -0
- package/dist/src/visibility-usage.d.ts +9 -0
- package/dist/src/visibility-usage.d.ts.map +1 -0
- package/dist/src/visibility-usage.js +127 -0
- package/dist/src/visibility-usage.js.map +1 -0
- package/package.json +2 -2
package/dist/src/openapi.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { compilerAssert, emitFile, getAllTags, getAnyExtensionFromPath,
|
|
2
|
-
import { createMetadataInfo, getAuthentication, getHttpService, getServers, getStatusCodeDescription,
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
:
|
|
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
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
|
762
|
-
if (
|
|
763
|
-
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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 (
|
|
1053
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
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,
|