@orpc/openapi 0.0.0-next.e6490f2 → 0.0.0-next.e77ba7d
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 +7 -2
- package/dist/adapters/aws-lambda/index.d.mts +6 -3
- package/dist/adapters/aws-lambda/index.d.ts +6 -3
- package/dist/adapters/aws-lambda/index.mjs +3 -3
- 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 +9 -3
- package/dist/adapters/fetch/index.d.ts +9 -3
- package/dist/adapters/fetch/index.mjs +1 -1
- package/dist/adapters/node/index.d.mts +9 -3
- package/dist/adapters/node/index.d.ts +9 -3
- package/dist/adapters/node/index.mjs +1 -1
- package/dist/adapters/standard/index.d.mts +8 -23
- package/dist/adapters/standard/index.d.ts +8 -23
- package/dist/adapters/standard/index.mjs +1 -1
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.mjs +2 -2
- package/dist/plugins/index.d.mts +18 -3
- package/dist/plugins/index.d.ts +18 -3
- package/dist/plugins/index.mjs +58 -18
- package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.DIt-Z9W1.mjs} +19 -8
- package/dist/shared/{openapi.DaYgbD_w.mjs → openapi.DNv9yFfn.mjs} +269 -68
- package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
- package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
- package/dist/shared/openapi.dbQeFCUJ.d.mts +120 -0
- package/dist/shared/openapi.dbQeFCUJ.d.ts +120 -0
- package/package.json +17 -10
- package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
- package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
- package/dist/shared/openapi.qZLdpE0a.d.mts +0 -52
- package/dist/shared/openapi.qZLdpE0a.d.ts +0 -52
|
@@ -3,7 +3,7 @@ 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, stringifyJSON, findDeepMatches, toArray, clone } from '@orpc/shared';
|
|
6
|
+
import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
|
|
7
7
|
import { TypeName } from 'json-schema-typed/draft-2020-12';
|
|
8
8
|
|
|
9
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
@@ -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));
|
|
@@ -345,6 +350,116 @@ function checkParamsSchema(schema, params) {
|
|
|
345
350
|
function toOpenAPISchema(schema) {
|
|
346
351
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
347
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
|
+
}
|
|
362
|
+
function simplifyComposedObjectJsonSchemasAndRefs(schema, doc) {
|
|
363
|
+
if (doc) {
|
|
364
|
+
schema = resolveOpenAPIJsonSchemaRef(doc, schema);
|
|
365
|
+
}
|
|
366
|
+
if (typeof schema !== "object" || !schema.anyOf && !schema.oneOf && !schema.allOf) {
|
|
367
|
+
return schema;
|
|
368
|
+
}
|
|
369
|
+
const unionSchemas = [
|
|
370
|
+
...toArray(schema.anyOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc))),
|
|
371
|
+
...toArray(schema.oneOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)))
|
|
372
|
+
];
|
|
373
|
+
const objectUnionSchemas = [];
|
|
374
|
+
for (const u of unionSchemas) {
|
|
375
|
+
if (!isObjectSchema(u)) {
|
|
376
|
+
return schema;
|
|
377
|
+
}
|
|
378
|
+
objectUnionSchemas.push(u);
|
|
379
|
+
}
|
|
380
|
+
const mergedUnionPropertyMap = /* @__PURE__ */ new Map();
|
|
381
|
+
for (const u of objectUnionSchemas) {
|
|
382
|
+
if (u.properties) {
|
|
383
|
+
for (const [key, value] of Object.entries(u.properties)) {
|
|
384
|
+
let entry = mergedUnionPropertyMap.get(key);
|
|
385
|
+
if (!entry) {
|
|
386
|
+
const required = objectUnionSchemas.every((s) => s.required?.includes(key));
|
|
387
|
+
entry = { required, schemas: [] };
|
|
388
|
+
mergedUnionPropertyMap.set(key, entry);
|
|
389
|
+
}
|
|
390
|
+
entry.schemas.push(value);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const intersectionSchemas = toArray(schema.allOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)));
|
|
395
|
+
const objectIntersectionSchemas = [];
|
|
396
|
+
for (const u of intersectionSchemas) {
|
|
397
|
+
if (!isObjectSchema(u)) {
|
|
398
|
+
return schema;
|
|
399
|
+
}
|
|
400
|
+
objectIntersectionSchemas.push(u);
|
|
401
|
+
}
|
|
402
|
+
if (isObjectSchema(schema)) {
|
|
403
|
+
objectIntersectionSchemas.push(schema);
|
|
404
|
+
}
|
|
405
|
+
const mergedInteractionPropertyMap = /* @__PURE__ */ new Map();
|
|
406
|
+
for (const u of objectIntersectionSchemas) {
|
|
407
|
+
if (u.properties) {
|
|
408
|
+
for (const [key, value] of Object.entries(u.properties)) {
|
|
409
|
+
let entry = mergedInteractionPropertyMap.get(key);
|
|
410
|
+
if (!entry) {
|
|
411
|
+
const required = objectIntersectionSchemas.some((s) => s.required?.includes(key));
|
|
412
|
+
entry = { required, schemas: [] };
|
|
413
|
+
mergedInteractionPropertyMap.set(key, entry);
|
|
414
|
+
}
|
|
415
|
+
entry.schemas.push(value);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const resultObjectSchema = { type: "object", properties: {}, required: [] };
|
|
420
|
+
const keys = /* @__PURE__ */ new Set([
|
|
421
|
+
...mergedUnionPropertyMap.keys(),
|
|
422
|
+
...mergedInteractionPropertyMap.keys()
|
|
423
|
+
]);
|
|
424
|
+
if (keys.size === 0) {
|
|
425
|
+
return schema;
|
|
426
|
+
}
|
|
427
|
+
const deduplicateSchemas = (schemas) => {
|
|
428
|
+
const seen = /* @__PURE__ */ new Set();
|
|
429
|
+
const result = [];
|
|
430
|
+
for (const schema2 of schemas) {
|
|
431
|
+
const key = stringifyJSON(schema2);
|
|
432
|
+
if (!seen.has(key)) {
|
|
433
|
+
seen.add(key);
|
|
434
|
+
result.push(schema2);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
};
|
|
439
|
+
for (const key of keys) {
|
|
440
|
+
const unionEntry = mergedUnionPropertyMap.get(key);
|
|
441
|
+
const intersectionEntry = mergedInteractionPropertyMap.get(key);
|
|
442
|
+
resultObjectSchema.properties[key] = (() => {
|
|
443
|
+
const dedupedUnionSchemas = unionEntry ? deduplicateSchemas(unionEntry.schemas) : [];
|
|
444
|
+
const dedupedIntersectionSchemas = intersectionEntry ? deduplicateSchemas(intersectionEntry.schemas) : [];
|
|
445
|
+
if (!dedupedUnionSchemas.length) {
|
|
446
|
+
return dedupedIntersectionSchemas.length === 1 ? dedupedIntersectionSchemas[0] : { allOf: dedupedIntersectionSchemas };
|
|
447
|
+
}
|
|
448
|
+
if (!dedupedIntersectionSchemas.length) {
|
|
449
|
+
return dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas };
|
|
450
|
+
}
|
|
451
|
+
const allOf = deduplicateSchemas([
|
|
452
|
+
...dedupedIntersectionSchemas,
|
|
453
|
+
dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas }
|
|
454
|
+
]);
|
|
455
|
+
return allOf.length === 1 ? allOf[0] : { allOf };
|
|
456
|
+
})();
|
|
457
|
+
if (unionEntry?.required || intersectionEntry?.required) {
|
|
458
|
+
resultObjectSchema.required.push(key);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return resultObjectSchema;
|
|
462
|
+
}
|
|
348
463
|
|
|
349
464
|
class CompositeSchemaConverter {
|
|
350
465
|
converters;
|
|
@@ -375,41 +490,47 @@ class OpenAPIGenerator {
|
|
|
375
490
|
*
|
|
376
491
|
* @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
377
492
|
*/
|
|
378
|
-
async generate(router,
|
|
379
|
-
const
|
|
493
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
494
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
495
|
+
return !(exclude?.(contract, path) ?? false);
|
|
496
|
+
});
|
|
380
497
|
const doc = {
|
|
381
|
-
...clone(
|
|
382
|
-
info:
|
|
383
|
-
openapi: "3.1.1"
|
|
384
|
-
exclude: void 0
|
|
498
|
+
...clone(baseDoc),
|
|
499
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
500
|
+
openapi: "3.1.1"
|
|
385
501
|
};
|
|
502
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
386
503
|
const contracts = [];
|
|
387
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
388
|
-
if (!
|
|
389
|
-
|
|
504
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
505
|
+
if (!value(filter, traverseOptions)) {
|
|
506
|
+
return;
|
|
390
507
|
}
|
|
508
|
+
contracts.push(traverseOptions);
|
|
391
509
|
});
|
|
392
510
|
const errors = [];
|
|
393
511
|
for (const { contract, path } of contracts) {
|
|
394
|
-
const
|
|
512
|
+
const stringPath = path.join(".");
|
|
395
513
|
try {
|
|
396
514
|
const def = contract["~orpc"];
|
|
397
515
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
398
516
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
399
517
|
let operationObjectRef;
|
|
400
|
-
if (def.route.spec !== void 0) {
|
|
518
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
401
519
|
operationObjectRef = def.route.spec;
|
|
402
520
|
} else {
|
|
403
521
|
operationObjectRef = {
|
|
404
|
-
operationId,
|
|
522
|
+
operationId: def.route.operationId ?? stringPath,
|
|
405
523
|
summary: def.route.summary,
|
|
406
524
|
description: def.route.description,
|
|
407
525
|
deprecated: def.route.deprecated,
|
|
408
526
|
tags: def.route.tags?.map((tag) => tag)
|
|
409
527
|
};
|
|
410
|
-
await this.#request(operationObjectRef, def);
|
|
411
|
-
await this.#successResponse(operationObjectRef, def);
|
|
412
|
-
await this.#errorResponse(operationObjectRef, def);
|
|
528
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
529
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
530
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
|
|
531
|
+
}
|
|
532
|
+
if (typeof def.route.spec === "function") {
|
|
533
|
+
operationObjectRef = def.route.spec(operationObjectRef);
|
|
413
534
|
}
|
|
414
535
|
doc.paths ??= {};
|
|
415
536
|
doc.paths[httpPath] ??= {};
|
|
@@ -419,7 +540,7 @@ class OpenAPIGenerator {
|
|
|
419
540
|
throw e;
|
|
420
541
|
}
|
|
421
542
|
errors.push(
|
|
422
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
543
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
423
544
|
${e.message}`
|
|
424
545
|
);
|
|
425
546
|
}
|
|
@@ -433,25 +554,101 @@ ${errors.join("\n\n")}`
|
|
|
433
554
|
}
|
|
434
555
|
return this.serializer.serialize(doc)[0];
|
|
435
556
|
}
|
|
436
|
-
async #
|
|
557
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
558
|
+
let undefinedErrorJsonSchema = {
|
|
559
|
+
type: "object",
|
|
560
|
+
properties: {
|
|
561
|
+
defined: { const: false },
|
|
562
|
+
code: { type: "string" },
|
|
563
|
+
status: { type: "number" },
|
|
564
|
+
message: { type: "string" },
|
|
565
|
+
data: {}
|
|
566
|
+
},
|
|
567
|
+
required: ["defined", "code", "status", "message"]
|
|
568
|
+
};
|
|
569
|
+
const baseSchemaConvertOptions = {};
|
|
570
|
+
if (commonSchemas) {
|
|
571
|
+
baseSchemaConvertOptions.components = [];
|
|
572
|
+
for (const key in commonSchemas) {
|
|
573
|
+
const options = commonSchemas[key];
|
|
574
|
+
if (options.schema === void 0) {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const { schema, strategy = "input" } = options;
|
|
578
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
579
|
+
const allowedStrategies = [strategy];
|
|
580
|
+
if (strategy === "input") {
|
|
581
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
582
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
583
|
+
allowedStrategies.push("output");
|
|
584
|
+
}
|
|
585
|
+
} else if (strategy === "output") {
|
|
586
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
587
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
588
|
+
allowedStrategies.push("input");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
baseSchemaConvertOptions.components.push({
|
|
592
|
+
schema,
|
|
593
|
+
required,
|
|
594
|
+
ref: `#/components/schemas/${key}`,
|
|
595
|
+
allowedStrategies
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
doc.components ??= {};
|
|
599
|
+
doc.components.schemas ??= {};
|
|
600
|
+
for (const key in commonSchemas) {
|
|
601
|
+
const options = commonSchemas[key];
|
|
602
|
+
if (options.schema === void 0) {
|
|
603
|
+
if (options.error === "UndefinedError") {
|
|
604
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
605
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
606
|
+
}
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const { schema, strategy = "input" } = options;
|
|
610
|
+
const [, json] = await this.converter.convert(
|
|
611
|
+
schema,
|
|
612
|
+
{
|
|
613
|
+
...baseSchemaConvertOptions,
|
|
614
|
+
strategy,
|
|
615
|
+
minStructureDepthForRef: 1
|
|
616
|
+
// not allow use $ref for root schemas
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
623
|
+
}
|
|
624
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
437
625
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
438
626
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
439
627
|
if (details) {
|
|
440
628
|
ref.requestBody = {
|
|
441
629
|
required: true,
|
|
442
630
|
content: toOpenAPIEventIteratorContent(
|
|
443
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
444
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
631
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
632
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
445
633
|
)
|
|
446
634
|
};
|
|
447
635
|
return;
|
|
448
636
|
}
|
|
449
637
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
450
638
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
451
|
-
let [required, schema] = await this.converter.convert(
|
|
639
|
+
let [required, schema] = await this.converter.convert(
|
|
640
|
+
def.inputSchema,
|
|
641
|
+
{
|
|
642
|
+
...baseSchemaConvertOptions,
|
|
643
|
+
strategy: "input"
|
|
644
|
+
}
|
|
645
|
+
);
|
|
452
646
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
453
647
|
return;
|
|
454
648
|
}
|
|
649
|
+
if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
|
|
650
|
+
schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
|
|
651
|
+
}
|
|
455
652
|
if (inputStructure === "compact") {
|
|
456
653
|
if (dynamicParams?.length) {
|
|
457
654
|
const error2 = new OpenAPIGeneratorError(
|
|
@@ -491,7 +688,8 @@ ${errors.join("\n\n")}`
|
|
|
491
688
|
if (!isObjectSchema(schema)) {
|
|
492
689
|
throw error;
|
|
493
690
|
}
|
|
494
|
-
|
|
691
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? simplifyComposedObjectJsonSchemasAndRefs(schema.properties.params, doc) : void 0;
|
|
692
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
495
693
|
throw new OpenAPIGeneratorError(
|
|
496
694
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
497
695
|
);
|
|
@@ -499,12 +697,13 @@ ${errors.join("\n\n")}`
|
|
|
499
697
|
for (const from of ["params", "query", "headers"]) {
|
|
500
698
|
const fromSchema = schema.properties?.[from];
|
|
501
699
|
if (fromSchema !== void 0) {
|
|
502
|
-
|
|
700
|
+
const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
|
|
701
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
503
702
|
throw error;
|
|
504
703
|
}
|
|
505
704
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
506
705
|
ref.parameters ??= [];
|
|
507
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
706
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
508
707
|
}
|
|
509
708
|
}
|
|
510
709
|
if (schema.properties?.body !== void 0) {
|
|
@@ -514,7 +713,7 @@ ${errors.join("\n\n")}`
|
|
|
514
713
|
};
|
|
515
714
|
}
|
|
516
715
|
}
|
|
517
|
-
async #successResponse(ref, def) {
|
|
716
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
518
717
|
const outputSchema = def.outputSchema;
|
|
519
718
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
520
719
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -525,13 +724,20 @@ ${errors.join("\n\n")}`
|
|
|
525
724
|
ref.responses[status] = {
|
|
526
725
|
description,
|
|
527
726
|
content: toOpenAPIEventIteratorContent(
|
|
528
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
529
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
727
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
728
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
530
729
|
)
|
|
531
730
|
};
|
|
532
731
|
return;
|
|
533
732
|
}
|
|
534
|
-
const [required, json] = await this.converter.convert(
|
|
733
|
+
const [required, json] = await this.converter.convert(
|
|
734
|
+
outputSchema,
|
|
735
|
+
{
|
|
736
|
+
...baseSchemaConvertOptions,
|
|
737
|
+
strategy: "output",
|
|
738
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
739
|
+
}
|
|
740
|
+
);
|
|
535
741
|
if (outputStructure === "compact") {
|
|
536
742
|
ref.responses ??= {};
|
|
537
743
|
ref.responses[status] = {
|
|
@@ -552,17 +758,19 @@ ${errors.join("\n\n")}`
|
|
|
552
758
|
|
|
553
759
|
But got: ${stringifyJSON(item)}
|
|
554
760
|
`);
|
|
555
|
-
|
|
761
|
+
const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
|
|
762
|
+
if (!isObjectSchema(simplifiedItem)) {
|
|
556
763
|
throw error;
|
|
557
764
|
}
|
|
558
765
|
let schemaStatus;
|
|
559
766
|
let schemaDescription;
|
|
560
|
-
if (
|
|
561
|
-
|
|
767
|
+
if (simplifiedItem.properties?.status !== void 0) {
|
|
768
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, simplifiedItem.properties.status);
|
|
769
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
562
770
|
throw error;
|
|
563
771
|
}
|
|
564
|
-
schemaStatus =
|
|
565
|
-
schemaDescription =
|
|
772
|
+
schemaStatus = statusSchema.const;
|
|
773
|
+
schemaDescription = statusSchema.description;
|
|
566
774
|
}
|
|
567
775
|
const itemStatus = schemaStatus ?? status;
|
|
568
776
|
const itemDescription = schemaDescription ?? description;
|
|
@@ -577,71 +785,64 @@ ${errors.join("\n\n")}`
|
|
|
577
785
|
ref.responses[itemStatus] = {
|
|
578
786
|
description: itemDescription
|
|
579
787
|
};
|
|
580
|
-
if (
|
|
581
|
-
|
|
788
|
+
if (simplifiedItem.properties?.headers !== void 0) {
|
|
789
|
+
const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
|
|
790
|
+
if (!isObjectSchema(headersSchema)) {
|
|
582
791
|
throw error;
|
|
583
792
|
}
|
|
584
|
-
for (const key in
|
|
585
|
-
const headerSchema =
|
|
793
|
+
for (const key in headersSchema.properties) {
|
|
794
|
+
const headerSchema = headersSchema.properties[key];
|
|
586
795
|
if (headerSchema !== void 0) {
|
|
587
796
|
ref.responses[itemStatus].headers ??= {};
|
|
588
797
|
ref.responses[itemStatus].headers[key] = {
|
|
589
798
|
schema: toOpenAPISchema(headerSchema),
|
|
590
|
-
required:
|
|
799
|
+
required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
591
800
|
};
|
|
592
801
|
}
|
|
593
802
|
}
|
|
594
803
|
}
|
|
595
|
-
if (
|
|
804
|
+
if (simplifiedItem.properties?.body !== void 0) {
|
|
596
805
|
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
597
|
-
applySchemaOptionality(
|
|
806
|
+
applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
|
|
598
807
|
);
|
|
599
808
|
}
|
|
600
809
|
}
|
|
601
810
|
}
|
|
602
|
-
async #errorResponse(ref, def) {
|
|
811
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
603
812
|
const errorMap = def.errorMap;
|
|
604
|
-
const
|
|
813
|
+
const errorResponsesByStatus = {};
|
|
605
814
|
for (const code in errorMap) {
|
|
606
815
|
const config = errorMap[code];
|
|
607
816
|
if (!config) {
|
|
608
817
|
continue;
|
|
609
818
|
}
|
|
610
819
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
820
|
+
const defaultMessage = fallbackORPCErrorMessage(code, config.message);
|
|
821
|
+
errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
|
|
822
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
823
|
+
errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
|
|
824
|
+
errorResponsesByStatus[status].errorSchemaVariants.push({
|
|
615
825
|
type: "object",
|
|
616
826
|
properties: {
|
|
617
827
|
defined: { const: true },
|
|
618
828
|
code: { const: code },
|
|
619
829
|
status: { const: status },
|
|
620
|
-
message: { type: "string", default:
|
|
830
|
+
message: { type: "string", default: defaultMessage },
|
|
621
831
|
data: dataSchema
|
|
622
832
|
},
|
|
623
833
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
624
834
|
});
|
|
625
835
|
}
|
|
626
836
|
ref.responses ??= {};
|
|
627
|
-
for (const
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
837
|
+
for (const statusString in errorResponsesByStatus) {
|
|
838
|
+
const errorResponse = errorResponsesByStatus[statusString];
|
|
839
|
+
const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
|
|
840
|
+
ref.responses[statusString] = {
|
|
841
|
+
description: statusString,
|
|
842
|
+
content: toOpenAPIContent(customBodySchema ?? {
|
|
632
843
|
oneOf: [
|
|
633
|
-
...
|
|
634
|
-
|
|
635
|
-
type: "object",
|
|
636
|
-
properties: {
|
|
637
|
-
defined: { const: false },
|
|
638
|
-
code: { type: "string" },
|
|
639
|
-
status: { type: "number" },
|
|
640
|
-
message: { type: "string" },
|
|
641
|
-
data: {}
|
|
642
|
-
},
|
|
643
|
-
required: ["defined", "code", "status", "message"]
|
|
644
|
-
}
|
|
844
|
+
...errorResponse.errorSchemaVariants,
|
|
845
|
+
undefinedErrorSchema
|
|
645
846
|
]
|
|
646
847
|
})
|
|
647
848
|
};
|
|
@@ -649,4 +850,4 @@ ${errors.join("\n\n")}`
|
|
|
649
850
|
}
|
|
650
851
|
}
|
|
651
852
|
|
|
652
|
-
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,
|
|
853
|
+
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, separateObjectSchema as m, filterSchemaBranches as n, applySchemaOptionality as o, expandUnionSchema as p, expandArrayableSchema as q, resolveOpenAPIJsonSchemaRef as r, simplifyComposedObjectJsonSchemasAndRefs as s, toOpenAPIPath as t, isPrimitiveSchema as u };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
+
import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
+
import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
+
import { ORPCError, HTTPPath } from '@orpc/client';
|
|
5
|
+
import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
|
|
6
|
+
import { Value } from '@orpc/shared';
|
|
7
|
+
|
|
8
|
+
interface StandardOpenAPICodecOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Customize how an ORPC error is encoded into a response body.
|
|
11
|
+
* Use this if your API needs a different error output structure.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* - Return `null | undefined` to fallback to default behavior
|
|
15
|
+
*
|
|
16
|
+
* @default ((e) => e.toJSON())
|
|
17
|
+
*/
|
|
18
|
+
customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
|
|
19
|
+
}
|
|
20
|
+
declare class StandardOpenAPICodec implements StandardCodec {
|
|
21
|
+
#private;
|
|
22
|
+
private readonly serializer;
|
|
23
|
+
private readonly customErrorResponseBodyEncoder;
|
|
24
|
+
constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
|
|
25
|
+
decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
|
|
26
|
+
encode(output: unknown, procedure: AnyProcedure): StandardResponse;
|
|
27
|
+
encodeError(error: ORPCError<any, any>): StandardResponse;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface StandardOpenAPIMatcherOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
33
|
+
*
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
37
|
+
}
|
|
38
|
+
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
39
|
+
private readonly filter;
|
|
40
|
+
private readonly tree;
|
|
41
|
+
private pendingRouters;
|
|
42
|
+
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
43
|
+
init(router: AnyRouter, path?: readonly string[]): void;
|
|
44
|
+
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
|
|
48
|
+
}
|
|
49
|
+
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
50
|
+
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
|
|
54
|
+
export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
|
|
2
|
+
import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
|
|
3
|
+
import { StandardCodec, StandardParams, StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
|
|
4
|
+
import { ORPCError, HTTPPath } from '@orpc/client';
|
|
5
|
+
import { StandardLazyRequest, StandardResponse } from '@orpc/standard-server';
|
|
6
|
+
import { Value } from '@orpc/shared';
|
|
7
|
+
|
|
8
|
+
interface StandardOpenAPICodecOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Customize how an ORPC error is encoded into a response body.
|
|
11
|
+
* Use this if your API needs a different error output structure.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* - Return `null | undefined` to fallback to default behavior
|
|
15
|
+
*
|
|
16
|
+
* @default ((e) => e.toJSON())
|
|
17
|
+
*/
|
|
18
|
+
customErrorResponseBodyEncoder?: (error: ORPCError<any, any>) => unknown;
|
|
19
|
+
}
|
|
20
|
+
declare class StandardOpenAPICodec implements StandardCodec {
|
|
21
|
+
#private;
|
|
22
|
+
private readonly serializer;
|
|
23
|
+
private readonly customErrorResponseBodyEncoder;
|
|
24
|
+
constructor(serializer: StandardOpenAPISerializer, options?: StandardOpenAPICodecOptions);
|
|
25
|
+
decode(request: StandardLazyRequest, params: StandardParams | undefined, procedure: AnyProcedure): Promise<unknown>;
|
|
26
|
+
encode(output: unknown, procedure: AnyProcedure): StandardResponse;
|
|
27
|
+
encodeError(error: ORPCError<any, any>): StandardResponse;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface StandardOpenAPIMatcherOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Filter procedures. Return `false` to exclude a procedure from matching.
|
|
33
|
+
*
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
|
|
37
|
+
}
|
|
38
|
+
declare class StandardOpenAPIMatcher implements StandardMatcher {
|
|
39
|
+
private readonly filter;
|
|
40
|
+
private readonly tree;
|
|
41
|
+
private pendingRouters;
|
|
42
|
+
constructor(options?: StandardOpenAPIMatcherOptions);
|
|
43
|
+
init(router: AnyRouter, path?: readonly string[]): void;
|
|
44
|
+
match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions, StandardOpenAPICodecOptions {
|
|
48
|
+
}
|
|
49
|
+
declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
|
|
50
|
+
constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { StandardOpenAPICodec as a, StandardOpenAPIHandler as c, StandardOpenAPIMatcher as e };
|
|
54
|
+
export type { StandardOpenAPICodecOptions as S, StandardOpenAPIHandlerOptions as b, StandardOpenAPIMatcherOptions as d };
|