@orpc/openapi 2.0.0-beta.4 → 2.0.0-beta.41
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/README.md +97 -94
- package/dist/adapters/aws-lambda/index.d.mts +26 -0
- package/dist/adapters/aws-lambda/index.d.ts +26 -0
- package/dist/adapters/aws-lambda/index.mjs +21 -0
- package/dist/adapters/fastify/index.d.mts +23 -0
- package/dist/adapters/fastify/index.d.ts +23 -0
- package/dist/adapters/fastify/index.mjs +21 -0
- package/dist/adapters/fetch/index.d.mts +15 -5
- package/dist/adapters/fetch/index.d.ts +15 -5
- package/dist/adapters/fetch/index.mjs +6 -6
- package/dist/adapters/node/index.d.mts +9 -4
- package/dist/adapters/node/index.d.ts +9 -4
- package/dist/adapters/node/index.mjs +4 -4
- package/dist/adapters/standard/index.d.mts +10 -46
- package/dist/adapters/standard/index.d.ts +10 -46
- package/dist/adapters/standard/index.mjs +6 -6
- package/dist/extensions/route.d.mts +5 -7
- package/dist/extensions/route.d.ts +5 -7
- package/dist/helpers/index.d.mts +9 -1
- package/dist/helpers/index.d.ts +9 -1
- package/dist/helpers/index.mjs +1 -1
- package/dist/index.d.mts +77 -45
- package/dist/index.d.ts +77 -45
- package/dist/index.mjs +614 -765
- package/dist/plugins/index.d.mts +35 -6
- package/dist/plugins/index.d.ts +35 -6
- package/dist/plugins/index.mjs +15 -10
- package/dist/shared/{openapi.B2SK0ZAr.mjs → openapi.8cM6P94D.mjs} +32 -36
- package/dist/shared/openapi.B2G-HeFn.mjs +123 -0
- package/dist/shared/{openapi.7vgmPxca.d.ts → openapi.BCNfESWr.d.ts} +12 -9
- package/dist/shared/{openapi.CX6Ri5dP.d.mts → openapi.BgpM--nz.d.mts} +12 -9
- package/dist/shared/{openapi.CTlpLuKN.mjs → openapi.Bi1qxGF9.mjs} +102 -54
- package/dist/shared/{openapi.DmAa7YPO.mjs → openapi.Bu_PfTtX.mjs} +140 -61
- package/dist/shared/{openapi.C7m7NAmH.d.mts → openapi.C2LayTNZ.d.mts} +16 -5
- package/dist/shared/{openapi.C7m7NAmH.d.ts → openapi.C2LayTNZ.d.ts} +16 -5
- package/dist/shared/openapi.C6FFC29v.d.mts +31 -0
- package/dist/shared/openapi.C6FFC29v.d.ts +31 -0
- package/dist/shared/{openapi.BQzzr4-4.d.ts → openapi.Cem81Zhm.d.mts} +41 -12
- package/dist/shared/{openapi.BcEtAxQj.d.mts → openapi.Cem81Zhm.d.ts} +41 -12
- package/dist/shared/openapi.Cg9h6jpm.d.mts +45 -0
- package/dist/shared/openapi.D0yWs-uW.d.ts +45 -0
- package/package.json +57 -17
- package/dist/shared/openapi.Bt87OzTt.mjs +0 -131
- package/dist/shared/openapi.CYgMBSUF.d.mts +0 -18
- package/dist/shared/openapi.CYgMBSUF.d.ts +0 -18
package/dist/index.mjs
CHANGED
|
@@ -1,393 +1,222 @@
|
|
|
1
|
-
export { B as BracketNotationSerializer } from './shared/openapi.
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
export { d as OpenAPIJsonSerializer } from './shared/openapi.
|
|
1
|
+
export { B as BracketNotationSerializer } from './shared/openapi.B2G-HeFn.mjs';
|
|
2
|
+
import { createContractClientFactory, getAsyncIteratorObjectSchemaDetails, ProcedureContract, resolveMetaPlugins } from '@orpc/contract';
|
|
3
|
+
import { D as DEFAULT_OPENAPI_METHOD, a as DEFAULT_OPENAPI_INPUT_STRUCTURE, c as DEFAULT_OPENAPI_SUCCESS_DESCRIPTION, b as DEFAULT_OPENAPI_OUTPUT_STRUCTURE, i as isBodylessMethod, O as OpenAPISerializer, g as getDynamicPathParams } from './shared/openapi.Bu_PfTtX.mjs';
|
|
4
|
+
export { d as OpenAPIJsonSerializer } from './shared/openapi.Bu_PfTtX.mjs';
|
|
5
5
|
import { g as getOpenAPIMeta, o as openapi } from './shared/openapi.B9PQzqBn.mjs';
|
|
6
|
+
import { downgradeSpecV32ToV31, downgradeSpecV31ToV30 } from '@openapi-spec/downgrader';
|
|
6
7
|
import { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
|
|
7
8
|
export { COMMON_ERROR_STATUS_MAP } from '@orpc/client';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import '@
|
|
9
|
+
import { encodeJsonPointerSegment, ensureJsonSchemaObject, mapJsonSchemaRefs, decodeJsonPointerSegment, isUnconstrainedSchema, combineJsonSchemasWithComposition, extractJsonObjectSchemaEntries, combineJsonObjectSchemaEntries, flattenJsonUnionSchema, isJsonPrimitiveSchema, matchArrayableJsonSchema, isJsonFileSchema, DelegatingJsonSchemaConverter, StandardJsonSchemaConverter, hoistRecursiveRefToDef } from '@orpc/json-schema';
|
|
10
|
+
import { DEFAULT_SUCCESS_STATUS, DEFAULT_ERROR_STATUS, walkProcedureContractsAsync } from '@orpc/server';
|
|
11
|
+
import { setOwn, getOwn, isDeepEqual, value, findDeepMatches, isPlainObject, stringifyJSON, toArray, clone, pathToHttpPath, mergeHttpPath, isTypescriptObject } from '@orpc/shared';
|
|
12
|
+
import '@standard-server/core';
|
|
12
13
|
|
|
13
|
-
function
|
|
14
|
-
return
|
|
14
|
+
function createContractJsonifiedClientFactory(link, options = {}) {
|
|
15
|
+
return createContractClientFactory(link, options);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
const errors = [];
|
|
36
|
-
await walkProcedureContractsAsync(router, async (contract, path) => {
|
|
37
|
-
if (value(options.filter, contract, path) === false) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const def = contract["~orpc"];
|
|
42
|
-
const meta = getOpenAPIMeta(contract);
|
|
43
|
-
const method = (meta?.method ?? DEFAULT_OPENAPI_METHOD).toLowerCase();
|
|
44
|
-
const postPath = meta?.path ?? pathToHttpPath(path);
|
|
45
|
-
const httpPath = meta?.prefix ? mergeHttpPath(meta.prefix, postPath) : postPath;
|
|
46
|
-
const dynamicPathParams = getDynamicPathParams(httpPath);
|
|
47
|
-
const openApiPath = toOpenAPIPath(httpPath, dynamicPathParams);
|
|
48
|
-
let operationRef;
|
|
49
|
-
if (meta?.spec !== void 0 && typeof meta.spec !== "function") {
|
|
50
|
-
operationRef = meta.spec;
|
|
51
|
-
} else {
|
|
52
|
-
operationRef = {
|
|
53
|
-
operationId: meta?.operationId ?? path.join("."),
|
|
54
|
-
summary: meta?.summary,
|
|
55
|
-
description: meta?.description,
|
|
56
|
-
deprecated: meta?.deprecated,
|
|
57
|
-
tags: meta?.tags?.map((tag) => tag)
|
|
58
|
-
};
|
|
59
|
-
await this.request(doc, operationRef, def, meta, dynamicPathParams, options, path);
|
|
60
|
-
await this.successResponse(doc, operationRef, def, meta, options, path);
|
|
61
|
-
await this.errorResponse(doc, operationRef, def, meta, options);
|
|
62
|
-
}
|
|
63
|
-
if (typeof meta?.spec === "function") {
|
|
64
|
-
operationRef = meta.spec(operationRef);
|
|
65
|
-
}
|
|
66
|
-
doc.paths ??= {};
|
|
67
|
-
doc.paths[openApiPath] ??= {};
|
|
68
|
-
doc.paths[openApiPath][method] = operationRef;
|
|
69
|
-
} catch (e) {
|
|
70
|
-
if (!(e instanceof OpenAPIGeneratorError)) {
|
|
71
|
-
throw e;
|
|
72
|
-
}
|
|
73
|
-
errors.push(
|
|
74
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${path.join(".")}
|
|
75
|
-
${e.message}`
|
|
76
|
-
);
|
|
18
|
+
const DEFS_REF_PREFIX = "#/$defs/";
|
|
19
|
+
const COMPONENTS_REF_PREFIX = "#/components/schemas/";
|
|
20
|
+
class OpenAPIComponentRegistry {
|
|
21
|
+
constructor(doc, customComponentName) {
|
|
22
|
+
this.doc = doc;
|
|
23
|
+
this.customComponentName = customComponentName;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Registers `schema` as a component under `preferredName` (or an equivalent/postfixed name)
|
|
27
|
+
* and returns a `$ref` to it.
|
|
28
|
+
*/
|
|
29
|
+
register(preferredName, schema) {
|
|
30
|
+
const { $defs, ...body } = schema;
|
|
31
|
+
let defName = preferredName;
|
|
32
|
+
if ($defs) {
|
|
33
|
+
for (let i = 2; Object.hasOwn($defs, defName); i++) {
|
|
34
|
+
defName = `${preferredName}${i}`;
|
|
77
35
|
}
|
|
78
|
-
});
|
|
79
|
-
if (errors.length) {
|
|
80
|
-
throw new OpenAPIGeneratorError(
|
|
81
|
-
`Some error occurred during OpenAPI generation:
|
|
82
|
-
|
|
83
|
-
${errors.join("\n\n")}`
|
|
84
|
-
);
|
|
85
36
|
}
|
|
86
|
-
return this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return [strip$schemaField(jsonSchema), optional];
|
|
37
|
+
return this.hoistDefs({
|
|
38
|
+
$defs: { ...$defs, [defName]: body },
|
|
39
|
+
$ref: `${DEFS_REF_PREFIX}${encodeJsonPointerSegment(defName)}`
|
|
40
|
+
});
|
|
91
41
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Moves a schema's root-level `$defs` into `doc.components.schemas` and rewrites
|
|
44
|
+
* its refs accordingly.
|
|
45
|
+
*/
|
|
46
|
+
hoistDefs(schema, direction) {
|
|
47
|
+
if (typeof schema !== "object" || !schema.$defs) {
|
|
48
|
+
return schema;
|
|
49
|
+
}
|
|
50
|
+
const { $defs, ...rest } = schema;
|
|
51
|
+
const defs = /* @__PURE__ */ new Map();
|
|
52
|
+
for (const defName of Object.keys($defs)) {
|
|
53
|
+
const defSchema = $defs[defName];
|
|
54
|
+
if (defSchema !== void 0) {
|
|
55
|
+
defs.set(defName, ensureJsonSchemaObject(defSchema));
|
|
103
56
|
}
|
|
104
57
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
58
|
+
if (defs.size === 0) {
|
|
59
|
+
return schema;
|
|
60
|
+
}
|
|
61
|
+
this.doc.components ??= {};
|
|
62
|
+
this.doc.components.schemas ??= {};
|
|
63
|
+
const componentsSchemas = this.doc.components.schemas;
|
|
64
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
65
|
+
const pendingSchemas = [];
|
|
66
|
+
for (const [defName, cleanSchema] of defs) {
|
|
67
|
+
const [componentName, reuseExisting] = resolveComponentName(
|
|
68
|
+
componentsSchemas,
|
|
69
|
+
new Set(renameMap.values()),
|
|
70
|
+
defName,
|
|
71
|
+
this.customComponentName?.(defName, cleanSchema) ?? defName,
|
|
72
|
+
defs,
|
|
73
|
+
direction
|
|
74
|
+
);
|
|
75
|
+
renameMap.set(defName, componentName);
|
|
76
|
+
if (!reuseExisting) {
|
|
77
|
+
pendingSchemas.push({ cleanSchema, componentName });
|
|
122
78
|
}
|
|
123
79
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
if (isUnconstrainedSchema(schema) && !dynamicParams?.length) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const objectSchemaEntries = extractJsonObjectSchemaEntries(schema);
|
|
130
|
-
if (!objectSchemaEntries) {
|
|
131
|
-
if (inputStructure === "detailed") {
|
|
132
|
-
throw new OpenAPIGeneratorError(
|
|
133
|
-
`Procedure at path "${path.join(".")}" has inputStructure "detailed" but its input schema is not an object.
|
|
134
|
-
Expected shape: { params?: Record<string, unknown>, query?: Record<string, unknown>, headers?: Record<string, unknown>, body?: unknown }`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
if (method === "GET") {
|
|
138
|
-
throw new OpenAPIGeneratorError(
|
|
139
|
-
`Procedure at path "${path.join(".")}" uses method "GET" but its input schema is not an object.
|
|
140
|
-
GET procedures map all input fields to query parameters, so the schema must be an object.
|
|
141
|
-
Expected: Record<string, unknown>`
|
|
142
|
-
);
|
|
143
|
-
}
|
|
80
|
+
for (const { cleanSchema, componentName } of pendingSchemas) {
|
|
81
|
+
setOwn(componentsSchemas, componentName, rewriteComponentSchemaRefs(cleanSchema, renameMap));
|
|
144
82
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Route params: ${dynamicParams.map((p) => `{${p}}`).join(", ")}
|
|
162
|
-
Schema keys: ${paramsObjectSchemaEntries.map(([n]) => n).join(", ") || "(none)"}`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
if (entry[2]) {
|
|
166
|
-
throw new OpenAPIGeneratorError(
|
|
167
|
-
`Procedure at path "${path.join(".")}" has dynamic param "${name}" marked as optional in its input schema, but path params must always be required in OpenAPI.`
|
|
168
|
-
);
|
|
83
|
+
return rewriteComponentSchemaRefs(rest, renameMap);
|
|
84
|
+
}
|
|
85
|
+
toOpenAPISchema(schema, direction) {
|
|
86
|
+
return ensureJsonSchemaObject(this.hoistDefs(schema, direction));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function resolveComponentName(componentsSchemas, claimedNames, defName, preferredName, defs, direction) {
|
|
90
|
+
let mintName;
|
|
91
|
+
for (let i = 1; ; i++) {
|
|
92
|
+
const [componentName, mintable, tail] = componentNameCandidate(preferredName, direction, i);
|
|
93
|
+
const existingSchema = getOwn(componentsSchemas, componentName);
|
|
94
|
+
if (existingSchema === void 0) {
|
|
95
|
+
if (mintable && !claimedNames.has(componentName)) {
|
|
96
|
+
mintName ??= componentName;
|
|
97
|
+
if (tail) {
|
|
98
|
+
return [mintName, false];
|
|
169
99
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
in: "path",
|
|
173
|
-
required: true,
|
|
174
|
-
name,
|
|
175
|
-
schema: toOpenAPISchema(entry[1], doc, options)
|
|
176
|
-
});
|
|
177
|
-
});
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
178
102
|
}
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
schema: toOpenAPISchema(schema2, doc, options),
|
|
186
|
-
allowEmptyValue: true,
|
|
187
|
-
allowReserved: true
|
|
188
|
-
};
|
|
189
|
-
if (!optional2) {
|
|
190
|
-
parameter.required = true;
|
|
191
|
-
}
|
|
192
|
-
if (style === "comma-delimited-array" || style === "comma-delimited-object") {
|
|
193
|
-
parameter.explode = false;
|
|
194
|
-
} else if (style === "pipe-delimited-array" || style === "pipe-delimited-object") {
|
|
195
|
-
parameter.style = "pipeDelimited";
|
|
196
|
-
} else if (style === "space-delimited-array" || style === "space-delimited-object") {
|
|
197
|
-
parameter.style = "spaceDelimited";
|
|
198
|
-
} else if (style === "json") {
|
|
199
|
-
parameter.content = {
|
|
200
|
-
"application/json": { schema: parameter.schema }
|
|
201
|
-
};
|
|
202
|
-
delete parameter.schema;
|
|
203
|
-
} else if (style === void 0) {
|
|
204
|
-
if (flattenJsonUnionSchema(schema2).some((s) => !isJsonPrimitiveSchema(s))) {
|
|
205
|
-
const arrayable = matchArrayableJsonSchema(schema2);
|
|
206
|
-
if (!arrayable || flattenJsonUnionSchema(arrayable[0]).some((s) => !isJsonPrimitiveSchema(s))) {
|
|
207
|
-
parameter.style = "deepObject";
|
|
208
|
-
parameter.explode = true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
} else ;
|
|
212
|
-
ref.parameters ??= [];
|
|
213
|
-
ref.parameters.push(parameter);
|
|
214
|
-
});
|
|
103
|
+
if (areSchemaRefsEquivalentForReuse(
|
|
104
|
+
DEFS_REF_PREFIX + encodeJsonPointerSegment(defName),
|
|
105
|
+
COMPONENTS_REF_PREFIX + encodeJsonPointerSegment(componentName),
|
|
106
|
+
{ defs, componentsSchemas, candidateToExistingKeys: /* @__PURE__ */ new Map(), pairedExistingKeys: /* @__PURE__ */ new Set(), visited: /* @__PURE__ */ new WeakMap() }
|
|
107
|
+
)) {
|
|
108
|
+
return [componentName, true];
|
|
215
109
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function componentNameCandidate(preferredName, direction, attempt) {
|
|
113
|
+
if (attempt === 1) {
|
|
114
|
+
return [preferredName, true, false];
|
|
115
|
+
}
|
|
116
|
+
if (direction !== void 0) {
|
|
117
|
+
if (attempt === 2) {
|
|
118
|
+
return [`${preferredName}${direction === "input" ? "Input" : "Output"}`, true, false];
|
|
226
119
|
}
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
ref.requestBody = {
|
|
230
|
-
required: bodyOptional ? void 0 : true,
|
|
231
|
-
content: toBodyContent(bodySchema, doc, options)
|
|
232
|
-
};
|
|
120
|
+
if (attempt === 3) {
|
|
121
|
+
return [`${preferredName}${direction === "input" ? "Output" : "Input"}`, false, false];
|
|
233
122
|
}
|
|
123
|
+
return [`${preferredName}${attempt - 2}`, true, true];
|
|
234
124
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
125
|
+
return [`${preferredName}${attempt}`, true, true];
|
|
126
|
+
}
|
|
127
|
+
function definedKeysOf(object) {
|
|
128
|
+
return Object.keys(object).filter((key) => object[key] !== void 0).sort();
|
|
129
|
+
}
|
|
130
|
+
function areSchemasEquivalentForReuse(candidate, existing, ctx) {
|
|
131
|
+
if (candidate === existing) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (typeof candidate !== typeof existing) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (candidate === null || existing === null) {
|
|
138
|
+
return candidate === existing;
|
|
139
|
+
}
|
|
140
|
+
if (typeof candidate !== "object" || typeof existing !== "object") {
|
|
141
|
+
return isDeepEqual(candidate, existing);
|
|
142
|
+
}
|
|
143
|
+
const seenExisting = ctx.visited.get(candidate);
|
|
144
|
+
if (seenExisting?.has(existing)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
if (seenExisting) {
|
|
148
|
+
seenExisting.add(existing);
|
|
149
|
+
} else {
|
|
150
|
+
ctx.visited.set(candidate, new WeakSet([existing]));
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(candidate) || Array.isArray(existing)) {
|
|
153
|
+
if (!Array.isArray(candidate) || !Array.isArray(existing) || candidate.length !== existing.length) {
|
|
154
|
+
return false;
|
|
253
155
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
156
|
+
return candidate.every((item, index) => areSchemasEquivalentForReuse(item, existing[index], ctx));
|
|
157
|
+
}
|
|
158
|
+
const candidateObject = candidate;
|
|
159
|
+
const existingObject = existing;
|
|
160
|
+
const candidateKeys = definedKeysOf(candidateObject);
|
|
161
|
+
const existingKeys = definedKeysOf(existingObject);
|
|
162
|
+
if (!isDeepEqual(candidateKeys, existingKeys)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return candidateKeys.every((key) => {
|
|
166
|
+
const candidateValue = candidateObject[key];
|
|
167
|
+
const existingValue = existingObject[key];
|
|
168
|
+
if (key === "$ref" && typeof candidateValue === "string" && typeof existingValue === "string") {
|
|
169
|
+
return areSchemaRefsEquivalentForReuse(candidateValue, existingValue, ctx);
|
|
262
170
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
171
|
+
return areSchemasEquivalentForReuse(candidateValue, existingValue, ctx);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function parseRefName(ref, prefix) {
|
|
175
|
+
if (!ref.startsWith(prefix)) {
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
return ref.slice(prefix.length).split("/").map(decodeJsonPointerSegment).join("/");
|
|
179
|
+
}
|
|
180
|
+
function resolveNamedRef(ref, prefix, getNamed) {
|
|
181
|
+
const name = parseRefName(ref, prefix);
|
|
182
|
+
if (name === void 0) {
|
|
183
|
+
return void 0;
|
|
184
|
+
}
|
|
185
|
+
const schema = getNamed(name);
|
|
186
|
+
return schema === void 0 ? void 0 : { key: prefix + name, schema };
|
|
187
|
+
}
|
|
188
|
+
function areSchemaRefsEquivalentForReuse(candidateRef, existingRef, ctx) {
|
|
189
|
+
const getComponent = (name) => getOwn(ctx.componentsSchemas, name);
|
|
190
|
+
const candidate = resolveNamedRef(candidateRef, DEFS_REF_PREFIX, (name) => ctx.defs.get(name)) ?? resolveNamedRef(candidateRef, COMPONENTS_REF_PREFIX, getComponent);
|
|
191
|
+
const existing = resolveNamedRef(existingRef, COMPONENTS_REF_PREFIX, getComponent);
|
|
192
|
+
if (candidate === void 0 || existing === void 0) {
|
|
193
|
+
return !candidate && !existing && candidateRef === existingRef;
|
|
194
|
+
}
|
|
195
|
+
const pairedExisting = ctx.candidateToExistingKeys.get(candidate.key);
|
|
196
|
+
if (pairedExisting !== existing.key) {
|
|
197
|
+
if (pairedExisting !== void 0 || ctx.pairedExistingKeys.has(existing.key)) {
|
|
198
|
+
return false;
|
|
290
199
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (headerSchemas.length) {
|
|
302
|
-
const entries = extractJsonObjectSchemaEntries(combineJsonSchemasWithComposition("anyOf", headerSchemas));
|
|
303
|
-
entries?.forEach(([name, schema2, optional]) => {
|
|
304
|
-
responseObject.headers ??= {};
|
|
305
|
-
responseObject.headers[name] = {
|
|
306
|
-
required: optional ? void 0 : true,
|
|
307
|
-
schema: toOpenAPISchema(schema2, doc, options)
|
|
308
|
-
};
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
ref.responses ??= {};
|
|
312
|
-
ref.responses[status2] = responseObject;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
async errorResponse(doc, ref, def, meta, options) {
|
|
316
|
-
const errorStatusMap = options.errorStatusMap ?? COMMON_ERROR_STATUS_MAP;
|
|
317
|
-
const errorMap = def.errorMap;
|
|
318
|
-
const errorDefinitionsByStatus = /* @__PURE__ */ new Map();
|
|
319
|
-
for (const code in errorMap) {
|
|
320
|
-
const config = errorMap[code];
|
|
321
|
-
if (!config) {
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
const status = errorStatusMap[code] ?? DEFAULT_ERROR_STATUS;
|
|
325
|
-
const defaultMessage = config.message;
|
|
326
|
-
const [dataJsonSchema, dataOptional] = await this.convertSchema(config.data, "output");
|
|
327
|
-
const definitions = errorDefinitionsByStatus.get(status);
|
|
328
|
-
if (definitions) {
|
|
329
|
-
definitions.push({ code, dataJsonSchema, dataOptional, defaultMessage });
|
|
330
|
-
} else {
|
|
331
|
-
errorDefinitionsByStatus.set(status, [{ code, dataJsonSchema, dataOptional, defaultMessage }]);
|
|
332
|
-
}
|
|
200
|
+
ctx.candidateToExistingKeys.set(candidate.key, existing.key);
|
|
201
|
+
ctx.pairedExistingKeys.add(existing.key);
|
|
202
|
+
}
|
|
203
|
+
return areSchemasEquivalentForReuse(candidate.schema, existing.schema, ctx);
|
|
204
|
+
}
|
|
205
|
+
function rewriteComponentSchemaRefs(schema, renameMap) {
|
|
206
|
+
return mapJsonSchemaRefs(schema, (ref) => {
|
|
207
|
+
const refName = parseRefName(ref, DEFS_REF_PREFIX);
|
|
208
|
+
if (refName === void 0) {
|
|
209
|
+
return ref;
|
|
333
210
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
UndefinedError: {
|
|
338
|
-
type: "object",
|
|
339
|
-
properties: {
|
|
340
|
-
defined: { const: false },
|
|
341
|
-
inferable: { type: "boolean" },
|
|
342
|
-
code: { type: "string" },
|
|
343
|
-
status: { type: "number" },
|
|
344
|
-
message: { type: "string" },
|
|
345
|
-
data: {}
|
|
346
|
-
},
|
|
347
|
-
required: ["defined", "inferable", "code", "status", "message"]
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
$ref: "#/$defs/UndefinedError"
|
|
351
|
-
}, doc, options);
|
|
352
|
-
for (const [status, definitions] of errorDefinitionsByStatus.entries()) {
|
|
353
|
-
const descriptions = definitions.map(({ defaultMessage }) => defaultMessage).filter((m) => m !== void 0);
|
|
354
|
-
const customBodySchema = value(
|
|
355
|
-
options.customErrorResponseBodySchema,
|
|
356
|
-
definitions.map((def2) => ({ ...def2, dataJsonSchema: hoistDefs(def2.dataJsonSchema, doc, options) })),
|
|
357
|
-
status
|
|
358
|
-
);
|
|
359
|
-
const responseSchema = customBodySchema ?? combineJsonSchemasWithComposition("oneOf", [
|
|
360
|
-
...definitions.map(({ code, dataJsonSchema, dataOptional, defaultMessage }) => {
|
|
361
|
-
return combineJsonObjectSchemaEntries([
|
|
362
|
-
["defined", { const: true }, false],
|
|
363
|
-
["inferable", { type: "boolean" }, false],
|
|
364
|
-
["code", { const: code }, false],
|
|
365
|
-
["status", { const: status }, false],
|
|
366
|
-
["message", { type: "string", default: defaultMessage }, false],
|
|
367
|
-
["data", dataJsonSchema, dataOptional]
|
|
368
|
-
]);
|
|
369
|
-
}),
|
|
370
|
-
undefinedErrorSchema
|
|
371
|
-
]);
|
|
372
|
-
ref.responses ??= {};
|
|
373
|
-
ref.responses[status] = {
|
|
374
|
-
description: descriptions.length ? descriptions.join(", ") : status.toString(),
|
|
375
|
-
content: {
|
|
376
|
-
"application/json": {
|
|
377
|
-
schema: toOpenAPISchema(responseSchema, doc, options)
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
}
|
|
211
|
+
const renamedName = renameMap.get(refName);
|
|
212
|
+
if (renamedName === void 0) {
|
|
213
|
+
return ref;
|
|
382
214
|
}
|
|
383
|
-
|
|
215
|
+
return COMPONENTS_REF_PREFIX + encodeJsonPointerSegment(renamedName);
|
|
216
|
+
});
|
|
384
217
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
schema,
|
|
388
|
-
doc,
|
|
389
|
-
options
|
|
390
|
-
));
|
|
218
|
+
|
|
219
|
+
class OpenAPIGeneratorError extends TypeError {
|
|
391
220
|
}
|
|
392
221
|
function toOpenAPIPath(path, dynamicPathParams) {
|
|
393
222
|
if (!dynamicPathParams?.length) {
|
|
@@ -403,21 +232,336 @@ function toOpenAPIPath(path, dynamicPathParams) {
|
|
|
403
232
|
normalized += path.slice(currentIndex);
|
|
404
233
|
return normalized;
|
|
405
234
|
}
|
|
406
|
-
function
|
|
407
|
-
|
|
408
|
-
|
|
235
|
+
function buildRequest(ctx, operation, def, meta, dynamicPathParams) {
|
|
236
|
+
const method = meta?.method ?? DEFAULT_OPENAPI_METHOD;
|
|
237
|
+
const inputStructure = meta?.inputStructure ?? DEFAULT_OPENAPI_INPUT_STRUCTURE;
|
|
238
|
+
if (inputStructure === "compact") {
|
|
239
|
+
const iteratorDetails = getAsyncIteratorObjectDetails(def.inputSchemas);
|
|
240
|
+
if (iteratorDetails) {
|
|
241
|
+
operation.requestBody = {
|
|
242
|
+
required: true,
|
|
243
|
+
content: toAsyncIteratorObjectContent(
|
|
244
|
+
ctx,
|
|
245
|
+
"input",
|
|
246
|
+
ctx.convertSchemas(iteratorDetails[0], "input"),
|
|
247
|
+
ctx.convertSchemas(iteratorDetails[1], "input")
|
|
248
|
+
)
|
|
249
|
+
};
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
409
252
|
}
|
|
410
|
-
const
|
|
411
|
-
|
|
253
|
+
const dynamicParams = dynamicPathParams?.map((v) => v.parameterName);
|
|
254
|
+
const [schema, optional] = ctx.convertSchemas(def.inputSchemas, "input");
|
|
255
|
+
if (isUnconstrainedSchema(schema) && !dynamicParams?.length) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const parts = inputStructure === "compact" ? extractCompactRequestParts(schema, optional, method, dynamicParams) : extractDetailedRequestParts(schema);
|
|
259
|
+
renderPathParameters(ctx, operation, dynamicParams, parts.paramsEntries, meta?.paramsStyles);
|
|
260
|
+
renderQueryParameters(ctx, operation, parts.queryEntries, meta?.queryStyles);
|
|
261
|
+
renderHeaderParameters(ctx, operation, parts.headersEntries);
|
|
262
|
+
if (parts.bodySchema !== void 0) {
|
|
263
|
+
operation.requestBody = {
|
|
264
|
+
required: parts.bodyOptional ? void 0 : true,
|
|
265
|
+
content: toBodyContent(ctx, "input", parts.bodySchema)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function extractCompactRequestParts(schema, optional, method, dynamicParams) {
|
|
270
|
+
const entries = extractJsonObjectSchemaEntries(schema);
|
|
271
|
+
if (!entries && isBodylessMethod(method)) {
|
|
272
|
+
throw new OpenAPIGeneratorError(
|
|
273
|
+
`method is ${method} but the input schema is not an object.
|
|
274
|
+
${method} sends every input field as a query parameter, so the input must be an object schema.
|
|
275
|
+
Fix: make the input an object, or use a method with a request body (POST, PUT, PATCH, DELETE).`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const paramsEntries = entries?.filter(([name]) => dynamicParams?.includes(name));
|
|
279
|
+
const restEntries = entries?.filter(([name]) => !dynamicParams?.includes(name));
|
|
280
|
+
const hasBody = !isBodylessMethod(method);
|
|
281
|
+
const bodySchema = !hasBody ? void 0 : !dynamicParams?.length ? schema : restEntries ? combineJsonObjectSchemaEntries(restEntries) : void 0;
|
|
282
|
+
return {
|
|
283
|
+
paramsEntries,
|
|
284
|
+
queryEntries: isBodylessMethod(method) ? restEntries : void 0,
|
|
285
|
+
headersEntries: void 0,
|
|
286
|
+
bodySchema,
|
|
287
|
+
bodyOptional: !dynamicParams?.length ? optional : restEntries?.every(([, , optional2]) => optional2)
|
|
288
|
+
};
|
|
412
289
|
}
|
|
413
|
-
function
|
|
290
|
+
function extractDetailedRequestParts(schema) {
|
|
291
|
+
const entries = extractJsonObjectSchemaEntries(schema);
|
|
292
|
+
if (!entries) {
|
|
293
|
+
throw new OpenAPIGeneratorError(
|
|
294
|
+
`inputStructure is "detailed" but the input schema is not an object.
|
|
295
|
+
Expected an object shaped like: { params?: object, query?: object, headers?: object, body?: unknown }`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const section = (name) => entries.find(([entryName]) => entryName === name);
|
|
299
|
+
return {
|
|
300
|
+
paramsEntries: extractJsonObjectSchemaEntries(section("params")?.[1] ?? false),
|
|
301
|
+
queryEntries: extractJsonObjectSchemaEntries(section("query")?.[1] ?? false),
|
|
302
|
+
headersEntries: extractJsonObjectSchemaEntries(section("headers")?.[1] ?? false),
|
|
303
|
+
bodySchema: section("body")?.[1],
|
|
304
|
+
bodyOptional: section("body")?.[2]
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function renderPathParameters(ctx, operation, dynamicParams, paramsEntries, paramsStyles) {
|
|
308
|
+
if (!dynamicParams?.length) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (!paramsEntries) {
|
|
312
|
+
throw new OpenAPIGeneratorError(
|
|
313
|
+
`the route declares path params (${dynamicParams.map((p) => `{${p}}`).join(", ")}) but there is no object schema to source them from.
|
|
314
|
+
compact mode: the input schema must be an object containing each param.
|
|
315
|
+
detailed mode: the input's "params" section must be an object containing each param.`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
for (const name of dynamicParams) {
|
|
319
|
+
const entry = paramsEntries.find(([entryName]) => entryName === name);
|
|
320
|
+
if (!entry) {
|
|
321
|
+
throw new OpenAPIGeneratorError(
|
|
322
|
+
`dynamic param "{${name}}" is missing from the input schema.
|
|
323
|
+
Route params: ${dynamicParams.map((p) => `{${p}}`).join(", ")}
|
|
324
|
+
Schema keys: ${paramsEntries.map(([n]) => n).join(", ") || "(none)"}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
if (entry[2]) {
|
|
328
|
+
throw new OpenAPIGeneratorError(
|
|
329
|
+
`dynamic param "${name}" is optional in the input schema.
|
|
330
|
+
OpenAPI requires path params to be required. Make "${name}" required.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const style = paramsStyles?.[name];
|
|
334
|
+
const parameter = {
|
|
335
|
+
in: "path",
|
|
336
|
+
required: true,
|
|
337
|
+
name,
|
|
338
|
+
schema: ctx.registry.toOpenAPISchema(entry[1], "input")
|
|
339
|
+
};
|
|
340
|
+
if (style === "comma-delimited-array" || style === "comma-delimited-object") {
|
|
341
|
+
parameter.style = "simple";
|
|
342
|
+
parameter.explode = false;
|
|
343
|
+
}
|
|
344
|
+
operation.parameters ??= [];
|
|
345
|
+
operation.parameters.push(parameter);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function renderQueryParameters(ctx, operation, queryEntries, queryStyles) {
|
|
349
|
+
for (const [name, schema, optional] of queryEntries ?? []) {
|
|
350
|
+
const style = queryStyles?.[name];
|
|
351
|
+
const parameter = {
|
|
352
|
+
in: "query",
|
|
353
|
+
name,
|
|
354
|
+
schema: ctx.registry.toOpenAPISchema(schema, "input"),
|
|
355
|
+
allowEmptyValue: true,
|
|
356
|
+
allowReserved: true
|
|
357
|
+
};
|
|
358
|
+
if (!optional) {
|
|
359
|
+
parameter.required = true;
|
|
360
|
+
}
|
|
361
|
+
if (style === "comma-delimited-array" || style === "comma-delimited-object") {
|
|
362
|
+
parameter.explode = false;
|
|
363
|
+
} else if (style === "pipe-delimited-array" || style === "pipe-delimited-object") {
|
|
364
|
+
parameter.style = "pipeDelimited";
|
|
365
|
+
} else if (style === "space-delimited-array" || style === "space-delimited-object") {
|
|
366
|
+
parameter.style = "spaceDelimited";
|
|
367
|
+
} else if (style === "json") {
|
|
368
|
+
parameter.content = {
|
|
369
|
+
"application/json": { schema: parameter.schema }
|
|
370
|
+
};
|
|
371
|
+
delete parameter.schema;
|
|
372
|
+
} else if (style === void 0) {
|
|
373
|
+
if (flattenJsonUnionSchema(schema).some((s) => !isJsonPrimitiveSchema(s))) {
|
|
374
|
+
const arrayable = matchArrayableJsonSchema(schema);
|
|
375
|
+
if (!arrayable || flattenJsonUnionSchema(arrayable[0]).some((s) => !isJsonPrimitiveSchema(s))) {
|
|
376
|
+
parameter.style = "deepObject";
|
|
377
|
+
parameter.explode = true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} else ;
|
|
381
|
+
operation.parameters ??= [];
|
|
382
|
+
operation.parameters.push(parameter);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function renderHeaderParameters(ctx, operation, headersEntries) {
|
|
386
|
+
for (const [name, schema, optional] of headersEntries ?? []) {
|
|
387
|
+
operation.parameters ??= [];
|
|
388
|
+
operation.parameters.push({
|
|
389
|
+
in: "header",
|
|
390
|
+
name,
|
|
391
|
+
required: optional ? void 0 : true,
|
|
392
|
+
schema: ctx.registry.toOpenAPISchema(schema, "input")
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function buildSuccessResponse(ctx, operation, def, meta) {
|
|
397
|
+
const status = meta?.successStatus ?? DEFAULT_SUCCESS_STATUS;
|
|
398
|
+
const description = meta?.successDescription ?? DEFAULT_OPENAPI_SUCCESS_DESCRIPTION;
|
|
399
|
+
const outputStructure = meta?.outputStructure ?? DEFAULT_OPENAPI_OUTPUT_STRUCTURE;
|
|
400
|
+
if (outputStructure === "compact") {
|
|
401
|
+
const iteratorDetails = getAsyncIteratorObjectDetails(def.outputSchemas);
|
|
402
|
+
if (iteratorDetails) {
|
|
403
|
+
operation.responses ??= {};
|
|
404
|
+
operation.responses[toResponseStatusKey(status)] = {
|
|
405
|
+
description,
|
|
406
|
+
content: toAsyncIteratorObjectContent(
|
|
407
|
+
ctx,
|
|
408
|
+
"output",
|
|
409
|
+
ctx.convertSchemas(iteratorDetails[0], "output"),
|
|
410
|
+
ctx.convertSchemas(iteratorDetails[1], "output")
|
|
411
|
+
)
|
|
412
|
+
};
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const [schema] = ctx.convertSchemas(def.outputSchemas, "output");
|
|
417
|
+
if (isUnconstrainedSchema(schema) || outputStructure === "compact") {
|
|
418
|
+
operation.responses ??= {};
|
|
419
|
+
operation.responses[toResponseStatusKey(status)] = {
|
|
420
|
+
description,
|
|
421
|
+
content: toBodyContent(ctx, "output", schema)
|
|
422
|
+
};
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
for (const [responseStatus, parts] of extractDetailedResponseParts(schema, status)) {
|
|
426
|
+
const responseObject = {
|
|
427
|
+
description: parts.descriptions.length ? parts.descriptions.join(", ") : description
|
|
428
|
+
};
|
|
429
|
+
if (parts.bodies.length) {
|
|
430
|
+
responseObject.content = toBodyContent(ctx, "output", combineJsonSchemasWithComposition("anyOf", parts.bodies));
|
|
431
|
+
}
|
|
432
|
+
if (parts.headers.length) {
|
|
433
|
+
const entries = extractJsonObjectSchemaEntries(combineJsonSchemasWithComposition("anyOf", parts.headers));
|
|
434
|
+
entries?.forEach(([name, schema2, optional]) => {
|
|
435
|
+
responseObject.headers ??= {};
|
|
436
|
+
responseObject.headers[name] = {
|
|
437
|
+
required: optional ? void 0 : true,
|
|
438
|
+
schema: ctx.registry.toOpenAPISchema(schema2, "output")
|
|
439
|
+
};
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
operation.responses ??= {};
|
|
443
|
+
operation.responses[toResponseStatusKey(responseStatus)] = responseObject;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function toResponseStatusKey(status) {
|
|
447
|
+
return String(status);
|
|
448
|
+
}
|
|
449
|
+
function extractDetailedResponseParts(schema, defaultStatus) {
|
|
450
|
+
const partsByStatus = /* @__PURE__ */ new Map();
|
|
451
|
+
for (const item of flattenJsonUnionSchema(schema)) {
|
|
452
|
+
const entries = extractJsonObjectSchemaEntries(item);
|
|
453
|
+
if (!entries) {
|
|
454
|
+
throw new OpenAPIGeneratorError(
|
|
455
|
+
`outputStructure is "detailed" but the output schema (or one of its union members) is not an object.
|
|
456
|
+
Expected each member shaped like: { status?: number (< 400), headers?: object, body?: unknown }`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const statusSchema = entries.find(([name]) => name === "status")?.[1];
|
|
460
|
+
if (statusSchema !== void 0 && (typeof statusSchema !== "object" || !Number.isInteger(statusSchema.const) || statusSchema.const >= 400)) {
|
|
461
|
+
throw new OpenAPIGeneratorError(
|
|
462
|
+
`invalid "status" field in the detailed output schema.
|
|
463
|
+
Expected: a literal (const) integer below 400
|
|
464
|
+
Received: ${stringifyJSON(statusSchema)}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const status = (statusSchema?.const || void 0) ?? defaultStatus;
|
|
468
|
+
let parts = partsByStatus.get(status);
|
|
469
|
+
if (!parts) {
|
|
470
|
+
parts = { descriptions: [], bodies: [], headers: [] };
|
|
471
|
+
partsByStatus.set(status, parts);
|
|
472
|
+
}
|
|
473
|
+
if (statusSchema?.description !== void 0) {
|
|
474
|
+
parts.descriptions.push(statusSchema.description);
|
|
475
|
+
}
|
|
476
|
+
const headersSchema = entries.find(([name]) => name === "headers")?.[1];
|
|
477
|
+
if (headersSchema !== void 0) {
|
|
478
|
+
parts.headers.push(headersSchema);
|
|
479
|
+
}
|
|
480
|
+
const bodySchema = entries.find(([name]) => name === "body")?.[1];
|
|
481
|
+
if (bodySchema !== void 0) {
|
|
482
|
+
parts.bodies.push(bodySchema);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return partsByStatus;
|
|
486
|
+
}
|
|
487
|
+
function buildErrorResponse(ctx, operation, def) {
|
|
488
|
+
const definitionsByStatus = /* @__PURE__ */ new Map();
|
|
489
|
+
for (const code of Object.keys(def.errorMap)) {
|
|
490
|
+
const config = def.errorMap[code];
|
|
491
|
+
if (!config) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const status = ctx.errorStatusMap[code] ?? DEFAULT_ERROR_STATUS;
|
|
495
|
+
const [dataJsonSchema, dataOptional] = ctx.convertSchemas(config.data !== void 0 ? [config.data] : void 0, "output");
|
|
496
|
+
let definitions = definitionsByStatus.get(status);
|
|
497
|
+
if (!definitions) {
|
|
498
|
+
definitions = [];
|
|
499
|
+
definitionsByStatus.set(status, definitions);
|
|
500
|
+
}
|
|
501
|
+
definitions.push({ code, dataJsonSchema, dataOptional, defaultMessage: config.message });
|
|
502
|
+
}
|
|
503
|
+
if (!definitionsByStatus.size) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const undefinedErrorSchema = ctx.registry.register("UndefinedError", {
|
|
507
|
+
title: "UndefinedError",
|
|
508
|
+
type: "object",
|
|
509
|
+
properties: {
|
|
510
|
+
defined: { const: false },
|
|
511
|
+
code: { type: "string" },
|
|
512
|
+
message: { type: "string" },
|
|
513
|
+
data: {}
|
|
514
|
+
},
|
|
515
|
+
required: ["defined", "code", "message"]
|
|
516
|
+
});
|
|
517
|
+
for (const [status, definitions] of definitionsByStatus.entries()) {
|
|
518
|
+
const descriptions = definitions.map(({ defaultMessage }) => defaultMessage).filter((m) => m !== void 0);
|
|
519
|
+
const customBodySchema = value(
|
|
520
|
+
ctx.customErrorResponseBodySchema,
|
|
521
|
+
definitions.map((def2) => ({ ...def2, dataJsonSchema: ctx.registry.hoistDefs(def2.dataJsonSchema, "output") })),
|
|
522
|
+
status
|
|
523
|
+
);
|
|
524
|
+
const responseSchema = customBodySchema ?? combineJsonSchemasWithComposition("oneOf", [
|
|
525
|
+
...definitions.map(({ code, dataJsonSchema, dataOptional }) => {
|
|
526
|
+
return ctx.registry.register(toErrorComponentName(code), {
|
|
527
|
+
title: code,
|
|
528
|
+
...combineJsonObjectSchemaEntries([
|
|
529
|
+
["defined", { const: true }, false],
|
|
530
|
+
["code", { const: code }, false],
|
|
531
|
+
// avoid using the defaultMessage here to improve component reusability
|
|
532
|
+
["message", { type: "string" }, false],
|
|
533
|
+
["data", ctx.registry.hoistDefs(dataJsonSchema, "output"), dataOptional]
|
|
534
|
+
])
|
|
535
|
+
});
|
|
536
|
+
}),
|
|
537
|
+
undefinedErrorSchema
|
|
538
|
+
]);
|
|
539
|
+
operation.responses ??= {};
|
|
540
|
+
operation.responses[toResponseStatusKey(status)] = {
|
|
541
|
+
description: descriptions.length ? descriptions.join(", ") : status.toString(),
|
|
542
|
+
content: {
|
|
543
|
+
"application/json": {
|
|
544
|
+
schema: ctx.registry.toOpenAPISchema(responseSchema, "output")
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function toErrorComponentName(code) {
|
|
551
|
+
const name = code.split(/[^a-z0-9]+/i).filter(Boolean).map((part) => {
|
|
552
|
+
const rest = part.slice(1);
|
|
553
|
+
return part.charAt(0).toUpperCase() + (rest === rest.toUpperCase() ? rest.toLowerCase() : rest);
|
|
554
|
+
}).join("");
|
|
555
|
+
return name || "Error";
|
|
556
|
+
}
|
|
557
|
+
function getAsyncIteratorObjectDetails(schemas) {
|
|
414
558
|
if (!schemas || schemas.length === 0) {
|
|
415
559
|
return void 0;
|
|
416
560
|
}
|
|
417
561
|
const yieldSchemas = [];
|
|
418
562
|
const returnSchemas = [];
|
|
419
563
|
for (const s of schemas) {
|
|
420
|
-
const details =
|
|
564
|
+
const details = getAsyncIteratorObjectSchemaDetails(s);
|
|
421
565
|
if (!details) {
|
|
422
566
|
return void 0;
|
|
423
567
|
}
|
|
@@ -426,9 +570,9 @@ function getEventIteratorDetails(schemas) {
|
|
|
426
570
|
returnSchemas.push(details.returnSchema);
|
|
427
571
|
}
|
|
428
572
|
}
|
|
429
|
-
return
|
|
573
|
+
return [yieldSchemas, returnSchemas];
|
|
430
574
|
}
|
|
431
|
-
function
|
|
575
|
+
function toAsyncIteratorObjectContent(ctx, direction, [yieldSchema, yieldOptional], [returnSchema, returnOptional]) {
|
|
432
576
|
const schema = combineJsonSchemasWithComposition("oneOf", [
|
|
433
577
|
combineJsonObjectSchemaEntries([
|
|
434
578
|
["event", { const: "message" }, false],
|
|
@@ -455,11 +599,11 @@ function toEventIteratorContent([yieldSchema, yieldOptional], [returnSchema, ret
|
|
|
455
599
|
]);
|
|
456
600
|
return {
|
|
457
601
|
"text/event-stream": {
|
|
458
|
-
schema: toOpenAPISchema(schema,
|
|
602
|
+
schema: ctx.registry.toOpenAPISchema(schema, direction)
|
|
459
603
|
}
|
|
460
604
|
};
|
|
461
605
|
}
|
|
462
|
-
function toBodyContent(
|
|
606
|
+
function toBodyContent(ctx, direction, schema) {
|
|
463
607
|
const fileSchemasByMediaType = /* @__PURE__ */ new Map();
|
|
464
608
|
const rest = flattenJsonUnionSchema(schema).filter((s) => {
|
|
465
609
|
if (!isJsonFileSchema(s)) {
|
|
@@ -485,416 +629,121 @@ function toBodyContent(schema, doc, options) {
|
|
|
485
629
|
const fileSchemas = fileSchemasByMediaType.get(contentType);
|
|
486
630
|
fileSchemasByMediaType.delete(contentType);
|
|
487
631
|
content[contentType] = {
|
|
488
|
-
schema: toOpenAPISchema(combineJsonSchemasWithComposition("anyOf", [restSchema, ...
|
|
632
|
+
schema: ctx.registry.toOpenAPISchema(combineJsonSchemasWithComposition("anyOf", [restSchema, ...fileSchemas ?? []]), direction)
|
|
489
633
|
};
|
|
490
634
|
}
|
|
491
635
|
for (const [contentType, schemas] of fileSchemasByMediaType.entries()) {
|
|
492
636
|
content[contentType] = {
|
|
493
|
-
schema: toOpenAPISchema(combineJsonSchemasWithComposition("anyOf", schemas),
|
|
637
|
+
schema: ctx.registry.toOpenAPISchema(combineJsonSchemasWithComposition("anyOf", schemas), direction)
|
|
494
638
|
};
|
|
495
639
|
}
|
|
496
640
|
return content;
|
|
497
641
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
for (const defName of Object.keys($defs)) {
|
|
509
|
-
const defSchema = $defs[defName];
|
|
510
|
-
if (defSchema === void 0) {
|
|
511
|
-
continue;
|
|
512
|
-
}
|
|
513
|
-
const normalized = normalizeHoistedDefSchema(defSchema);
|
|
514
|
-
if (value(options.shouldHoistDef, defName, normalized) !== false) {
|
|
515
|
-
hoistedDefs[defName] = normalized;
|
|
516
|
-
} else {
|
|
517
|
-
localDefs[defName] = normalized;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
hoistReferencedLocalDefs(hoistedDefs, localDefs);
|
|
521
|
-
if (Object.keys(hoistedDefs).length === 0) {
|
|
522
|
-
return schema;
|
|
523
|
-
}
|
|
524
|
-
doc.components ??= {};
|
|
525
|
-
doc.components.schemas ??= {};
|
|
526
|
-
const componentsSchemas = doc.components.schemas;
|
|
527
|
-
const identityRenameMap = Object.fromEntries(
|
|
528
|
-
Object.keys(hoistedDefs).map((defName) => [defName, defName])
|
|
529
|
-
);
|
|
530
|
-
const renameMap = {};
|
|
531
|
-
const pendingSchemas = [];
|
|
532
|
-
for (const defName of Object.keys(hoistedDefs)) {
|
|
533
|
-
const cleanSchema = hoistedDefs[defName];
|
|
534
|
-
const existingSchema = componentsSchemas[defName];
|
|
535
|
-
const candidateSchemas = Object.fromEntries(
|
|
536
|
-
Object.keys(hoistedDefs).map((currentDefName) => [
|
|
537
|
-
currentDefName,
|
|
538
|
-
rewriteComponentSchemaRefs(
|
|
539
|
-
withReferencedLocalDefs(hoistedDefs[currentDefName], localDefs),
|
|
540
|
-
{
|
|
541
|
-
...identityRenameMap,
|
|
542
|
-
...renameMap
|
|
543
|
-
}
|
|
544
|
-
)
|
|
545
|
-
])
|
|
546
|
-
);
|
|
547
|
-
const prelimSchema = candidateSchemas[defName];
|
|
548
|
-
if (existingSchema !== void 0) {
|
|
549
|
-
const reusableComponentName = findReusableComponentName(componentsSchemas, defName, prelimSchema, candidateSchemas);
|
|
550
|
-
if (reusableComponentName !== void 0) {
|
|
551
|
-
renameMap[defName] = reusableComponentName;
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
const componentName = findUniqueComponentName(componentsSchemas, defName);
|
|
555
|
-
renameMap[defName] = componentName;
|
|
556
|
-
pendingSchemas.push({ cleanSchema, componentName });
|
|
557
|
-
} else {
|
|
558
|
-
const reusableComponentName = findReusableComponentName(componentsSchemas, defName, prelimSchema, candidateSchemas);
|
|
559
|
-
if (reusableComponentName !== void 0) {
|
|
560
|
-
renameMap[defName] = reusableComponentName;
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
renameMap[defName] = defName;
|
|
564
|
-
pendingSchemas.push({ cleanSchema, componentName: defName });
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
for (const { cleanSchema, componentName } of pendingSchemas) {
|
|
568
|
-
componentsSchemas[componentName] = rewriteComponentSchemaRefs(
|
|
569
|
-
withReferencedLocalDefs(cleanSchema, localDefs),
|
|
570
|
-
renameMap
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
return rewriteComponentSchemaRefs(withReferencedLocalDefs(rest, localDefs), renameMap);
|
|
574
|
-
}
|
|
575
|
-
function normalizeHoistedDefSchema(schema) {
|
|
576
|
-
let cleanSchema = typeof schema === "boolean" ? schema ? {} : { not: {} } : { ...schema };
|
|
577
|
-
if (cleanSchema.additionalProperties === false) {
|
|
578
|
-
const { additionalProperties: _ignored, ...withoutAdditionalProperties } = cleanSchema;
|
|
579
|
-
cleanSchema = withoutAdditionalProperties;
|
|
580
|
-
}
|
|
581
|
-
return cleanSchema;
|
|
582
|
-
}
|
|
583
|
-
function hoistReferencedLocalDefs(hoistedDefs, localDefs) {
|
|
584
|
-
const queue = Object.values(hoistedDefs);
|
|
585
|
-
while (queue.length > 0) {
|
|
586
|
-
const current = queue.shift();
|
|
587
|
-
if (current === void 0) {
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
visitSchemaRefs(current, (refName) => {
|
|
591
|
-
const referenced = localDefs[refName];
|
|
592
|
-
if (referenced === void 0) {
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
hoistedDefs[refName] = referenced;
|
|
596
|
-
delete localDefs[refName];
|
|
597
|
-
queue.push(referenced);
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
function withReferencedLocalDefs(schema, localDefs) {
|
|
602
|
-
const referencedLocalDefs = collectReferencedLocalDefNames(schema, localDefs);
|
|
603
|
-
if (referencedLocalDefs.length === 0) {
|
|
604
|
-
return schema;
|
|
605
|
-
}
|
|
606
|
-
const mergedDefs = {
|
|
607
|
-
...schema.$defs
|
|
608
|
-
};
|
|
609
|
-
for (const defName of referencedLocalDefs) {
|
|
610
|
-
mergedDefs[defName] = localDefs[defName];
|
|
642
|
+
|
|
643
|
+
class OpenAPIGenerator {
|
|
644
|
+
serializer;
|
|
645
|
+
converter;
|
|
646
|
+
constructor(options = {}) {
|
|
647
|
+
this.serializer = options.serializer ?? new OpenAPISerializer();
|
|
648
|
+
this.converter = new DelegatingJsonSchemaConverter([
|
|
649
|
+
...toArray(options.converters),
|
|
650
|
+
new StandardJsonSchemaConverter()
|
|
651
|
+
]);
|
|
611
652
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
visitSchemaRefs(current, (refName) => {
|
|
630
|
-
if (localDefs[refName] === void 0 || referenced.has(refName)) {
|
|
653
|
+
async generate(router, options = {}) {
|
|
654
|
+
const version = options.version ?? "3.2.0";
|
|
655
|
+
const doc = {
|
|
656
|
+
...clone(options.base),
|
|
657
|
+
openapi: "3.2.0",
|
|
658
|
+
info: options.base?.info ?? { title: "API Reference", version: "0.0.0" }
|
|
659
|
+
};
|
|
660
|
+
const ctx = {
|
|
661
|
+
registry: new OpenAPIComponentRegistry(doc, options.customComponentName),
|
|
662
|
+
convertSchemas: (schemas, direction) => this.convertSchemas(schemas, direction),
|
|
663
|
+
errorStatusMap: options.errorStatusMap ?? COMMON_ERROR_STATUS_MAP,
|
|
664
|
+
customErrorResponseBodySchema: options.customErrorResponseBodySchema
|
|
665
|
+
};
|
|
666
|
+
const errors = [];
|
|
667
|
+
await walkProcedureContractsAsync(router, (contract, path) => {
|
|
668
|
+
if (value(options.filter, contract, path) === false) {
|
|
631
669
|
return;
|
|
632
670
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
671
|
+
try {
|
|
672
|
+
const def = contract["~orpc"];
|
|
673
|
+
const meta = getOpenAPIMeta(contract);
|
|
674
|
+
const method = (meta?.method ?? DEFAULT_OPENAPI_METHOD).toLowerCase();
|
|
675
|
+
if (method === "query" && !version.startsWith("3.2.")) {
|
|
676
|
+
throw new OpenAPIGeneratorError(
|
|
677
|
+
`QUERY operations require OpenAPI 3.2, but version "${version}" was requested.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const postPath = meta?.path ?? pathToHttpPath(path);
|
|
681
|
+
const httpPath = meta?.prefix ? mergeHttpPath(meta.prefix, postPath) : postPath;
|
|
682
|
+
const dynamicPathParams = getDynamicPathParams(httpPath);
|
|
683
|
+
const openApiPath = toOpenAPIPath(httpPath, dynamicPathParams);
|
|
684
|
+
let operation;
|
|
685
|
+
if (meta?.spec !== void 0 && typeof meta.spec !== "function") {
|
|
686
|
+
operation = meta.spec;
|
|
687
|
+
} else {
|
|
688
|
+
operation = {
|
|
689
|
+
operationId: meta?.operationId ?? path.join("."),
|
|
690
|
+
summary: meta?.summary,
|
|
691
|
+
description: meta?.description,
|
|
692
|
+
deprecated: meta?.deprecated,
|
|
693
|
+
tags: meta?.tags?.map((tag) => tag)
|
|
694
|
+
};
|
|
695
|
+
buildRequest(ctx, operation, def, meta, dynamicPathParams);
|
|
696
|
+
buildSuccessResponse(ctx, operation, def, meta);
|
|
697
|
+
buildErrorResponse(ctx, operation, def);
|
|
698
|
+
}
|
|
699
|
+
if (typeof meta?.spec === "function") {
|
|
700
|
+
operation = meta.spec(operation);
|
|
701
|
+
}
|
|
702
|
+
doc.paths ??= {};
|
|
703
|
+
doc.paths[openApiPath] ??= {};
|
|
704
|
+
doc.paths[openApiPath][method] = operation;
|
|
705
|
+
} catch (e) {
|
|
706
|
+
if (!(e instanceof OpenAPIGeneratorError)) {
|
|
707
|
+
throw e;
|
|
708
|
+
}
|
|
709
|
+
errors.push(`Procedure at ${path.join(".") || "(root)"}: ${e.message}`);
|
|
637
710
|
}
|
|
638
711
|
});
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (seen.has(schema)) {
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
seen.add(schema);
|
|
650
|
-
if (typeof schema.$ref === "string") {
|
|
651
|
-
const refName = parseLocalDefRefName(schema.$ref);
|
|
652
|
-
if (refName !== void 0) {
|
|
653
|
-
onRef(refName);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
657
|
-
if (Array.isArray(schema[keyword])) {
|
|
658
|
-
for (const item of schema[keyword]) {
|
|
659
|
-
visitSchemaRefs(item, onRef, seen);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
if (schema.properties) {
|
|
664
|
-
for (const property of Object.values(schema.properties)) {
|
|
665
|
-
visitSchemaRefs(property, onRef, seen);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
if (schema.items !== void 0) {
|
|
669
|
-
visitSchemaRefs(schema.items, onRef, seen);
|
|
670
|
-
}
|
|
671
|
-
if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) {
|
|
672
|
-
visitSchemaRefs(schema.additionalProperties, onRef, seen);
|
|
673
|
-
}
|
|
674
|
-
if (schema.not !== void 0) {
|
|
675
|
-
visitSchemaRefs(schema.not, onRef, seen);
|
|
676
|
-
}
|
|
677
|
-
if (schema.if !== void 0) {
|
|
678
|
-
visitSchemaRefs(schema.if, onRef, seen);
|
|
679
|
-
}
|
|
680
|
-
if (schema.then !== void 0) {
|
|
681
|
-
visitSchemaRefs(schema.then, onRef, seen);
|
|
682
|
-
}
|
|
683
|
-
if (schema.else !== void 0) {
|
|
684
|
-
visitSchemaRefs(schema.else, onRef, seen);
|
|
685
|
-
}
|
|
686
|
-
if (Array.isArray(schema.prefixItems)) {
|
|
687
|
-
for (const item of schema.prefixItems) {
|
|
688
|
-
visitSchemaRefs(item, onRef, seen);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
if (schema.$defs) {
|
|
692
|
-
for (const def of Object.values(schema.$defs)) {
|
|
693
|
-
if (def !== void 0) {
|
|
694
|
-
visitSchemaRefs(def, onRef, seen);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
function findUniqueComponentName(componentsSchemas, baseName) {
|
|
700
|
-
const candidate = `${baseName}`;
|
|
701
|
-
if (componentsSchemas[candidate] === void 0)
|
|
702
|
-
return candidate;
|
|
703
|
-
let i = 2;
|
|
704
|
-
while (componentsSchemas[`${baseName}${i}`] !== void 0) {
|
|
705
|
-
i++;
|
|
706
|
-
}
|
|
707
|
-
return `${baseName}${i}`;
|
|
708
|
-
}
|
|
709
|
-
function findReusableComponentName(componentsSchemas, defName, schema, candidateSchemas) {
|
|
710
|
-
const exactMatch = componentsSchemas[defName];
|
|
711
|
-
if (exactMatch !== void 0 && areSchemasEquivalentForReuse(
|
|
712
|
-
schema,
|
|
713
|
-
exactMatch,
|
|
714
|
-
schema,
|
|
715
|
-
exactMatch,
|
|
716
|
-
candidateSchemas,
|
|
717
|
-
componentsSchemas,
|
|
718
|
-
/* @__PURE__ */ new Map([[defName, defName]]),
|
|
719
|
-
/* @__PURE__ */ new Map([[defName, defName]])
|
|
720
|
-
)) {
|
|
721
|
-
return defName;
|
|
722
|
-
}
|
|
723
|
-
for (const [componentName, componentSchema] of Object.entries(componentsSchemas)) {
|
|
724
|
-
if (componentName === defName) {
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
if (areSchemasEquivalentForReuse(
|
|
728
|
-
schema,
|
|
729
|
-
componentSchema,
|
|
730
|
-
schema,
|
|
731
|
-
componentSchema,
|
|
732
|
-
candidateSchemas,
|
|
733
|
-
componentsSchemas,
|
|
734
|
-
/* @__PURE__ */ new Map([[defName, componentName]]),
|
|
735
|
-
/* @__PURE__ */ new Map([[componentName, defName]])
|
|
736
|
-
)) {
|
|
737
|
-
return componentName;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
return void 0;
|
|
741
|
-
}
|
|
742
|
-
function areSchemasEquivalentForReuse(candidate, existing, candidateRootSchema, existingRootSchema, candidateSchemas, existingSchemas, candidateToExistingComponentNames, existingToCandidateComponentNames, visited = /* @__PURE__ */ new WeakMap()) {
|
|
743
|
-
if (candidate === existing) {
|
|
744
|
-
return true;
|
|
745
|
-
}
|
|
746
|
-
if (typeof candidate !== typeof existing) {
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
if (candidate === null || existing === null) {
|
|
750
|
-
return candidate === existing;
|
|
751
|
-
}
|
|
752
|
-
if (typeof candidate !== "object" || typeof existing !== "object") {
|
|
753
|
-
return isDeepEqual(candidate, existing);
|
|
754
|
-
}
|
|
755
|
-
const seenExisting = visited.get(candidate);
|
|
756
|
-
if (seenExisting?.has(existing)) {
|
|
757
|
-
return true;
|
|
758
|
-
}
|
|
759
|
-
if (seenExisting) {
|
|
760
|
-
seenExisting.add(existing);
|
|
761
|
-
} else {
|
|
762
|
-
visited.set(candidate, new WeakSet([existing]));
|
|
763
|
-
}
|
|
764
|
-
if (Array.isArray(candidate) || Array.isArray(existing)) {
|
|
765
|
-
if (!Array.isArray(candidate) || !Array.isArray(existing) || candidate.length !== existing.length) {
|
|
766
|
-
return false;
|
|
767
|
-
}
|
|
768
|
-
return candidate.every((item, index) => areSchemasEquivalentForReuse(
|
|
769
|
-
item,
|
|
770
|
-
existing[index],
|
|
771
|
-
candidateRootSchema,
|
|
772
|
-
existingRootSchema,
|
|
773
|
-
candidateSchemas,
|
|
774
|
-
existingSchemas,
|
|
775
|
-
candidateToExistingComponentNames,
|
|
776
|
-
existingToCandidateComponentNames,
|
|
777
|
-
visited
|
|
778
|
-
));
|
|
779
|
-
}
|
|
780
|
-
const candidateObject = candidate;
|
|
781
|
-
const existingObject = existing;
|
|
782
|
-
const candidateKeys = Object.keys(candidateObject).sort();
|
|
783
|
-
const existingKeys = Object.keys(existingObject).sort();
|
|
784
|
-
if (!isDeepEqual(candidateKeys, existingKeys)) {
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
return candidateKeys.every((key) => {
|
|
788
|
-
const candidateValue = candidateObject[key];
|
|
789
|
-
const existingValue = existingObject[key];
|
|
790
|
-
if (key === "$ref" && typeof candidateValue === "string" && typeof existingValue === "string") {
|
|
791
|
-
return areSchemaRefsEquivalentForReuse(
|
|
792
|
-
candidateValue,
|
|
793
|
-
existingValue,
|
|
794
|
-
candidateRootSchema,
|
|
795
|
-
existingRootSchema,
|
|
796
|
-
candidateSchemas,
|
|
797
|
-
existingSchemas,
|
|
798
|
-
candidateToExistingComponentNames,
|
|
799
|
-
existingToCandidateComponentNames,
|
|
800
|
-
visited
|
|
712
|
+
if (errors.length) {
|
|
713
|
+
throw new OpenAPIGeneratorError(
|
|
714
|
+
`[OpenAPIGenerator] Failed to generate the OpenAPI document (${errors.length} error${errors.length === 1 ? "" : "s"}):
|
|
715
|
+
|
|
716
|
+
${errors.join("\n\n")}`
|
|
801
717
|
);
|
|
802
718
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
existingValue,
|
|
806
|
-
candidateRootSchema,
|
|
807
|
-
existingRootSchema,
|
|
808
|
-
candidateSchemas,
|
|
809
|
-
existingSchemas,
|
|
810
|
-
candidateToExistingComponentNames,
|
|
811
|
-
existingToCandidateComponentNames,
|
|
812
|
-
visited
|
|
813
|
-
);
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
function parseComponentRefName(ref) {
|
|
817
|
-
if (!ref.startsWith("#/components/schemas/")) {
|
|
818
|
-
return void 0;
|
|
719
|
+
const versioned = toVersionedOpenAPIDocument(doc, version);
|
|
720
|
+
return this.serializer.serialize(versioned, { asFormData: false, useFormDataForBlobFields: false });
|
|
819
721
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
const localDefName = parseLocalDefRefName(ref);
|
|
824
|
-
if (localDefName !== void 0 && typeof rootSchema === "object" && rootSchema !== null) {
|
|
825
|
-
const localDef = rootSchema.$defs?.[localDefName];
|
|
826
|
-
if (localDef !== void 0) {
|
|
827
|
-
return {
|
|
828
|
-
schema: localDef,
|
|
829
|
-
rootSchema
|
|
830
|
-
};
|
|
831
|
-
}
|
|
722
|
+
convertSchema(schema, direction) {
|
|
723
|
+
const [jsonSchema, optional] = this.converter.convert(schema, direction);
|
|
724
|
+
return [hoistRecursiveRefToDef(strip$schemaField(jsonSchema)), optional];
|
|
832
725
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
if (componentSchema !== void 0) {
|
|
837
|
-
return {
|
|
838
|
-
schema: componentSchema,
|
|
839
|
-
rootSchema: componentSchema
|
|
840
|
-
};
|
|
726
|
+
convertSchemas(schemas, direction) {
|
|
727
|
+
if (!schemas || schemas.length <= 1) {
|
|
728
|
+
return this.convertSchema(schemas?.[0], direction);
|
|
841
729
|
}
|
|
730
|
+
const results = schemas.map((s) => this.convertSchema(s, direction));
|
|
731
|
+
return [
|
|
732
|
+
combineJsonSchemasWithComposition("allOf", results.map(([jsonSchema]) => jsonSchema)),
|
|
733
|
+
results.every(([, optional]) => optional)
|
|
734
|
+
];
|
|
842
735
|
}
|
|
843
|
-
return void 0;
|
|
844
736
|
}
|
|
845
|
-
function
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
if (candidateComponentName === void 0 !== (existingComponentName === void 0)) {
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
if (candidateComponentName !== void 0 && existingComponentName !== void 0) {
|
|
852
|
-
const mappedExisting = candidateToExistingComponentNames.get(candidateComponentName);
|
|
853
|
-
if (mappedExisting !== void 0 && mappedExisting !== existingComponentName) {
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
const mappedCandidate = existingToCandidateComponentNames.get(existingComponentName);
|
|
857
|
-
if (mappedCandidate !== void 0 && mappedCandidate !== candidateComponentName) {
|
|
858
|
-
return false;
|
|
859
|
-
}
|
|
860
|
-
candidateToExistingComponentNames.set(candidateComponentName, existingComponentName);
|
|
861
|
-
existingToCandidateComponentNames.set(existingComponentName, candidateComponentName);
|
|
862
|
-
}
|
|
863
|
-
const resolvedCandidate = resolveSchemaComparisonRef(candidateRef, candidateRootSchema, candidateSchemas);
|
|
864
|
-
const resolvedExisting = resolveSchemaComparisonRef(existingRef, existingRootSchema, existingSchemas);
|
|
865
|
-
if (resolvedCandidate === void 0 || resolvedExisting === void 0) {
|
|
866
|
-
return candidateRef === existingRef;
|
|
867
|
-
}
|
|
868
|
-
return areSchemasEquivalentForReuse(
|
|
869
|
-
resolvedCandidate.schema,
|
|
870
|
-
resolvedExisting.schema,
|
|
871
|
-
resolvedCandidate.rootSchema,
|
|
872
|
-
resolvedExisting.rootSchema,
|
|
873
|
-
candidateSchemas,
|
|
874
|
-
existingSchemas,
|
|
875
|
-
candidateToExistingComponentNames,
|
|
876
|
-
existingToCandidateComponentNames,
|
|
877
|
-
visited
|
|
878
|
-
);
|
|
737
|
+
function toVersionedOpenAPIDocument(doc, version) {
|
|
738
|
+
const downgraded = version.startsWith("3.2.") ? doc : version.startsWith("3.1.") ? downgradeSpecV32ToV31(doc) : downgradeSpecV31ToV30(downgradeSpecV32ToV31(doc));
|
|
739
|
+
return { ...downgraded, openapi: version };
|
|
879
740
|
}
|
|
880
|
-
function
|
|
881
|
-
if (
|
|
882
|
-
return
|
|
741
|
+
function strip$schemaField(schema) {
|
|
742
|
+
if (typeof schema !== "object") {
|
|
743
|
+
return schema;
|
|
883
744
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
function rewriteComponentSchemaRefs(schema, renameMap) {
|
|
887
|
-
return mapJsonSchemaRefs(schema, (ref) => {
|
|
888
|
-
const refName = parseLocalDefRefName(ref);
|
|
889
|
-
if (refName === void 0) {
|
|
890
|
-
return ref;
|
|
891
|
-
}
|
|
892
|
-
const renamedName = renameMap[refName];
|
|
893
|
-
if (renamedName === void 0) {
|
|
894
|
-
return ref;
|
|
895
|
-
}
|
|
896
|
-
return `#/components/schemas/${encodeJsonPointerSegment(renamedName)}`;
|
|
897
|
-
});
|
|
745
|
+
const { $schema, ...rest } = schema;
|
|
746
|
+
return rest;
|
|
898
747
|
}
|
|
899
748
|
|
|
900
749
|
function populateRouterContractOpenAPIPaths(router, options = {}) {
|
|
@@ -918,7 +767,7 @@ function populateRouterContractOpenAPIPaths(router, options = {}) {
|
|
|
918
767
|
return router;
|
|
919
768
|
}
|
|
920
769
|
const populated = {};
|
|
921
|
-
for (const key
|
|
770
|
+
for (const key of Object.keys(router)) {
|
|
922
771
|
populated[key] = populateRouterContractOpenAPIPaths(
|
|
923
772
|
router[key],
|
|
924
773
|
{ ...options, path: [...path, key] }
|
|
@@ -927,4 +776,4 @@ function populateRouterContractOpenAPIPaths(router, options = {}) {
|
|
|
927
776
|
return populated;
|
|
928
777
|
}
|
|
929
778
|
|
|
930
|
-
export { DEFAULT_OPENAPI_INPUT_STRUCTURE, DEFAULT_OPENAPI_METHOD, DEFAULT_OPENAPI_OUTPUT_STRUCTURE, DEFAULT_OPENAPI_SUCCESS_DESCRIPTION, OpenAPIGenerator, OpenAPIGeneratorError, OpenAPISerializer,
|
|
779
|
+
export { DEFAULT_OPENAPI_INPUT_STRUCTURE, DEFAULT_OPENAPI_METHOD, DEFAULT_OPENAPI_OUTPUT_STRUCTURE, DEFAULT_OPENAPI_SUCCESS_DESCRIPTION, OpenAPIGenerator, OpenAPIGeneratorError, OpenAPISerializer, createContractJsonifiedClientFactory, getDynamicPathParams, getOpenAPIMeta, isBodylessMethod, openapi, populateRouterContractOpenAPIPaths };
|