@orpc/openapi 0.0.0-next.a09e9be → 0.0.0-next.a0ab4bf
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 +5 -7
- package/dist/adapters/aws-lambda/index.d.mts +19 -0
- package/dist/adapters/aws-lambda/index.d.ts +19 -0
- package/dist/adapters/aws-lambda/index.mjs +18 -0
- package/dist/adapters/fetch/index.d.mts +3 -1
- package/dist/adapters/fetch/index.d.ts +3 -1
- package/dist/adapters/fetch/index.mjs +2 -1
- package/dist/adapters/node/index.d.mts +3 -1
- package/dist/adapters/node/index.d.ts +3 -1
- package/dist/adapters/node/index.mjs +2 -1
- package/dist/adapters/standard/index.d.mts +6 -11
- package/dist/adapters/standard/index.d.ts +6 -11
- package/dist/adapters/standard/index.mjs +2 -1
- package/dist/index.d.mts +23 -12
- package/dist/index.d.ts +23 -12
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +9 -10
- package/dist/plugins/index.d.ts +9 -10
- package/dist/plugins/index.mjs +1 -1
- package/dist/shared/{openapi.p5tsmBXx.mjs → openapi.BkBlt1Qf.mjs} +38 -9
- package/dist/shared/{openapi.fMEQd3Yd.mjs → openapi.BtoY8ZFF.mjs} +265 -67
- package/dist/shared/openapi.CQmjvnb0.d.mts +31 -0
- package/dist/shared/openapi.CQmjvnb0.d.ts +31 -0
- package/dist/shared/openapi.DPAN3GVs.d.mts +108 -0
- package/dist/shared/openapi.DPAN3GVs.d.ts +108 -0
- package/package.json +14 -10
- 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,7 +1,8 @@
|
|
|
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, 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';
|
|
@@ -52,13 +53,21 @@ class StandardOpenAPICodec {
|
|
|
52
53
|
body: this.serializer.serialize(output)
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
|
-
if (!
|
|
56
|
-
throw new Error(
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (!this.#isDetailedOutput(output)) {
|
|
57
|
+
throw new Error(`
|
|
58
|
+
Invalid "detailed" output structure:
|
|
59
|
+
\u2022 Expected an object with optional properties:
|
|
60
|
+
- status (number 200-399)
|
|
61
|
+
- headers (Record<string, string | string[]>)
|
|
62
|
+
- body (any)
|
|
63
|
+
\u2022 No extra keys allowed.
|
|
64
|
+
|
|
65
|
+
Actual value:
|
|
66
|
+
${stringifyJSON(output)}
|
|
67
|
+
`);
|
|
59
68
|
}
|
|
60
69
|
return {
|
|
61
|
-
status: successStatus,
|
|
70
|
+
status: output.status ?? successStatus,
|
|
62
71
|
headers: output.headers ?? {},
|
|
63
72
|
body: this.serializer.serialize(output.body)
|
|
64
73
|
};
|
|
@@ -70,6 +79,18 @@ class StandardOpenAPICodec {
|
|
|
70
79
|
body: this.serializer.serialize(error.toJSON(), { outputFormat: "plain" })
|
|
71
80
|
};
|
|
72
81
|
}
|
|
82
|
+
#isDetailedOutput(output) {
|
|
83
|
+
if (!isObject(output)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (output.headers && !isObject(output.headers)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (output.status !== void 0 && (typeof output.status !== "number" || !Number.isInteger(output.status) || isORPCErrorStatus(output.status))) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
function toRou3Pattern(path) {
|
|
@@ -80,10 +101,18 @@ function decodeParams(params) {
|
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
class StandardOpenAPIMatcher {
|
|
104
|
+
filter;
|
|
83
105
|
tree = createRouter();
|
|
84
106
|
pendingRouters = [];
|
|
107
|
+
constructor(options = {}) {
|
|
108
|
+
this.filter = options.filter ?? true;
|
|
109
|
+
}
|
|
85
110
|
init(router, path = []) {
|
|
86
|
-
const laziedOptions = traverseContractProcedures({ router, path }, (
|
|
111
|
+
const laziedOptions = traverseContractProcedures({ router, path }, (traverseOptions) => {
|
|
112
|
+
if (!value(this.filter, traverseOptions)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const { path: path2, contract } = traverseOptions;
|
|
87
116
|
const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
|
|
88
117
|
const httpPath = toRou3Pattern(contract["~orpc"].route.path ?? toHttpPath(path2));
|
|
89
118
|
if (isProcedure(contract)) {
|
|
@@ -147,9 +176,9 @@ class StandardOpenAPIMatcher {
|
|
|
147
176
|
class StandardOpenAPIHandler extends StandardHandler {
|
|
148
177
|
constructor(router, options) {
|
|
149
178
|
const jsonSerializer = new StandardOpenAPIJsonSerializer(options);
|
|
150
|
-
const bracketNotationSerializer = new StandardBracketNotationSerializer();
|
|
179
|
+
const bracketNotationSerializer = new StandardBracketNotationSerializer(options);
|
|
151
180
|
const serializer = new StandardOpenAPISerializer(jsonSerializer, bracketNotationSerializer);
|
|
152
|
-
const matcher = new StandardOpenAPIMatcher();
|
|
181
|
+
const matcher = new StandardOpenAPIMatcher(options);
|
|
153
182
|
const codec = new StandardOpenAPICodec(serializer);
|
|
154
183
|
super(router, matcher, codec, options);
|
|
155
184
|
}
|
|
@@ -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 'json-schema-typed/draft-2020-12';
|
|
8
8
|
|
|
9
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
10
10
|
function customOpenAPIOperation(o, extend) {
|
|
@@ -184,6 +184,57 @@ function applySchemaOptionality(required, schema) {
|
|
|
184
184
|
]
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
+
function expandUnionSchema(schema) {
|
|
188
|
+
if (typeof schema === "object") {
|
|
189
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
190
|
+
if (schema[keyword] && Object.keys(schema).every(
|
|
191
|
+
(k) => k === keyword || !LOGIC_KEYWORDS.includes(k)
|
|
192
|
+
)) {
|
|
193
|
+
return schema[keyword].flatMap((s) => expandUnionSchema(s));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return [schema];
|
|
198
|
+
}
|
|
199
|
+
function expandArrayableSchema(schema) {
|
|
200
|
+
const schemas = expandUnionSchema(schema);
|
|
201
|
+
if (schemas.length !== 2) {
|
|
202
|
+
return void 0;
|
|
203
|
+
}
|
|
204
|
+
const arraySchema = schemas.find(
|
|
205
|
+
(s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
|
|
206
|
+
);
|
|
207
|
+
if (arraySchema === void 0) {
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
const items1 = arraySchema.items;
|
|
211
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
212
|
+
if (stringifyJSON(items1) !== stringifyJSON(items2)) {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
return [items2, arraySchema];
|
|
216
|
+
}
|
|
217
|
+
const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
218
|
+
TypeName.String,
|
|
219
|
+
TypeName.Number,
|
|
220
|
+
TypeName.Integer,
|
|
221
|
+
TypeName.Boolean,
|
|
222
|
+
TypeName.Null
|
|
223
|
+
]);
|
|
224
|
+
function isPrimitiveSchema(schema) {
|
|
225
|
+
return expandUnionSchema(schema).every((s) => {
|
|
226
|
+
if (typeof s === "boolean") {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (s.const !== void 0) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
187
238
|
|
|
188
239
|
function toOpenAPIPath(path) {
|
|
189
240
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
@@ -256,13 +307,26 @@ function toOpenAPIParameters(schema, parameterIn) {
|
|
|
256
307
|
const parameters = [];
|
|
257
308
|
for (const key in schema.properties) {
|
|
258
309
|
const keySchema = schema.properties[key];
|
|
310
|
+
let isDeepObjectStyle = true;
|
|
311
|
+
if (parameterIn !== "query") {
|
|
312
|
+
isDeepObjectStyle = false;
|
|
313
|
+
} else if (isPrimitiveSchema(keySchema)) {
|
|
314
|
+
isDeepObjectStyle = false;
|
|
315
|
+
} else {
|
|
316
|
+
const [item] = expandArrayableSchema(keySchema) ?? [];
|
|
317
|
+
if (item !== void 0 && isPrimitiveSchema(item)) {
|
|
318
|
+
isDeepObjectStyle = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
259
321
|
parameters.push({
|
|
260
322
|
name: key,
|
|
261
323
|
in: parameterIn,
|
|
262
324
|
required: schema.required?.includes(key),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
325
|
+
schema: toOpenAPISchema(keySchema),
|
|
326
|
+
style: isDeepObjectStyle ? "deepObject" : void 0,
|
|
327
|
+
explode: isDeepObjectStyle ? true : void 0,
|
|
328
|
+
allowEmptyValue: parameterIn === "query" ? true : void 0,
|
|
329
|
+
allowReserved: parameterIn === "query" ? true : void 0
|
|
266
330
|
});
|
|
267
331
|
}
|
|
268
332
|
return parameters;
|
|
@@ -281,6 +345,15 @@ function checkParamsSchema(schema, params) {
|
|
|
281
345
|
function toOpenAPISchema(schema) {
|
|
282
346
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
283
347
|
}
|
|
348
|
+
const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
349
|
+
function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
350
|
+
if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
|
|
351
|
+
return schema;
|
|
352
|
+
}
|
|
353
|
+
const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
|
|
354
|
+
const resolved = doc.components?.schemas?.[name];
|
|
355
|
+
return resolved ?? schema;
|
|
356
|
+
}
|
|
284
357
|
|
|
285
358
|
class CompositeSchemaConverter {
|
|
286
359
|
converters;
|
|
@@ -312,14 +385,24 @@ class OpenAPIGenerator {
|
|
|
312
385
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
313
386
|
*/
|
|
314
387
|
async generate(router, options = {}) {
|
|
388
|
+
const filter = options.filter ?? (({ contract, path }) => {
|
|
389
|
+
return !(options.exclude?.(contract, path) ?? false);
|
|
390
|
+
});
|
|
315
391
|
const doc = {
|
|
316
392
|
...clone(options),
|
|
317
393
|
info: options.info ?? { title: "API Reference", version: "0.0.0" },
|
|
318
|
-
openapi: "3.1.1"
|
|
394
|
+
openapi: "3.1.1",
|
|
395
|
+
exclude: void 0,
|
|
396
|
+
filter: void 0,
|
|
397
|
+
commonSchemas: void 0
|
|
319
398
|
};
|
|
399
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
|
|
320
400
|
const contracts = [];
|
|
321
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
322
|
-
|
|
401
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
402
|
+
if (!value(filter, traverseOptions)) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
contracts.push(traverseOptions);
|
|
323
406
|
});
|
|
324
407
|
const errors = [];
|
|
325
408
|
for (const { contract, path } of contracts) {
|
|
@@ -328,16 +411,21 @@ class OpenAPIGenerator {
|
|
|
328
411
|
const def = contract["~orpc"];
|
|
329
412
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
330
413
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
414
|
+
let operationObjectRef;
|
|
415
|
+
if (def.route.spec !== void 0) {
|
|
416
|
+
operationObjectRef = def.route.spec;
|
|
417
|
+
} else {
|
|
418
|
+
operationObjectRef = {
|
|
419
|
+
operationId,
|
|
420
|
+
summary: def.route.summary,
|
|
421
|
+
description: def.route.description,
|
|
422
|
+
deprecated: def.route.deprecated,
|
|
423
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
424
|
+
};
|
|
425
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
426
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
427
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
|
|
428
|
+
}
|
|
341
429
|
doc.paths ??= {};
|
|
342
430
|
doc.paths[httpPath] ??= {};
|
|
343
431
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -360,22 +448,96 @@ ${errors.join("\n\n")}`
|
|
|
360
448
|
}
|
|
361
449
|
return this.serializer.serialize(doc)[0];
|
|
362
450
|
}
|
|
363
|
-
async #
|
|
451
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
452
|
+
let undefinedErrorJsonSchema = {
|
|
453
|
+
type: "object",
|
|
454
|
+
properties: {
|
|
455
|
+
defined: { const: false },
|
|
456
|
+
code: { type: "string" },
|
|
457
|
+
status: { type: "number" },
|
|
458
|
+
message: { type: "string" },
|
|
459
|
+
data: {}
|
|
460
|
+
},
|
|
461
|
+
required: ["defined", "code", "status", "message"]
|
|
462
|
+
};
|
|
463
|
+
const baseSchemaConvertOptions = {};
|
|
464
|
+
if (commonSchemas) {
|
|
465
|
+
baseSchemaConvertOptions.components = [];
|
|
466
|
+
for (const key in commonSchemas) {
|
|
467
|
+
const options = commonSchemas[key];
|
|
468
|
+
if (options.schema === void 0) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const { schema, strategy = "input" } = options;
|
|
472
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
473
|
+
const allowedStrategies = [strategy];
|
|
474
|
+
if (strategy === "input") {
|
|
475
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
476
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
477
|
+
allowedStrategies.push("output");
|
|
478
|
+
}
|
|
479
|
+
} else if (strategy === "output") {
|
|
480
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
481
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
482
|
+
allowedStrategies.push("input");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
baseSchemaConvertOptions.components.push({
|
|
486
|
+
schema,
|
|
487
|
+
required,
|
|
488
|
+
ref: `#/components/schemas/${key}`,
|
|
489
|
+
allowedStrategies
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
doc.components ??= {};
|
|
493
|
+
doc.components.schemas ??= {};
|
|
494
|
+
for (const key in commonSchemas) {
|
|
495
|
+
const options = commonSchemas[key];
|
|
496
|
+
if (options.schema === void 0) {
|
|
497
|
+
if (options.error === "UndefinedError") {
|
|
498
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
499
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
500
|
+
}
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const { schema, strategy = "input" } = options;
|
|
504
|
+
const [, json] = await this.converter.convert(
|
|
505
|
+
schema,
|
|
506
|
+
{
|
|
507
|
+
...baseSchemaConvertOptions,
|
|
508
|
+
strategy,
|
|
509
|
+
minStructureDepthForRef: 1
|
|
510
|
+
// not allow use $ref for root schemas
|
|
511
|
+
}
|
|
512
|
+
);
|
|
513
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
517
|
+
}
|
|
518
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
364
519
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
365
520
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
366
521
|
if (details) {
|
|
367
522
|
ref.requestBody = {
|
|
368
523
|
required: true,
|
|
369
524
|
content: toOpenAPIEventIteratorContent(
|
|
370
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
371
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
525
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
526
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
372
527
|
)
|
|
373
528
|
};
|
|
374
529
|
return;
|
|
375
530
|
}
|
|
376
531
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
377
532
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
378
|
-
let [required, schema] = await this.converter.convert(
|
|
533
|
+
let [required, schema] = await this.converter.convert(
|
|
534
|
+
def.inputSchema,
|
|
535
|
+
{
|
|
536
|
+
...baseSchemaConvertOptions,
|
|
537
|
+
strategy: "input",
|
|
538
|
+
minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
|
|
539
|
+
}
|
|
540
|
+
);
|
|
379
541
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
380
542
|
return;
|
|
381
543
|
}
|
|
@@ -418,7 +580,8 @@ ${errors.join("\n\n")}`
|
|
|
418
580
|
if (!isObjectSchema(schema)) {
|
|
419
581
|
throw error;
|
|
420
582
|
}
|
|
421
|
-
|
|
583
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
|
|
584
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
422
585
|
throw new OpenAPIGeneratorError(
|
|
423
586
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
424
587
|
);
|
|
@@ -426,12 +589,13 @@ ${errors.join("\n\n")}`
|
|
|
426
589
|
for (const from of ["params", "query", "headers"]) {
|
|
427
590
|
const fromSchema = schema.properties?.[from];
|
|
428
591
|
if (fromSchema !== void 0) {
|
|
429
|
-
|
|
592
|
+
const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
|
|
593
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
430
594
|
throw error;
|
|
431
595
|
}
|
|
432
596
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
433
597
|
ref.parameters ??= [];
|
|
434
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
598
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
435
599
|
}
|
|
436
600
|
}
|
|
437
601
|
if (schema.properties?.body !== void 0) {
|
|
@@ -441,7 +605,7 @@ ${errors.join("\n\n")}`
|
|
|
441
605
|
};
|
|
442
606
|
}
|
|
443
607
|
}
|
|
444
|
-
async #successResponse(ref, def) {
|
|
608
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
445
609
|
const outputSchema = def.outputSchema;
|
|
446
610
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
447
611
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -452,46 +616,90 @@ ${errors.join("\n\n")}`
|
|
|
452
616
|
ref.responses[status] = {
|
|
453
617
|
description,
|
|
454
618
|
content: toOpenAPIEventIteratorContent(
|
|
455
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
456
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
619
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
620
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
457
621
|
)
|
|
458
622
|
};
|
|
459
623
|
return;
|
|
460
624
|
}
|
|
461
|
-
const [required, json] = await this.converter.convert(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
625
|
+
const [required, json] = await this.converter.convert(
|
|
626
|
+
outputSchema,
|
|
627
|
+
{
|
|
628
|
+
...baseSchemaConvertOptions,
|
|
629
|
+
strategy: "output",
|
|
630
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
631
|
+
}
|
|
632
|
+
);
|
|
466
633
|
if (outputStructure === "compact") {
|
|
634
|
+
ref.responses ??= {};
|
|
635
|
+
ref.responses[status] = {
|
|
636
|
+
description
|
|
637
|
+
};
|
|
467
638
|
ref.responses[status].content = toOpenAPIContent(applySchemaOptionality(required, json));
|
|
468
639
|
return;
|
|
469
640
|
}
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
641
|
+
const handledStatuses = /* @__PURE__ */ new Set();
|
|
642
|
+
for (const item of expandUnionSchema(json)) {
|
|
643
|
+
const error = new OpenAPIGeneratorError(`
|
|
644
|
+
When output structure is "detailed", output schema must satisfy:
|
|
645
|
+
{
|
|
646
|
+
status?: number, // must be a literal number and in the range of 200-399
|
|
647
|
+
headers?: Record<string, unknown>,
|
|
648
|
+
body?: unknown
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
But got: ${stringifyJSON(item)}
|
|
652
|
+
`);
|
|
653
|
+
if (!isObjectSchema(item)) {
|
|
478
654
|
throw error;
|
|
479
655
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
656
|
+
let schemaStatus;
|
|
657
|
+
let schemaDescription;
|
|
658
|
+
if (item.properties?.status !== void 0) {
|
|
659
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
|
|
660
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
661
|
+
throw error;
|
|
662
|
+
}
|
|
663
|
+
schemaStatus = statusSchema.const;
|
|
664
|
+
schemaDescription = statusSchema.description;
|
|
665
|
+
}
|
|
666
|
+
const itemStatus = schemaStatus ?? status;
|
|
667
|
+
const itemDescription = schemaDescription ?? description;
|
|
668
|
+
if (handledStatuses.has(itemStatus)) {
|
|
669
|
+
throw new OpenAPIGeneratorError(`
|
|
670
|
+
When output structure is "detailed", each success status must be unique.
|
|
671
|
+
But got status: ${itemStatus} used more than once.
|
|
672
|
+
`);
|
|
673
|
+
}
|
|
674
|
+
handledStatuses.add(itemStatus);
|
|
675
|
+
ref.responses ??= {};
|
|
676
|
+
ref.responses[itemStatus] = {
|
|
677
|
+
description: itemDescription
|
|
678
|
+
};
|
|
679
|
+
if (item.properties?.headers !== void 0) {
|
|
680
|
+
const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
|
|
681
|
+
if (!isObjectSchema(headersSchema)) {
|
|
682
|
+
throw error;
|
|
683
|
+
}
|
|
684
|
+
for (const key in headersSchema.properties) {
|
|
685
|
+
const headerSchema = headersSchema.properties[key];
|
|
686
|
+
if (headerSchema !== void 0) {
|
|
687
|
+
ref.responses[itemStatus].headers ??= {};
|
|
688
|
+
ref.responses[itemStatus].headers[key] = {
|
|
689
|
+
schema: toOpenAPISchema(headerSchema),
|
|
690
|
+
required: item.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (item.properties?.body !== void 0) {
|
|
696
|
+
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
697
|
+
applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
|
|
698
|
+
);
|
|
486
699
|
}
|
|
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
700
|
}
|
|
493
701
|
}
|
|
494
|
-
async #errorResponse(ref, def) {
|
|
702
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
|
|
495
703
|
const errorMap = def.errorMap;
|
|
496
704
|
const errors = {};
|
|
497
705
|
for (const code in errorMap) {
|
|
@@ -501,7 +709,7 @@ ${errors.join("\n\n")}`
|
|
|
501
709
|
}
|
|
502
710
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
503
711
|
const message = fallbackORPCErrorMessage(code, config.message);
|
|
504
|
-
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
|
|
712
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
505
713
|
errors[status] ??= [];
|
|
506
714
|
errors[status].push({
|
|
507
715
|
type: "object",
|
|
@@ -523,17 +731,7 @@ ${errors.join("\n\n")}`
|
|
|
523
731
|
content: toOpenAPIContent({
|
|
524
732
|
oneOf: [
|
|
525
733
|
...schemas,
|
|
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
|
-
}
|
|
734
|
+
undefinedErrorSchema
|
|
537
735
|
]
|
|
538
736
|
})
|
|
539
737
|
};
|
|
@@ -541,4 +739,4 @@ ${errors.join("\n\n")}`
|
|
|
541
739
|
}
|
|
542
740
|
}
|
|
543
741
|
|
|
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 };
|
|
742
|
+
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 };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
+
import { TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
+
import { StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
+
import { HTTPPath } from '@orpc/client';
|
|
5
|
+
import { Value } from '@orpc/shared';
|
|
6
|
+
|
|
7
|
+
interface StandardOpenAPIMatcherOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
10
|
+
*
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
14
|
+
}
|
|
15
|
+
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
16
|
+
private readonly filter;
|
|
17
|
+
private readonly tree;
|
|
18
|
+
private pendingRouters;
|
|
19
|
+
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
20
|
+
init(router: AnyRouter, path?: readonly string[]): void;
|
|
21
|
+
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions {
|
|
25
|
+
}
|
|
26
|
+
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
27
|
+
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { StandardOpenAPIHandler as a, StandardOpenAPIMatcher as c };
|
|
31
|
+
export type { StandardOpenAPIHandlerOptions as S, StandardOpenAPIMatcherOptions as b };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
+
import { TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
+
import { StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
+
import { HTTPPath } from '@orpc/client';
|
|
5
|
+
import { Value } from '@orpc/shared';
|
|
6
|
+
|
|
7
|
+
interface StandardOpenAPIMatcherOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
10
|
+
*
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
14
|
+
}
|
|
15
|
+
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
16
|
+
private readonly filter;
|
|
17
|
+
private readonly tree;
|
|
18
|
+
private pendingRouters;
|
|
19
|
+
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
20
|
+
init(router: AnyRouter, path?: readonly string[]): void;
|
|
21
|
+
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions {
|
|
25
|
+
}
|
|
26
|
+
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
27
|
+
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { StandardOpenAPIHandler as a, StandardOpenAPIMatcher as c };
|
|
31
|
+
export type { StandardOpenAPIHandlerOptions as S, StandardOpenAPIMatcherOptions as b };
|