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