@orpc/openapi 0.0.0-next.7336c81 → 0.0.0-next.739ee37
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 +11 -7
- package/dist/adapters/aws-lambda/index.d.mts +20 -0
- package/dist/adapters/aws-lambda/index.d.ts +20 -0
- package/dist/adapters/aws-lambda/index.mjs +18 -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 +18 -0
- package/dist/adapters/fetch/index.d.mts +10 -4
- package/dist/adapters/fetch/index.d.ts +10 -4
- package/dist/adapters/fetch/index.mjs +2 -1
- package/dist/adapters/node/index.d.mts +10 -4
- package/dist/adapters/node/index.d.ts +10 -4
- package/dist/adapters/node/index.mjs +2 -1
- package/dist/adapters/standard/index.d.mts +8 -22
- package/dist/adapters/standard/index.d.ts +8 -22
- package/dist/adapters/standard/index.mjs +2 -1
- package/dist/index.d.mts +29 -15
- package/dist/index.d.ts +29 -15
- package/dist/index.mjs +34 -8
- package/dist/plugins/index.d.mts +26 -12
- package/dist/plugins/index.d.ts +26 -12
- package/dist/plugins/index.mjs +74 -23
- package/dist/shared/openapi.BfNjg7j9.d.mts +120 -0
- package/dist/shared/openapi.BfNjg7j9.d.ts +120 -0
- package/dist/shared/{openapi.p5tsmBXx.mjs → openapi.DIt-Z9W1.mjs} +45 -13
- package/dist/shared/{openapi.fMEQd3Yd.mjs → openapi.DrTcell5.mjs} +293 -87
- package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
- package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
- package/package.json +21 -11
- package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
- package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
- package/dist/shared/openapi.DP97kr00.d.mts +0 -47
- package/dist/shared/openapi.DP97kr00.d.ts +0 -47
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, StandardBracketNotationSerializer, StandardOpenAPISerializer } from '@orpc/openapi-client/standard';
|
|
2
2
|
import { StandardHandler } from '@orpc/server/standard';
|
|
3
|
+
import { isORPCErrorStatus } from '@orpc/client';
|
|
3
4
|
import { fallbackContractConfig } from '@orpc/contract';
|
|
4
|
-
import { isObject } from '@orpc/shared';
|
|
5
|
+
import { isObject, stringifyJSON, tryDecodeURIComponent, value } from '@orpc/shared';
|
|
5
6
|
import { toHttpPath } from '@orpc/client/standard';
|
|
6
7
|
import { traverseContractProcedures, isProcedure, getLazyMeta, unlazy, getRouter, createContractedProcedure } from '@orpc/server';
|
|
7
8
|
import { createRouter, addRoute, findRoute } from 'rou3';
|
|
8
9
|
|
|
9
10
|
class StandardOpenAPICodec {
|
|
10
|
-
constructor(serializer) {
|
|
11
|
+
constructor(serializer, options = {}) {
|
|
11
12
|
this.serializer = serializer;
|
|
13
|
+
this.customErrorResponseBodyEncoder = options.customErrorResponseBodyEncoder;
|
|
12
14
|
}
|
|
15
|
+
customErrorResponseBodyEncoder;
|
|
13
16
|
async decode(request, params, procedure) {
|
|
14
17
|
const inputStructure = fallbackContractConfig("defaultInputStructure", procedure["~orpc"].route.inputStructure);
|
|
15
18
|
if (inputStructure === "compact") {
|
|
@@ -52,38 +55,67 @@ class StandardOpenAPICodec {
|
|
|
52
55
|
body: this.serializer.serialize(output)
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
|
-
if (!
|
|
56
|
-
throw new Error(
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if (!this.#isDetailedOutput(output)) {
|
|
59
|
+
throw new Error(`
|
|
60
|
+
Invalid "detailed" output structure:
|
|
61
|
+
\u2022 Expected an object with optional properties:
|
|
62
|
+
- status (number 200-399)
|
|
63
|
+
- headers (Record<string, string | string[]>)
|
|
64
|
+
- body (any)
|
|
65
|
+
\u2022 No extra keys allowed.
|
|
66
|
+
|
|
67
|
+
Actual value:
|
|
68
|
+
${stringifyJSON(output)}
|
|
69
|
+
`);
|
|
59
70
|
}
|
|
60
71
|
return {
|
|
61
|
-
status: successStatus,
|
|
72
|
+
status: output.status ?? successStatus,
|
|
62
73
|
headers: output.headers ?? {},
|
|
63
74
|
body: this.serializer.serialize(output.body)
|
|
64
75
|
};
|
|
65
76
|
}
|
|
66
77
|
encodeError(error) {
|
|
78
|
+
const body = this.customErrorResponseBodyEncoder?.(error) ?? error.toJSON();
|
|
67
79
|
return {
|
|
68
80
|
status: error.status,
|
|
69
81
|
headers: {},
|
|
70
|
-
body: this.serializer.serialize(
|
|
82
|
+
body: this.serializer.serialize(body, { outputFormat: "plain" })
|
|
71
83
|
};
|
|
72
84
|
}
|
|
85
|
+
#isDetailedOutput(output) {
|
|
86
|
+
if (!isObject(output)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (output.headers && !isObject(output.headers)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (output.status !== void 0 && (typeof output.status !== "number" || !Number.isInteger(output.status) || isORPCErrorStatus(output.status))) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
73
97
|
}
|
|
74
98
|
|
|
75
99
|
function toRou3Pattern(path) {
|
|
76
100
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/**:$1").replace(/\/\{([^}]+)\}/g, "/:$1");
|
|
77
101
|
}
|
|
78
102
|
function decodeParams(params) {
|
|
79
|
-
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key,
|
|
103
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, tryDecodeURIComponent(value)]));
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
class StandardOpenAPIMatcher {
|
|
107
|
+
filter;
|
|
83
108
|
tree = createRouter();
|
|
84
109
|
pendingRouters = [];
|
|
110
|
+
constructor(options = {}) {
|
|
111
|
+
this.filter = options.filter ?? true;
|
|
112
|
+
}
|
|
85
113
|
init(router, path = []) {
|
|
86
|
-
const laziedOptions = traverseContractProcedures({ router, path }, (
|
|
114
|
+
const laziedOptions = traverseContractProcedures({ router, path }, (traverseOptions) => {
|
|
115
|
+
if (!value(this.filter, traverseOptions)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const { path: path2, contract } = traverseOptions;
|
|
87
119
|
const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
|
|
88
120
|
const httpPath = toRou3Pattern(contract["~orpc"].route.path ?? toHttpPath(path2));
|
|
89
121
|
if (isProcedure(contract)) {
|
|
@@ -147,10 +179,10 @@ class StandardOpenAPIMatcher {
|
|
|
147
179
|
class StandardOpenAPIHandler extends StandardHandler {
|
|
148
180
|
constructor(router, options) {
|
|
149
181
|
const jsonSerializer = new StandardOpenAPIJsonSerializer(options);
|
|
150
|
-
const bracketNotationSerializer = new StandardBracketNotationSerializer();
|
|
182
|
+
const bracketNotationSerializer = new StandardBracketNotationSerializer(options);
|
|
151
183
|
const serializer = new StandardOpenAPISerializer(jsonSerializer, bracketNotationSerializer);
|
|
152
|
-
const matcher = new StandardOpenAPIMatcher();
|
|
153
|
-
const codec = new StandardOpenAPICodec(serializer);
|
|
184
|
+
const matcher = new StandardOpenAPIMatcher(options);
|
|
185
|
+
const codec = new StandardOpenAPICodec(serializer, options);
|
|
154
186
|
super(router, matcher, codec, options);
|
|
155
187
|
}
|
|
156
188
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
|
|
1
|
+
import { isORPCErrorStatus, fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
|
|
2
2
|
import { toHttpPath } from '@orpc/client/standard';
|
|
3
3
|
import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
|
|
4
4
|
import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
|
|
5
5
|
import { isProcedure, resolveContractProcedures } from '@orpc/server';
|
|
6
|
-
import { isObject, findDeepMatches, toArray, clone } from '@orpc/shared';
|
|
7
|
-
import 'json-schema-typed/draft-2020-12';
|
|
6
|
+
import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
|
|
7
|
+
import { TypeName } from '@orpc/interop/json-schema-typed/draft-2020-12';
|
|
8
8
|
|
|
9
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
10
10
|
function customOpenAPIOperation(o, extend) {
|
|
@@ -114,13 +114,18 @@ function isAnySchema(schema) {
|
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
116
116
|
function separateObjectSchema(schema, separatedProperties) {
|
|
117
|
-
if (Object.keys(schema).some(
|
|
117
|
+
if (Object.keys(schema).some(
|
|
118
|
+
(k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
|
|
119
|
+
)) {
|
|
118
120
|
return [{ type: "object" }, schema];
|
|
119
121
|
}
|
|
120
122
|
const matched = { ...schema };
|
|
121
123
|
const rest = { ...schema };
|
|
122
|
-
matched.properties =
|
|
123
|
-
|
|
124
|
+
matched.properties = separatedProperties.reduce((acc, key) => {
|
|
125
|
+
const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
|
|
126
|
+
if (keySchema !== void 0) {
|
|
127
|
+
acc[key] = keySchema;
|
|
128
|
+
}
|
|
124
129
|
return acc;
|
|
125
130
|
}, {});
|
|
126
131
|
matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
|
|
@@ -184,6 +189,57 @@ function applySchemaOptionality(required, schema) {
|
|
|
184
189
|
]
|
|
185
190
|
};
|
|
186
191
|
}
|
|
192
|
+
function expandUnionSchema(schema) {
|
|
193
|
+
if (typeof schema === "object") {
|
|
194
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
195
|
+
if (schema[keyword] && Object.keys(schema).every(
|
|
196
|
+
(k) => k === keyword || !LOGIC_KEYWORDS.includes(k)
|
|
197
|
+
)) {
|
|
198
|
+
return schema[keyword].flatMap((s) => expandUnionSchema(s));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [schema];
|
|
203
|
+
}
|
|
204
|
+
function expandArrayableSchema(schema) {
|
|
205
|
+
const schemas = expandUnionSchema(schema);
|
|
206
|
+
if (schemas.length !== 2) {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
const arraySchema = schemas.find(
|
|
210
|
+
(s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
|
|
211
|
+
);
|
|
212
|
+
if (arraySchema === void 0) {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
const items1 = arraySchema.items;
|
|
216
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
217
|
+
if (stringifyJSON(items1) !== stringifyJSON(items2)) {
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
return [items2, arraySchema];
|
|
221
|
+
}
|
|
222
|
+
const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
223
|
+
TypeName.String,
|
|
224
|
+
TypeName.Number,
|
|
225
|
+
TypeName.Integer,
|
|
226
|
+
TypeName.Boolean,
|
|
227
|
+
TypeName.Null
|
|
228
|
+
]);
|
|
229
|
+
function isPrimitiveSchema(schema) {
|
|
230
|
+
return expandUnionSchema(schema).every((s) => {
|
|
231
|
+
if (typeof s === "boolean") {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (s.const !== void 0) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
187
243
|
|
|
188
244
|
function toOpenAPIPath(path) {
|
|
189
245
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
@@ -256,13 +312,26 @@ function toOpenAPIParameters(schema, parameterIn) {
|
|
|
256
312
|
const parameters = [];
|
|
257
313
|
for (const key in schema.properties) {
|
|
258
314
|
const keySchema = schema.properties[key];
|
|
315
|
+
let isDeepObjectStyle = true;
|
|
316
|
+
if (parameterIn !== "query") {
|
|
317
|
+
isDeepObjectStyle = false;
|
|
318
|
+
} else if (isPrimitiveSchema(keySchema)) {
|
|
319
|
+
isDeepObjectStyle = false;
|
|
320
|
+
} else {
|
|
321
|
+
const [item] = expandArrayableSchema(keySchema) ?? [];
|
|
322
|
+
if (item !== void 0 && isPrimitiveSchema(item)) {
|
|
323
|
+
isDeepObjectStyle = false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
259
326
|
parameters.push({
|
|
260
327
|
name: key,
|
|
261
328
|
in: parameterIn,
|
|
262
329
|
required: schema.required?.includes(key),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
330
|
+
schema: toOpenAPISchema(keySchema),
|
|
331
|
+
style: isDeepObjectStyle ? "deepObject" : void 0,
|
|
332
|
+
explode: isDeepObjectStyle ? true : void 0,
|
|
333
|
+
allowEmptyValue: parameterIn === "query" ? true : void 0,
|
|
334
|
+
allowReserved: parameterIn === "query" ? true : void 0
|
|
266
335
|
});
|
|
267
336
|
}
|
|
268
337
|
return parameters;
|
|
@@ -281,6 +350,15 @@ function checkParamsSchema(schema, params) {
|
|
|
281
350
|
function toOpenAPISchema(schema) {
|
|
282
351
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
283
352
|
}
|
|
353
|
+
const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
354
|
+
function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
355
|
+
if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
|
|
356
|
+
return schema;
|
|
357
|
+
}
|
|
358
|
+
const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
|
|
359
|
+
const resolved = doc.components?.schemas?.[name];
|
|
360
|
+
return resolved ?? schema;
|
|
361
|
+
}
|
|
284
362
|
|
|
285
363
|
class CompositeSchemaConverter {
|
|
286
364
|
converters;
|
|
@@ -311,33 +389,48 @@ class OpenAPIGenerator {
|
|
|
311
389
|
*
|
|
312
390
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
313
391
|
*/
|
|
314
|
-
async generate(router,
|
|
392
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
393
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
394
|
+
return !(exclude?.(contract, path) ?? false);
|
|
395
|
+
});
|
|
315
396
|
const doc = {
|
|
316
|
-
...clone(
|
|
317
|
-
info:
|
|
397
|
+
...clone(baseDoc),
|
|
398
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
318
399
|
openapi: "3.1.1"
|
|
319
400
|
};
|
|
401
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
320
402
|
const contracts = [];
|
|
321
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
322
|
-
|
|
403
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
404
|
+
if (!value(filter, traverseOptions)) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
contracts.push(traverseOptions);
|
|
323
408
|
});
|
|
324
409
|
const errors = [];
|
|
325
410
|
for (const { contract, path } of contracts) {
|
|
326
|
-
const
|
|
411
|
+
const stringPath = path.join(".");
|
|
327
412
|
try {
|
|
328
413
|
const def = contract["~orpc"];
|
|
329
414
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
330
415
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
416
|
+
let operationObjectRef;
|
|
417
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
418
|
+
operationObjectRef = def.route.spec;
|
|
419
|
+
} else {
|
|
420
|
+
operationObjectRef = {
|
|
421
|
+
operationId: def.route.operationId ?? stringPath,
|
|
422
|
+
summary: def.route.summary,
|
|
423
|
+
description: def.route.description,
|
|
424
|
+
deprecated: def.route.deprecated,
|
|
425
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
426
|
+
};
|
|
427
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
428
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
429
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
|
|
430
|
+
}
|
|
431
|
+
if (typeof def.route.spec === "function") {
|
|
432
|
+
operationObjectRef = def.route.spec(operationObjectRef);
|
|
433
|
+
}
|
|
341
434
|
doc.paths ??= {};
|
|
342
435
|
doc.paths[httpPath] ??= {};
|
|
343
436
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -346,7 +439,7 @@ class OpenAPIGenerator {
|
|
|
346
439
|
throw e;
|
|
347
440
|
}
|
|
348
441
|
errors.push(
|
|
349
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
442
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
350
443
|
${e.message}`
|
|
351
444
|
);
|
|
352
445
|
}
|
|
@@ -360,22 +453,96 @@ ${errors.join("\n\n")}`
|
|
|
360
453
|
}
|
|
361
454
|
return this.serializer.serialize(doc)[0];
|
|
362
455
|
}
|
|
363
|
-
async #
|
|
456
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
457
|
+
let undefinedErrorJsonSchema = {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {
|
|
460
|
+
defined: { const: false },
|
|
461
|
+
code: { type: "string" },
|
|
462
|
+
status: { type: "number" },
|
|
463
|
+
message: { type: "string" },
|
|
464
|
+
data: {}
|
|
465
|
+
},
|
|
466
|
+
required: ["defined", "code", "status", "message"]
|
|
467
|
+
};
|
|
468
|
+
const baseSchemaConvertOptions = {};
|
|
469
|
+
if (commonSchemas) {
|
|
470
|
+
baseSchemaConvertOptions.components = [];
|
|
471
|
+
for (const key in commonSchemas) {
|
|
472
|
+
const options = commonSchemas[key];
|
|
473
|
+
if (options.schema === void 0) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const { schema, strategy = "input" } = options;
|
|
477
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
478
|
+
const allowedStrategies = [strategy];
|
|
479
|
+
if (strategy === "input") {
|
|
480
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
481
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
482
|
+
allowedStrategies.push("output");
|
|
483
|
+
}
|
|
484
|
+
} else if (strategy === "output") {
|
|
485
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
486
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
487
|
+
allowedStrategies.push("input");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
baseSchemaConvertOptions.components.push({
|
|
491
|
+
schema,
|
|
492
|
+
required,
|
|
493
|
+
ref: `#/components/schemas/${key}`,
|
|
494
|
+
allowedStrategies
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
doc.components ??= {};
|
|
498
|
+
doc.components.schemas ??= {};
|
|
499
|
+
for (const key in commonSchemas) {
|
|
500
|
+
const options = commonSchemas[key];
|
|
501
|
+
if (options.schema === void 0) {
|
|
502
|
+
if (options.error === "UndefinedError") {
|
|
503
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
504
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
505
|
+
}
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const { schema, strategy = "input" } = options;
|
|
509
|
+
const [, json] = await this.converter.convert(
|
|
510
|
+
schema,
|
|
511
|
+
{
|
|
512
|
+
...baseSchemaConvertOptions,
|
|
513
|
+
strategy,
|
|
514
|
+
minStructureDepthForRef: 1
|
|
515
|
+
// not allow use $ref for root schemas
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
522
|
+
}
|
|
523
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
364
524
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
365
525
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
366
526
|
if (details) {
|
|
367
527
|
ref.requestBody = {
|
|
368
528
|
required: true,
|
|
369
529
|
content: toOpenAPIEventIteratorContent(
|
|
370
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
371
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
530
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
531
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
372
532
|
)
|
|
373
533
|
};
|
|
374
534
|
return;
|
|
375
535
|
}
|
|
376
536
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
377
537
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
378
|
-
let [required, schema] = await this.converter.convert(
|
|
538
|
+
let [required, schema] = await this.converter.convert(
|
|
539
|
+
def.inputSchema,
|
|
540
|
+
{
|
|
541
|
+
...baseSchemaConvertOptions,
|
|
542
|
+
strategy: "input",
|
|
543
|
+
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
544
|
+
}
|
|
545
|
+
);
|
|
379
546
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
380
547
|
return;
|
|
381
548
|
}
|
|
@@ -397,13 +564,14 @@ ${errors.join("\n\n")}`
|
|
|
397
564
|
ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
|
|
398
565
|
}
|
|
399
566
|
if (method === "GET") {
|
|
400
|
-
|
|
567
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, schema);
|
|
568
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
401
569
|
throw new OpenAPIGeneratorError(
|
|
402
570
|
'When method is "GET", input schema must satisfy: object | any | unknown'
|
|
403
571
|
);
|
|
404
572
|
}
|
|
405
573
|
ref.parameters ??= [];
|
|
406
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
574
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
|
|
407
575
|
} else {
|
|
408
576
|
ref.requestBody = {
|
|
409
577
|
required,
|
|
@@ -418,7 +586,8 @@ ${errors.join("\n\n")}`
|
|
|
418
586
|
if (!isObjectSchema(schema)) {
|
|
419
587
|
throw error;
|
|
420
588
|
}
|
|
421
|
-
|
|
589
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
|
|
590
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
422
591
|
throw new OpenAPIGeneratorError(
|
|
423
592
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
424
593
|
);
|
|
@@ -426,12 +595,13 @@ ${errors.join("\n\n")}`
|
|
|
426
595
|
for (const from of ["params", "query", "headers"]) {
|
|
427
596
|
const fromSchema = schema.properties?.[from];
|
|
428
597
|
if (fromSchema !== void 0) {
|
|
429
|
-
|
|
598
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
599
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
430
600
|
throw error;
|
|
431
601
|
}
|
|
432
602
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
433
603
|
ref.parameters ??= [];
|
|
434
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
604
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
435
605
|
}
|
|
436
606
|
}
|
|
437
607
|
if (schema.properties?.body !== void 0) {
|
|
@@ -441,7 +611,7 @@ ${errors.join("\n\n")}`
|
|
|
441
611
|
};
|
|
442
612
|
}
|
|
443
613
|
}
|
|
444
|
-
async #successResponse(ref, def) {
|
|
614
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
445
615
|
const outputSchema = def.outputSchema;
|
|
446
616
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
447
617
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -452,88 +622,124 @@ ${errors.join("\n\n")}`
|
|
|
452
622
|
ref.responses[status] = {
|
|
453
623
|
description,
|
|
454
624
|
content: toOpenAPIEventIteratorContent(
|
|
455
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
456
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
625
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
626
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
457
627
|
)
|
|
458
628
|
};
|
|
459
629
|
return;
|
|
460
630
|
}
|
|
461
|
-
const [required, json] = await this.converter.convert(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
631
|
+
const [required, json] = await this.converter.convert(
|
|
632
|
+
outputSchema,
|
|
633
|
+
{
|
|
634
|
+
...baseSchemaConvertOptions,
|
|
635
|
+
strategy: "output",
|
|
636
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
637
|
+
}
|
|
638
|
+
);
|
|
466
639
|
if (outputStructure === "compact") {
|
|
640
|
+
ref.responses ??= {};
|
|
641
|
+
ref.responses[status] = {
|
|
642
|
+
description
|
|
643
|
+
};
|
|
467
644
|
ref.responses[status].content = toOpenAPIContent(applySchemaOptionality(required, json));
|
|
468
645
|
return;
|
|
469
646
|
}
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
647
|
+
const handledStatuses = /* @__PURE__ */ new Set();
|
|
648
|
+
for (const item of expandUnionSchema(json)) {
|
|
649
|
+
const error = new OpenAPIGeneratorError(`
|
|
650
|
+
When output structure is "detailed", output schema must satisfy:
|
|
651
|
+
{
|
|
652
|
+
status?: number, // must be a literal number and in the range of 200-399
|
|
653
|
+
headers?: Record<string, unknown>,
|
|
654
|
+
body?: unknown
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
But got: ${stringifyJSON(item)}
|
|
658
|
+
`);
|
|
659
|
+
if (!isObjectSchema(item)) {
|
|
478
660
|
throw error;
|
|
479
661
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
662
|
+
let schemaStatus;
|
|
663
|
+
let schemaDescription;
|
|
664
|
+
if (item.properties?.status !== void 0) {
|
|
665
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
|
|
666
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
667
|
+
throw error;
|
|
668
|
+
}
|
|
669
|
+
schemaStatus = statusSchema.const;
|
|
670
|
+
schemaDescription = statusSchema.description;
|
|
671
|
+
}
|
|
672
|
+
const itemStatus = schemaStatus ?? status;
|
|
673
|
+
const itemDescription = schemaDescription ?? description;
|
|
674
|
+
if (handledStatuses.has(itemStatus)) {
|
|
675
|
+
throw new OpenAPIGeneratorError(`
|
|
676
|
+
When output structure is "detailed", each success status must be unique.
|
|
677
|
+
But got status: ${itemStatus} used more than once.
|
|
678
|
+
`);
|
|
679
|
+
}
|
|
680
|
+
handledStatuses.add(itemStatus);
|
|
681
|
+
ref.responses ??= {};
|
|
682
|
+
ref.responses[itemStatus] = {
|
|
683
|
+
description: itemDescription
|
|
684
|
+
};
|
|
685
|
+
if (item.properties?.headers !== void 0) {
|
|
686
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
687
|
+
if (!isObjectSchema(headersSchema)) {
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
for (const key in headersSchema.properties) {
|
|
691
|
+
const headerSchema = headersSchema.properties[key];
|
|
692
|
+
if (headerSchema !== void 0) {
|
|
693
|
+
ref.responses[itemStatus].headers ??= {};
|
|
694
|
+
ref.responses[itemStatus].headers[key] = {
|
|
695
|
+
schema: toOpenAPISchema(headerSchema),
|
|
696
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (item.properties?.body !== void 0) {
|
|
702
|
+
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
703
|
+
applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
|
|
704
|
+
);
|
|
486
705
|
}
|
|
487
|
-
}
|
|
488
|
-
if (json.properties?.body !== void 0) {
|
|
489
|
-
ref.responses[status].content = toOpenAPIContent(
|
|
490
|
-
applySchemaOptionality(json.required?.includes("body") ?? false, json.properties.body)
|
|
491
|
-
);
|
|
492
706
|
}
|
|
493
707
|
}
|
|
494
|
-
async #errorResponse(ref, def) {
|
|
708
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
495
709
|
const errorMap = def.errorMap;
|
|
496
|
-
const
|
|
710
|
+
const errorResponsesByStatus = {};
|
|
497
711
|
for (const code in errorMap) {
|
|
498
712
|
const config = errorMap[code];
|
|
499
713
|
if (!config) {
|
|
500
714
|
continue;
|
|
501
715
|
}
|
|
502
716
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
717
|
+
const defaultMessage = fallbackORPCErrorMessage(code, config.message);
|
|
718
|
+
errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
|
|
719
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
720
|
+
errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
|
|
721
|
+
errorResponsesByStatus[status].errorSchemaVariants.push({
|
|
507
722
|
type: "object",
|
|
508
723
|
properties: {
|
|
509
724
|
defined: { const: true },
|
|
510
725
|
code: { const: code },
|
|
511
726
|
status: { const: status },
|
|
512
|
-
message: { type: "string", default:
|
|
727
|
+
message: { type: "string", default: defaultMessage },
|
|
513
728
|
data: dataSchema
|
|
514
729
|
},
|
|
515
730
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
516
731
|
});
|
|
517
732
|
}
|
|
518
733
|
ref.responses ??= {};
|
|
519
|
-
for (const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
734
|
+
for (const statusString in errorResponsesByStatus) {
|
|
735
|
+
const errorResponse = errorResponsesByStatus[statusString];
|
|
736
|
+
const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
|
|
737
|
+
ref.responses[statusString] = {
|
|
738
|
+
description: statusString,
|
|
739
|
+
content: toOpenAPIContent(customBodySchema ?? {
|
|
524
740
|
oneOf: [
|
|
525
|
-
...
|
|
526
|
-
|
|
527
|
-
type: "object",
|
|
528
|
-
properties: {
|
|
529
|
-
defined: { const: false },
|
|
530
|
-
code: { type: "string" },
|
|
531
|
-
status: { type: "number" },
|
|
532
|
-
message: { type: "string" },
|
|
533
|
-
data: {}
|
|
534
|
-
},
|
|
535
|
-
required: ["defined", "code", "status", "message"]
|
|
536
|
-
}
|
|
741
|
+
...errorResponse.errorSchemaVariants,
|
|
742
|
+
undefinedErrorSchema
|
|
537
743
|
]
|
|
538
744
|
})
|
|
539
745
|
};
|
|
@@ -541,4 +747,4 @@ ${errors.join("\n\n")}`
|
|
|
541
747
|
}
|
|
542
748
|
}
|
|
543
749
|
|
|
544
|
-
export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, separateObjectSchema as s, toOpenAPIPath as t };
|
|
750
|
+
export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, resolveOpenAPIJsonSchemaRef as r, separateObjectSchema as s, toOpenAPIPath as t };
|