@orpc/openapi 0.0.0-next.b6b0cc3 → 0.0.0-next.b6b8746
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 +127 -20
- 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 +11 -5
- package/dist/adapters/fetch/index.d.ts +11 -5
- package/dist/adapters/fetch/index.mjs +1 -1
- package/dist/adapters/node/index.d.mts +11 -5
- package/dist/adapters/node/index.d.ts +11 -5
- 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 +25 -13
- package/dist/index.d.ts +25 -13
- package/dist/index.mjs +3 -3
- package/dist/plugins/index.d.mts +21 -5
- package/dist/plugins/index.d.ts +21 -5
- package/dist/plugins/index.mjs +69 -20
- package/dist/shared/{openapi.C_UtQ8Us.mjs → openapi.BB-W-NKv.mjs} +33 -8
- package/dist/shared/openapi.BGy4N6eR.d.mts +120 -0
- package/dist/shared/openapi.BGy4N6eR.d.ts +120 -0
- package/dist/shared/{openapi.PDTdnRIU.mjs → openapi.BwdtJjDu.mjs} +366 -83
- package/dist/shared/openapi.DwaweYRb.d.mts +54 -0
- package/dist/shared/openapi.DwaweYRb.d.ts +54 -0
- package/package.json +24 -14
- package/dist/shared/openapi.CwdCLgSU.d.mts +0 -53
- package/dist/shared/openapi.CwdCLgSU.d.ts +0 -53
- package/dist/shared/openapi.D3j94c9n.d.mts +0 -12
- package/dist/shared/openapi.D3j94c9n.d.ts +0 -12
|
@@ -3,8 +3,8 @@ 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,
|
|
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) {
|
|
@@ -108,22 +108,47 @@ function isAnySchema(schema) {
|
|
|
108
108
|
if (schema === true) {
|
|
109
109
|
return true;
|
|
110
110
|
}
|
|
111
|
-
if (Object.keys(schema).every((k) => !LOGIC_KEYWORDS.includes(k))) {
|
|
111
|
+
if (Object.keys(schema).filter((v) => schema[v] !== void 0).every((k) => !LOGIC_KEYWORDS.includes(k))) {
|
|
112
112
|
return true;
|
|
113
113
|
}
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
116
|
+
function isNeverSchema(schema) {
|
|
117
|
+
if (schema === false) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (typeof schema === "object" && schema.not !== void 0) {
|
|
121
|
+
if (schema.not === true) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
116
130
|
function separateObjectSchema(schema, separatedProperties) {
|
|
117
|
-
if (Object.keys(schema).some(
|
|
131
|
+
if (Object.keys(schema).some(
|
|
132
|
+
(k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
|
|
133
|
+
)) {
|
|
118
134
|
return [{ type: "object" }, schema];
|
|
119
135
|
}
|
|
120
136
|
const matched = { ...schema };
|
|
121
137
|
const rest = { ...schema };
|
|
122
|
-
matched.properties =
|
|
123
|
-
|
|
138
|
+
matched.properties = separatedProperties.reduce((acc, key) => {
|
|
139
|
+
const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
|
|
140
|
+
if (keySchema !== void 0) {
|
|
141
|
+
acc[key] = keySchema;
|
|
142
|
+
}
|
|
124
143
|
return acc;
|
|
125
144
|
}, {});
|
|
145
|
+
if (Object.keys(matched.properties).length === 0) {
|
|
146
|
+
matched.properties = void 0;
|
|
147
|
+
}
|
|
126
148
|
matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
|
|
149
|
+
if (matched.required?.length === 0) {
|
|
150
|
+
matched.required = void 0;
|
|
151
|
+
}
|
|
127
152
|
matched.examples = schema.examples?.map((example) => {
|
|
128
153
|
if (!isObject(example)) {
|
|
129
154
|
return example;
|
|
@@ -135,11 +160,14 @@ function separateObjectSchema(schema, separatedProperties) {
|
|
|
135
160
|
return acc;
|
|
136
161
|
}, {});
|
|
137
162
|
});
|
|
138
|
-
rest.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => !separatedProperties.includes(key)).reduce((acc, [key, value]) => {
|
|
163
|
+
rest.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => !separatedProperties.includes(key)).reduce((acc = {}, [key, value]) => {
|
|
139
164
|
acc[key] = value;
|
|
140
165
|
return acc;
|
|
141
|
-
},
|
|
166
|
+
}, void 0);
|
|
142
167
|
rest.required = schema.required?.filter((key) => !separatedProperties.includes(key));
|
|
168
|
+
if (rest.required?.length === 0) {
|
|
169
|
+
rest.required = void 0;
|
|
170
|
+
}
|
|
143
171
|
rest.examples = schema.examples?.map((example) => {
|
|
144
172
|
if (!isObject(example)) {
|
|
145
173
|
return example;
|
|
@@ -196,6 +224,45 @@ function expandUnionSchema(schema) {
|
|
|
196
224
|
}
|
|
197
225
|
return [schema];
|
|
198
226
|
}
|
|
227
|
+
function expandArrayableSchema(schema) {
|
|
228
|
+
const schemas = expandUnionSchema(schema);
|
|
229
|
+
if (schemas.length !== 2) {
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
const arraySchema = schemas.find(
|
|
233
|
+
(s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
|
|
234
|
+
);
|
|
235
|
+
if (arraySchema === void 0) {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
const items1 = arraySchema.items;
|
|
239
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
240
|
+
if (stringifyJSON(items1) !== stringifyJSON(items2)) {
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
return [items2, arraySchema];
|
|
244
|
+
}
|
|
245
|
+
const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
246
|
+
TypeName.String,
|
|
247
|
+
TypeName.Number,
|
|
248
|
+
TypeName.Integer,
|
|
249
|
+
TypeName.Boolean,
|
|
250
|
+
TypeName.Null
|
|
251
|
+
]);
|
|
252
|
+
function isPrimitiveSchema(schema) {
|
|
253
|
+
return expandUnionSchema(schema).every((s) => {
|
|
254
|
+
if (typeof s === "boolean") {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
if (s.const !== void 0) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
199
266
|
|
|
200
267
|
function toOpenAPIPath(path) {
|
|
201
268
|
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
@@ -211,7 +278,7 @@ function toOpenAPIContent(schema) {
|
|
|
211
278
|
schema: toOpenAPISchema(file)
|
|
212
279
|
};
|
|
213
280
|
}
|
|
214
|
-
if (restSchema !== void 0) {
|
|
281
|
+
if (restSchema !== void 0 && !isAnySchema(restSchema) && !isNeverSchema(restSchema)) {
|
|
215
282
|
content["application/json"] = {
|
|
216
283
|
schema: toOpenAPISchema(restSchema)
|
|
217
284
|
};
|
|
@@ -268,13 +335,26 @@ function toOpenAPIParameters(schema, parameterIn) {
|
|
|
268
335
|
const parameters = [];
|
|
269
336
|
for (const key in schema.properties) {
|
|
270
337
|
const keySchema = schema.properties[key];
|
|
338
|
+
let isDeepObjectStyle = true;
|
|
339
|
+
if (parameterIn !== "query") {
|
|
340
|
+
isDeepObjectStyle = false;
|
|
341
|
+
} else if (isPrimitiveSchema(keySchema)) {
|
|
342
|
+
isDeepObjectStyle = false;
|
|
343
|
+
} else {
|
|
344
|
+
const [item] = expandArrayableSchema(keySchema) ?? [];
|
|
345
|
+
if (item !== void 0 && isPrimitiveSchema(item)) {
|
|
346
|
+
isDeepObjectStyle = false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
271
349
|
parameters.push({
|
|
272
350
|
name: key,
|
|
273
351
|
in: parameterIn,
|
|
274
352
|
required: schema.required?.includes(key),
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
353
|
+
schema: toOpenAPISchema(keySchema),
|
|
354
|
+
style: isDeepObjectStyle ? "deepObject" : void 0,
|
|
355
|
+
explode: isDeepObjectStyle ? true : void 0,
|
|
356
|
+
allowEmptyValue: parameterIn === "query" ? true : void 0,
|
|
357
|
+
allowReserved: parameterIn === "query" ? true : void 0
|
|
278
358
|
});
|
|
279
359
|
}
|
|
280
360
|
return parameters;
|
|
@@ -293,6 +373,116 @@ function checkParamsSchema(schema, params) {
|
|
|
293
373
|
function toOpenAPISchema(schema) {
|
|
294
374
|
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
295
375
|
}
|
|
376
|
+
const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
377
|
+
function resolveOpenAPIJsonSchemaRef(doc, schema) {
|
|
378
|
+
if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
|
|
379
|
+
return schema;
|
|
380
|
+
}
|
|
381
|
+
const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
|
|
382
|
+
const resolved = doc.components?.schemas?.[name];
|
|
383
|
+
return resolved ?? schema;
|
|
384
|
+
}
|
|
385
|
+
function simplifyComposedObjectJsonSchemasAndRefs(schema, doc) {
|
|
386
|
+
if (doc) {
|
|
387
|
+
schema = resolveOpenAPIJsonSchemaRef(doc, schema);
|
|
388
|
+
}
|
|
389
|
+
if (typeof schema !== "object" || !schema.anyOf && !schema.oneOf && !schema.allOf) {
|
|
390
|
+
return schema;
|
|
391
|
+
}
|
|
392
|
+
const unionSchemas = [
|
|
393
|
+
...toArray(schema.anyOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc))),
|
|
394
|
+
...toArray(schema.oneOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)))
|
|
395
|
+
];
|
|
396
|
+
const objectUnionSchemas = [];
|
|
397
|
+
for (const u of unionSchemas) {
|
|
398
|
+
if (!isObjectSchema(u)) {
|
|
399
|
+
return schema;
|
|
400
|
+
}
|
|
401
|
+
objectUnionSchemas.push(u);
|
|
402
|
+
}
|
|
403
|
+
const mergedUnionPropertyMap = /* @__PURE__ */ new Map();
|
|
404
|
+
for (const u of objectUnionSchemas) {
|
|
405
|
+
if (u.properties) {
|
|
406
|
+
for (const [key, value] of Object.entries(u.properties)) {
|
|
407
|
+
let entry = mergedUnionPropertyMap.get(key);
|
|
408
|
+
if (!entry) {
|
|
409
|
+
const required = objectUnionSchemas.every((s) => s.required?.includes(key));
|
|
410
|
+
entry = { required, schemas: [] };
|
|
411
|
+
mergedUnionPropertyMap.set(key, entry);
|
|
412
|
+
}
|
|
413
|
+
entry.schemas.push(value);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const intersectionSchemas = toArray(schema.allOf?.map((s) => simplifyComposedObjectJsonSchemasAndRefs(s, doc)));
|
|
418
|
+
const objectIntersectionSchemas = [];
|
|
419
|
+
for (const u of intersectionSchemas) {
|
|
420
|
+
if (!isObjectSchema(u)) {
|
|
421
|
+
return schema;
|
|
422
|
+
}
|
|
423
|
+
objectIntersectionSchemas.push(u);
|
|
424
|
+
}
|
|
425
|
+
if (isObjectSchema(schema)) {
|
|
426
|
+
objectIntersectionSchemas.push(schema);
|
|
427
|
+
}
|
|
428
|
+
const mergedInteractionPropertyMap = /* @__PURE__ */ new Map();
|
|
429
|
+
for (const u of objectIntersectionSchemas) {
|
|
430
|
+
if (u.properties) {
|
|
431
|
+
for (const [key, value] of Object.entries(u.properties)) {
|
|
432
|
+
let entry = mergedInteractionPropertyMap.get(key);
|
|
433
|
+
if (!entry) {
|
|
434
|
+
const required = objectIntersectionSchemas.some((s) => s.required?.includes(key));
|
|
435
|
+
entry = { required, schemas: [] };
|
|
436
|
+
mergedInteractionPropertyMap.set(key, entry);
|
|
437
|
+
}
|
|
438
|
+
entry.schemas.push(value);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const resultObjectSchema = { type: "object", properties: {}, required: [] };
|
|
443
|
+
const keys = /* @__PURE__ */ new Set([
|
|
444
|
+
...mergedUnionPropertyMap.keys(),
|
|
445
|
+
...mergedInteractionPropertyMap.keys()
|
|
446
|
+
]);
|
|
447
|
+
if (keys.size === 0) {
|
|
448
|
+
return schema;
|
|
449
|
+
}
|
|
450
|
+
const deduplicateSchemas = (schemas) => {
|
|
451
|
+
const seen = /* @__PURE__ */ new Set();
|
|
452
|
+
const result = [];
|
|
453
|
+
for (const schema2 of schemas) {
|
|
454
|
+
const key = stringifyJSON(schema2);
|
|
455
|
+
if (!seen.has(key)) {
|
|
456
|
+
seen.add(key);
|
|
457
|
+
result.push(schema2);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return result;
|
|
461
|
+
};
|
|
462
|
+
for (const key of keys) {
|
|
463
|
+
const unionEntry = mergedUnionPropertyMap.get(key);
|
|
464
|
+
const intersectionEntry = mergedInteractionPropertyMap.get(key);
|
|
465
|
+
resultObjectSchema.properties[key] = (() => {
|
|
466
|
+
const dedupedUnionSchemas = unionEntry ? deduplicateSchemas(unionEntry.schemas) : [];
|
|
467
|
+
const dedupedIntersectionSchemas = intersectionEntry ? deduplicateSchemas(intersectionEntry.schemas) : [];
|
|
468
|
+
if (!dedupedUnionSchemas.length) {
|
|
469
|
+
return dedupedIntersectionSchemas.length === 1 ? dedupedIntersectionSchemas[0] : { allOf: dedupedIntersectionSchemas };
|
|
470
|
+
}
|
|
471
|
+
if (!dedupedIntersectionSchemas.length) {
|
|
472
|
+
return dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas };
|
|
473
|
+
}
|
|
474
|
+
const allOf = deduplicateSchemas([
|
|
475
|
+
...dedupedIntersectionSchemas,
|
|
476
|
+
dedupedUnionSchemas.length === 1 ? dedupedUnionSchemas[0] : { anyOf: dedupedUnionSchemas }
|
|
477
|
+
]);
|
|
478
|
+
return allOf.length === 1 ? allOf[0] : { allOf };
|
|
479
|
+
})();
|
|
480
|
+
if (unionEntry?.required || intersectionEntry?.required) {
|
|
481
|
+
resultObjectSchema.required.push(key);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return resultObjectSchema;
|
|
485
|
+
}
|
|
296
486
|
|
|
297
487
|
class CompositeSchemaConverter {
|
|
298
488
|
converters;
|
|
@@ -321,39 +511,50 @@ class OpenAPIGenerator {
|
|
|
321
511
|
/**
|
|
322
512
|
* Generates OpenAPI specifications from oRPC routers/contracts.
|
|
323
513
|
*
|
|
324
|
-
* @see {@link https://orpc.
|
|
514
|
+
* @see {@link https://orpc.dev/docs/openapi/openapi-specification OpenAPI Specification Docs}
|
|
325
515
|
*/
|
|
326
|
-
async generate(router,
|
|
327
|
-
const
|
|
516
|
+
async generate(router, { customErrorResponseBodySchema, commonSchemas, filter: baseFilter, exclude, ...baseDoc } = {}) {
|
|
517
|
+
const filter = baseFilter ?? (({ contract, path }) => {
|
|
518
|
+
return !(exclude?.(contract, path) ?? false);
|
|
519
|
+
});
|
|
328
520
|
const doc = {
|
|
329
|
-
...clone(
|
|
330
|
-
info:
|
|
331
|
-
openapi: "3.1.1"
|
|
332
|
-
exclude: void 0
|
|
521
|
+
...clone(baseDoc),
|
|
522
|
+
info: baseDoc.info ?? { title: "API Reference", version: "0.0.0" },
|
|
523
|
+
openapi: "3.1.1"
|
|
333
524
|
};
|
|
525
|
+
const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, commonSchemas);
|
|
334
526
|
const contracts = [];
|
|
335
|
-
await resolveContractProcedures({ path: [], router }, (
|
|
336
|
-
if (!
|
|
337
|
-
|
|
527
|
+
await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
|
|
528
|
+
if (!value(filter, traverseOptions)) {
|
|
529
|
+
return;
|
|
338
530
|
}
|
|
531
|
+
contracts.push(traverseOptions);
|
|
339
532
|
});
|
|
340
533
|
const errors = [];
|
|
341
534
|
for (const { contract, path } of contracts) {
|
|
342
|
-
const
|
|
535
|
+
const stringPath = path.join(".");
|
|
343
536
|
try {
|
|
344
537
|
const def = contract["~orpc"];
|
|
345
538
|
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
346
539
|
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
540
|
+
let operationObjectRef;
|
|
541
|
+
if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
|
|
542
|
+
operationObjectRef = def.route.spec;
|
|
543
|
+
} else {
|
|
544
|
+
operationObjectRef = {
|
|
545
|
+
operationId: def.route.operationId ?? stringPath,
|
|
546
|
+
summary: def.route.summary,
|
|
547
|
+
description: def.route.description,
|
|
548
|
+
deprecated: def.route.deprecated,
|
|
549
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
550
|
+
};
|
|
551
|
+
await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
552
|
+
await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
|
|
553
|
+
await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema, customErrorResponseBodySchema);
|
|
554
|
+
}
|
|
555
|
+
if (typeof def.route.spec === "function") {
|
|
556
|
+
operationObjectRef = def.route.spec(operationObjectRef);
|
|
557
|
+
}
|
|
357
558
|
doc.paths ??= {};
|
|
358
559
|
doc.paths[httpPath] ??= {};
|
|
359
560
|
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
@@ -362,7 +563,7 @@ class OpenAPIGenerator {
|
|
|
362
563
|
throw e;
|
|
363
564
|
}
|
|
364
565
|
errors.push(
|
|
365
|
-
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${
|
|
566
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
|
|
366
567
|
${e.message}`
|
|
367
568
|
);
|
|
368
569
|
}
|
|
@@ -376,25 +577,102 @@ ${errors.join("\n\n")}`
|
|
|
376
577
|
}
|
|
377
578
|
return this.serializer.serialize(doc)[0];
|
|
378
579
|
}
|
|
379
|
-
async #
|
|
580
|
+
async #resolveCommonSchemas(doc, commonSchemas) {
|
|
581
|
+
let undefinedErrorJsonSchema = {
|
|
582
|
+
type: "object",
|
|
583
|
+
properties: {
|
|
584
|
+
defined: { const: false },
|
|
585
|
+
code: { type: "string" },
|
|
586
|
+
status: { type: "number" },
|
|
587
|
+
message: { type: "string" },
|
|
588
|
+
data: {}
|
|
589
|
+
},
|
|
590
|
+
required: ["defined", "code", "status", "message"]
|
|
591
|
+
};
|
|
592
|
+
const baseSchemaConvertOptions = {};
|
|
593
|
+
if (commonSchemas) {
|
|
594
|
+
baseSchemaConvertOptions.components = [];
|
|
595
|
+
for (const key in commonSchemas) {
|
|
596
|
+
const options = commonSchemas[key];
|
|
597
|
+
if (options.schema === void 0) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const { schema, strategy = "input" } = options;
|
|
601
|
+
const [required, json] = await this.converter.convert(schema, { strategy });
|
|
602
|
+
const allowedStrategies = [strategy];
|
|
603
|
+
if (strategy === "input") {
|
|
604
|
+
const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
|
|
605
|
+
if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
|
|
606
|
+
allowedStrategies.push("output");
|
|
607
|
+
}
|
|
608
|
+
} else if (strategy === "output") {
|
|
609
|
+
const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
|
|
610
|
+
if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
|
|
611
|
+
allowedStrategies.push("input");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
baseSchemaConvertOptions.components.push({
|
|
615
|
+
schema,
|
|
616
|
+
required,
|
|
617
|
+
ref: `#/components/schemas/${key}`,
|
|
618
|
+
allowedStrategies
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
doc.components ??= {};
|
|
622
|
+
doc.components.schemas ??= {};
|
|
623
|
+
for (const key in commonSchemas) {
|
|
624
|
+
const options = commonSchemas[key];
|
|
625
|
+
if (options.schema === void 0) {
|
|
626
|
+
if (options.error === "UndefinedError") {
|
|
627
|
+
doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
|
|
628
|
+
undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
|
|
629
|
+
}
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
const { schema, strategy = "input" } = options;
|
|
633
|
+
const [, json] = await this.converter.convert(
|
|
634
|
+
schema,
|
|
635
|
+
{
|
|
636
|
+
...baseSchemaConvertOptions,
|
|
637
|
+
strategy,
|
|
638
|
+
minStructureDepthForRef: 1
|
|
639
|
+
// not allow use $ref for root schemas
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
doc.components.schemas[key] = toOpenAPISchema(json);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
|
|
646
|
+
}
|
|
647
|
+
async #request(doc, ref, def, baseSchemaConvertOptions) {
|
|
380
648
|
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
381
649
|
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
382
650
|
if (details) {
|
|
383
651
|
ref.requestBody = {
|
|
384
652
|
required: true,
|
|
385
653
|
content: toOpenAPIEventIteratorContent(
|
|
386
|
-
await this.converter.convert(details.yields, { strategy: "input" }),
|
|
387
|
-
await this.converter.convert(details.returns, { strategy: "input" })
|
|
654
|
+
await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
|
|
655
|
+
await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
|
|
388
656
|
)
|
|
389
657
|
};
|
|
390
658
|
return;
|
|
391
659
|
}
|
|
392
660
|
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
393
661
|
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
394
|
-
let [required, schema] = await this.converter.convert(
|
|
662
|
+
let [required, schema] = await this.converter.convert(
|
|
663
|
+
def.inputSchema,
|
|
664
|
+
{
|
|
665
|
+
...baseSchemaConvertOptions,
|
|
666
|
+
strategy: "input"
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
let omitResponseBody = false;
|
|
395
670
|
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
396
671
|
return;
|
|
397
672
|
}
|
|
673
|
+
if (inputStructure === "detailed" || inputStructure === "compact" && (dynamicParams?.length || method === "GET")) {
|
|
674
|
+
schema = simplifyComposedObjectJsonSchemasAndRefs(schema, doc);
|
|
675
|
+
}
|
|
398
676
|
if (inputStructure === "compact") {
|
|
399
677
|
if (dynamicParams?.length) {
|
|
400
678
|
const error2 = new OpenAPIGeneratorError(
|
|
@@ -406,6 +684,7 @@ ${errors.join("\n\n")}`
|
|
|
406
684
|
const [paramsSchema, rest] = separateObjectSchema(schema, dynamicParams);
|
|
407
685
|
schema = rest;
|
|
408
686
|
required = rest.required ? rest.required.length !== 0 : false;
|
|
687
|
+
omitResponseBody = !required && !rest.properties;
|
|
409
688
|
if (!checkParamsSchema(paramsSchema, dynamicParams)) {
|
|
410
689
|
throw error2;
|
|
411
690
|
}
|
|
@@ -420,7 +699,7 @@ ${errors.join("\n\n")}`
|
|
|
420
699
|
}
|
|
421
700
|
ref.parameters ??= [];
|
|
422
701
|
ref.parameters.push(...toOpenAPIParameters(schema, "query"));
|
|
423
|
-
} else {
|
|
702
|
+
} else if (!omitResponseBody) {
|
|
424
703
|
ref.requestBody = {
|
|
425
704
|
required,
|
|
426
705
|
content: toOpenAPIContent(schema)
|
|
@@ -434,7 +713,8 @@ ${errors.join("\n\n")}`
|
|
|
434
713
|
if (!isObjectSchema(schema)) {
|
|
435
714
|
throw error;
|
|
436
715
|
}
|
|
437
|
-
|
|
716
|
+
const resolvedParamSchema = schema.properties?.params !== void 0 ? simplifyComposedObjectJsonSchemasAndRefs(schema.properties.params, doc) : void 0;
|
|
717
|
+
if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
|
|
438
718
|
throw new OpenAPIGeneratorError(
|
|
439
719
|
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
440
720
|
);
|
|
@@ -442,12 +722,13 @@ ${errors.join("\n\n")}`
|
|
|
442
722
|
for (const from of ["params", "query", "headers"]) {
|
|
443
723
|
const fromSchema = schema.properties?.[from];
|
|
444
724
|
if (fromSchema !== void 0) {
|
|
445
|
-
|
|
725
|
+
const resolvedSchema = simplifyComposedObjectJsonSchemasAndRefs(fromSchema, doc);
|
|
726
|
+
if (!isObjectSchema(resolvedSchema)) {
|
|
446
727
|
throw error;
|
|
447
728
|
}
|
|
448
729
|
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
449
730
|
ref.parameters ??= [];
|
|
450
|
-
ref.parameters.push(...toOpenAPIParameters(
|
|
731
|
+
ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
|
|
451
732
|
}
|
|
452
733
|
}
|
|
453
734
|
if (schema.properties?.body !== void 0) {
|
|
@@ -457,7 +738,7 @@ ${errors.join("\n\n")}`
|
|
|
457
738
|
};
|
|
458
739
|
}
|
|
459
740
|
}
|
|
460
|
-
async #successResponse(ref, def) {
|
|
741
|
+
async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
|
|
461
742
|
const outputSchema = def.outputSchema;
|
|
462
743
|
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
463
744
|
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
@@ -468,13 +749,20 @@ ${errors.join("\n\n")}`
|
|
|
468
749
|
ref.responses[status] = {
|
|
469
750
|
description,
|
|
470
751
|
content: toOpenAPIEventIteratorContent(
|
|
471
|
-
await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
472
|
-
await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
752
|
+
await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
|
|
753
|
+
await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
|
|
473
754
|
)
|
|
474
755
|
};
|
|
475
756
|
return;
|
|
476
757
|
}
|
|
477
|
-
const [required, json] = await this.converter.convert(
|
|
758
|
+
const [required, json] = await this.converter.convert(
|
|
759
|
+
outputSchema,
|
|
760
|
+
{
|
|
761
|
+
...baseSchemaConvertOptions,
|
|
762
|
+
strategy: "output",
|
|
763
|
+
minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
|
|
764
|
+
}
|
|
765
|
+
);
|
|
478
766
|
if (outputStructure === "compact") {
|
|
479
767
|
ref.responses ??= {};
|
|
480
768
|
ref.responses[status] = {
|
|
@@ -495,17 +783,19 @@ ${errors.join("\n\n")}`
|
|
|
495
783
|
|
|
496
784
|
But got: ${stringifyJSON(item)}
|
|
497
785
|
`);
|
|
498
|
-
|
|
786
|
+
const simplifiedItem = simplifyComposedObjectJsonSchemasAndRefs(item, doc);
|
|
787
|
+
if (!isObjectSchema(simplifiedItem)) {
|
|
499
788
|
throw error;
|
|
500
789
|
}
|
|
501
790
|
let schemaStatus;
|
|
502
791
|
let schemaDescription;
|
|
503
|
-
if (
|
|
504
|
-
|
|
792
|
+
if (simplifiedItem.properties?.status !== void 0) {
|
|
793
|
+
const statusSchema = resolveOpenAPIJsonSchemaRef(doc, simplifiedItem.properties.status);
|
|
794
|
+
if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
|
|
505
795
|
throw error;
|
|
506
796
|
}
|
|
507
|
-
schemaStatus =
|
|
508
|
-
schemaDescription =
|
|
797
|
+
schemaStatus = statusSchema.const;
|
|
798
|
+
schemaDescription = statusSchema.description;
|
|
509
799
|
}
|
|
510
800
|
const itemStatus = schemaStatus ?? status;
|
|
511
801
|
const itemDescription = schemaDescription ?? description;
|
|
@@ -520,71 +810,64 @@ ${errors.join("\n\n")}`
|
|
|
520
810
|
ref.responses[itemStatus] = {
|
|
521
811
|
description: itemDescription
|
|
522
812
|
};
|
|
523
|
-
if (
|
|
524
|
-
|
|
813
|
+
if (simplifiedItem.properties?.headers !== void 0) {
|
|
814
|
+
const headersSchema = simplifyComposedObjectJsonSchemasAndRefs(simplifiedItem.properties.headers, doc);
|
|
815
|
+
if (!isObjectSchema(headersSchema)) {
|
|
525
816
|
throw error;
|
|
526
817
|
}
|
|
527
|
-
for (const key in
|
|
528
|
-
const headerSchema =
|
|
818
|
+
for (const key in headersSchema.properties) {
|
|
819
|
+
const headerSchema = headersSchema.properties[key];
|
|
529
820
|
if (headerSchema !== void 0) {
|
|
530
821
|
ref.responses[itemStatus].headers ??= {};
|
|
531
822
|
ref.responses[itemStatus].headers[key] = {
|
|
532
823
|
schema: toOpenAPISchema(headerSchema),
|
|
533
|
-
required:
|
|
824
|
+
required: simplifiedItem.required?.includes("headers") && headersSchema.required?.includes(key)
|
|
534
825
|
};
|
|
535
826
|
}
|
|
536
827
|
}
|
|
537
828
|
}
|
|
538
|
-
if (
|
|
829
|
+
if (simplifiedItem.properties?.body !== void 0) {
|
|
539
830
|
ref.responses[itemStatus].content = toOpenAPIContent(
|
|
540
|
-
applySchemaOptionality(
|
|
831
|
+
applySchemaOptionality(simplifiedItem.required?.includes("body") ?? false, simplifiedItem.properties.body)
|
|
541
832
|
);
|
|
542
833
|
}
|
|
543
834
|
}
|
|
544
835
|
}
|
|
545
|
-
async #errorResponse(ref, def) {
|
|
836
|
+
async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema, customErrorResponseBodySchema) {
|
|
546
837
|
const errorMap = def.errorMap;
|
|
547
|
-
const
|
|
838
|
+
const errorResponsesByStatus = {};
|
|
548
839
|
for (const code in errorMap) {
|
|
549
840
|
const config = errorMap[code];
|
|
550
841
|
if (!config) {
|
|
551
842
|
continue;
|
|
552
843
|
}
|
|
553
844
|
const status = fallbackORPCErrorStatus(code, config.status);
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
845
|
+
const defaultMessage = fallbackORPCErrorMessage(code, config.message);
|
|
846
|
+
errorResponsesByStatus[status] ??= { status, definedErrorDefinitions: [], errorSchemaVariants: [] };
|
|
847
|
+
const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
|
|
848
|
+
errorResponsesByStatus[status].definedErrorDefinitions.push([code, defaultMessage, dataRequired, dataSchema]);
|
|
849
|
+
errorResponsesByStatus[status].errorSchemaVariants.push({
|
|
558
850
|
type: "object",
|
|
559
851
|
properties: {
|
|
560
852
|
defined: { const: true },
|
|
561
853
|
code: { const: code },
|
|
562
854
|
status: { const: status },
|
|
563
|
-
message: { type: "string", default:
|
|
855
|
+
message: { type: "string", default: defaultMessage },
|
|
564
856
|
data: dataSchema
|
|
565
857
|
},
|
|
566
858
|
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
567
859
|
});
|
|
568
860
|
}
|
|
569
861
|
ref.responses ??= {};
|
|
570
|
-
for (const
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
862
|
+
for (const statusString in errorResponsesByStatus) {
|
|
863
|
+
const errorResponse = errorResponsesByStatus[statusString];
|
|
864
|
+
const customBodySchema = value(customErrorResponseBodySchema, errorResponse.definedErrorDefinitions, errorResponse.status);
|
|
865
|
+
ref.responses[statusString] = {
|
|
866
|
+
description: statusString,
|
|
867
|
+
content: toOpenAPIContent(customBodySchema ?? {
|
|
575
868
|
oneOf: [
|
|
576
|
-
...
|
|
577
|
-
|
|
578
|
-
type: "object",
|
|
579
|
-
properties: {
|
|
580
|
-
defined: { const: false },
|
|
581
|
-
code: { type: "string" },
|
|
582
|
-
status: { type: "number" },
|
|
583
|
-
message: { type: "string" },
|
|
584
|
-
data: {}
|
|
585
|
-
},
|
|
586
|
-
required: ["defined", "code", "status", "message"]
|
|
587
|
-
}
|
|
869
|
+
...errorResponse.errorSchemaVariants,
|
|
870
|
+
undefinedErrorSchema
|
|
588
871
|
]
|
|
589
872
|
})
|
|
590
873
|
};
|
|
@@ -592,4 +875,4 @@ ${errors.join("\n\n")}`
|
|
|
592
875
|
}
|
|
593
876
|
}
|
|
594
877
|
|
|
595
|
-
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,
|
|
878
|
+
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, isNeverSchema as m, separateObjectSchema as n, filterSchemaBranches as o, applySchemaOptionality as p, expandUnionSchema as q, resolveOpenAPIJsonSchemaRef as r, simplifyComposedObjectJsonSchemasAndRefs as s, toOpenAPIPath as t, expandArrayableSchema as u, isPrimitiveSchema as v };
|