@orpc/openapi 0.0.0-next.85df466 → 0.0.0-next.85e5dff
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 +14 -1
- package/dist/adapters/fetch/index.d.mts +6 -9
- package/dist/adapters/fetch/index.d.ts +6 -9
- package/dist/adapters/fetch/index.mjs +5 -5
- package/dist/adapters/hono/index.d.mts +2 -3
- package/dist/adapters/hono/index.d.ts +2 -3
- package/dist/adapters/hono/index.mjs +5 -5
- package/dist/adapters/next/index.d.mts +2 -3
- package/dist/adapters/next/index.d.ts +2 -3
- package/dist/adapters/next/index.mjs +5 -5
- package/dist/adapters/node/index.d.mts +6 -9
- package/dist/adapters/node/index.d.ts +6 -9
- package/dist/adapters/node/index.mjs +12 -19
- package/dist/adapters/standard/index.d.mts +21 -11
- package/dist/adapters/standard/index.d.ts +21 -11
- package/dist/adapters/standard/index.mjs +3 -3
- package/dist/index.d.mts +85 -141
- package/dist/index.d.ts +85 -141
- package/dist/index.mjs +422 -560
- package/dist/shared/openapi.CGZ7t-VN.mjs +17 -0
- package/dist/shared/openapi.IfmmOyba.d.mts +8 -0
- package/dist/shared/openapi.IfmmOyba.d.ts +8 -0
- package/dist/shared/{openapi.CDsfPHgw.mjs → openapi.sdeu0I7N.mjs} +23 -25
- package/package.json +9 -11
- package/dist/shared/openapi.BHG_gu5Z.mjs +0 -8
- package/dist/shared/openapi.D0VMNR6V.mjs +0 -25
- package/dist/shared/openapi.Dz_6xooR.d.mts +0 -7
- package/dist/shared/openapi.Dz_6xooR.d.ts +0 -7
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import { isProcedure,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { findDeepMatches, isObject, get, omit, group } from '@orpc/shared';
|
|
5
|
-
import { fallbackORPCErrorStatus } from '@orpc/client';
|
|
1
|
+
import { isProcedure, resolveContractProcedures } from '@orpc/server';
|
|
2
|
+
import { fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
|
|
3
|
+
import { toHttpPath } from '@orpc/client/standard';
|
|
6
4
|
import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
export { draft202012 as JSONSchema };
|
|
5
|
+
import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
|
|
6
|
+
import { isObject, findDeepMatches, clone } from '@orpc/shared';
|
|
10
7
|
export { Format as JSONSchemaFormat } from 'json-schema-typed/draft-2020-12';
|
|
11
|
-
import { t as toOpenAPI31RoutePattern } from './shared/openapi.BHG_gu5Z.mjs';
|
|
12
|
-
export { s as standardizeHTTPPath } from './shared/openapi.BHG_gu5Z.mjs';
|
|
13
8
|
|
|
14
9
|
const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
|
|
15
|
-
function
|
|
10
|
+
function customOpenAPIOperation(o, extend) {
|
|
16
11
|
return new Proxy(o, {
|
|
17
12
|
get(target, prop, receiver) {
|
|
18
13
|
if (prop === OPERATION_EXTENDER_SYMBOL) {
|
|
@@ -22,248 +17,258 @@ function setOperationExtender(o, extend) {
|
|
|
22
17
|
}
|
|
23
18
|
});
|
|
24
19
|
}
|
|
25
|
-
function
|
|
20
|
+
function getCustomOpenAPIOperation(o) {
|
|
26
21
|
return o[OPERATION_EXTENDER_SYMBOL];
|
|
27
22
|
}
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
for (const errorItem of Object.values(
|
|
31
|
-
const maybeExtender =
|
|
23
|
+
function applyCustomOpenAPIOperation(operation, contract) {
|
|
24
|
+
const operationCustoms = [];
|
|
25
|
+
for (const errorItem of Object.values(contract["~orpc"].errorMap)) {
|
|
26
|
+
const maybeExtender = errorItem ? getCustomOpenAPIOperation(errorItem) : void 0;
|
|
32
27
|
if (maybeExtender) {
|
|
33
|
-
|
|
28
|
+
operationCustoms.push(maybeExtender);
|
|
34
29
|
}
|
|
35
30
|
}
|
|
36
|
-
if (isProcedure(
|
|
37
|
-
for (const middleware of
|
|
38
|
-
const maybeExtender =
|
|
31
|
+
if (isProcedure(contract)) {
|
|
32
|
+
for (const middleware of contract["~orpc"].middlewares) {
|
|
33
|
+
const maybeExtender = getCustomOpenAPIOperation(middleware);
|
|
39
34
|
if (maybeExtender) {
|
|
40
|
-
|
|
35
|
+
operationCustoms.push(maybeExtender);
|
|
41
36
|
}
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
39
|
let currentOperation = operation;
|
|
45
|
-
for (const
|
|
46
|
-
if (typeof
|
|
47
|
-
currentOperation =
|
|
40
|
+
for (const custom of operationCustoms) {
|
|
41
|
+
if (typeof custom === "function") {
|
|
42
|
+
currentOperation = custom(currentOperation, contract);
|
|
48
43
|
} else {
|
|
49
44
|
currentOperation = {
|
|
50
45
|
...currentOperation,
|
|
51
|
-
...
|
|
46
|
+
...custom
|
|
52
47
|
};
|
|
53
48
|
}
|
|
54
49
|
}
|
|
55
50
|
return currentOperation;
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
53
|
+
const LOGIC_KEYWORDS = [
|
|
54
|
+
"$dynamicRef",
|
|
55
|
+
"$ref",
|
|
56
|
+
"additionalItems",
|
|
57
|
+
"additionalProperties",
|
|
58
|
+
"allOf",
|
|
59
|
+
"anyOf",
|
|
60
|
+
"const",
|
|
61
|
+
"contains",
|
|
62
|
+
"contentEncoding",
|
|
63
|
+
"contentMediaType",
|
|
64
|
+
"contentSchema",
|
|
65
|
+
"dependencies",
|
|
66
|
+
"dependentRequired",
|
|
67
|
+
"dependentSchemas",
|
|
68
|
+
"else",
|
|
69
|
+
"enum",
|
|
70
|
+
"exclusiveMaximum",
|
|
71
|
+
"exclusiveMinimum",
|
|
72
|
+
"format",
|
|
73
|
+
"if",
|
|
74
|
+
"items",
|
|
75
|
+
"maxContains",
|
|
76
|
+
"maximum",
|
|
77
|
+
"maxItems",
|
|
78
|
+
"maxLength",
|
|
79
|
+
"maxProperties",
|
|
80
|
+
"minContains",
|
|
81
|
+
"minimum",
|
|
82
|
+
"minItems",
|
|
83
|
+
"minLength",
|
|
84
|
+
"minProperties",
|
|
85
|
+
"multipleOf",
|
|
86
|
+
"not",
|
|
87
|
+
"oneOf",
|
|
88
|
+
"pattern",
|
|
89
|
+
"patternProperties",
|
|
90
|
+
"prefixItems",
|
|
91
|
+
"properties",
|
|
92
|
+
"propertyNames",
|
|
93
|
+
"required",
|
|
94
|
+
"then",
|
|
95
|
+
"type",
|
|
96
|
+
"unevaluatedItems",
|
|
97
|
+
"unevaluatedProperties",
|
|
98
|
+
"uniqueItems"
|
|
99
|
+
];
|
|
82
100
|
|
|
83
|
-
|
|
101
|
+
function isFileSchema(schema) {
|
|
102
|
+
return isObject(schema) && schema.type === "string" && typeof schema.contentMediaType === "string";
|
|
84
103
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
function isObjectSchema(schema) {
|
|
105
|
+
return isObject(schema) && schema.type === "object";
|
|
106
|
+
}
|
|
107
|
+
function isAnySchema(schema) {
|
|
108
|
+
if (schema === true) {
|
|
109
|
+
return true;
|
|
91
110
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route?.method);
|
|
95
|
-
const httpPath = contract["~orpc"].route?.path;
|
|
96
|
-
if (this.schemaUtils.isAnySchema(inputSchema)) {
|
|
97
|
-
return {
|
|
98
|
-
paramsSchema: void 0,
|
|
99
|
-
querySchema: void 0,
|
|
100
|
-
headersSchema: void 0,
|
|
101
|
-
bodySchema: void 0
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
if (structure === "detailed") {
|
|
105
|
-
return this.parseDetailedSchema(inputSchema);
|
|
106
|
-
} else {
|
|
107
|
-
return this.parseCompactSchema(inputSchema, method, httpPath);
|
|
108
|
-
}
|
|
111
|
+
if (Object.keys(schema).every((k) => !LOGIC_KEYWORDS.includes(k))) {
|
|
112
|
+
return true;
|
|
109
113
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
if (querySchema !== void 0 && this.schemaUtils.isAnySchema(querySchema)) {
|
|
128
|
-
querySchema = void 0;
|
|
129
|
-
}
|
|
130
|
-
if (querySchema !== void 0 && !this.schemaUtils.isObjectSchema(querySchema)) {
|
|
131
|
-
throw new OpenAPIError(`When input structure is 'detailed', query schema in input schema must be an object.`);
|
|
132
|
-
}
|
|
133
|
-
if (headersSchema !== void 0 && this.schemaUtils.isAnySchema(headersSchema)) {
|
|
134
|
-
headersSchema = void 0;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
function separateObjectSchema(schema, separatedProperties) {
|
|
117
|
+
if (Object.keys(schema).some((k) => k !== "type" && k !== "properties" && k !== "required" && LOGIC_KEYWORDS.includes(k))) {
|
|
118
|
+
return [{ type: "object" }, schema];
|
|
119
|
+
}
|
|
120
|
+
const matched = { ...schema };
|
|
121
|
+
const rest = { ...schema };
|
|
122
|
+
matched.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => separatedProperties.includes(key)).reduce((acc, [key, value]) => {
|
|
123
|
+
acc[key] = value;
|
|
124
|
+
return acc;
|
|
125
|
+
}, {});
|
|
126
|
+
matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
|
|
127
|
+
matched.examples = schema.examples?.map((example) => {
|
|
128
|
+
if (!isObject(example)) {
|
|
129
|
+
return example;
|
|
135
130
|
}
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
return Object.entries(example).reduce((acc, [key, value]) => {
|
|
132
|
+
if (separatedProperties.includes(key)) {
|
|
133
|
+
acc[key] = value;
|
|
134
|
+
}
|
|
135
|
+
return acc;
|
|
136
|
+
}, {});
|
|
137
|
+
});
|
|
138
|
+
rest.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => !separatedProperties.includes(key)).reduce((acc, [key, value]) => {
|
|
139
|
+
acc[key] = value;
|
|
140
|
+
return acc;
|
|
141
|
+
}, {});
|
|
142
|
+
rest.required = schema.required?.filter((key) => !separatedProperties.includes(key));
|
|
143
|
+
rest.examples = schema.examples?.map((example) => {
|
|
144
|
+
if (!isObject(example)) {
|
|
145
|
+
return example;
|
|
138
146
|
}
|
|
139
|
-
return
|
|
147
|
+
return Object.entries(example).reduce((acc, [key, value]) => {
|
|
148
|
+
if (!separatedProperties.includes(key)) {
|
|
149
|
+
acc[key] = value;
|
|
150
|
+
}
|
|
151
|
+
return acc;
|
|
152
|
+
}, {});
|
|
153
|
+
});
|
|
154
|
+
return [matched, rest];
|
|
155
|
+
}
|
|
156
|
+
function filterSchemaBranches(schema, check, matches = []) {
|
|
157
|
+
if (check(schema)) {
|
|
158
|
+
matches.push(schema);
|
|
159
|
+
return [matches, void 0];
|
|
140
160
|
}
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
if (isObject(schema)) {
|
|
162
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
163
|
+
if (schema[keyword] && Object.keys(schema).every(
|
|
164
|
+
(k) => k === keyword || !LOGIC_KEYWORDS.includes(k)
|
|
165
|
+
)) {
|
|
166
|
+
const rest = schema[keyword].map((s) => filterSchemaBranches(s, check, matches)[1]).filter((v) => !!v);
|
|
167
|
+
if (rest.length === 1 && typeof rest[0] === "object") {
|
|
168
|
+
return [matches, { ...schema, [keyword]: void 0, ...rest[0] }];
|
|
148
169
|
}
|
|
149
|
-
|
|
150
|
-
throw new OpenAPIError(`When input structure is 'compact' and method is 'GET', input schema must be an object.`);
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
paramsSchema: void 0,
|
|
154
|
-
querySchema,
|
|
155
|
-
headersSchema: void 0,
|
|
156
|
-
bodySchema: void 0
|
|
157
|
-
};
|
|
170
|
+
return [matches, { ...schema, [keyword]: rest }];
|
|
158
171
|
}
|
|
159
|
-
return {
|
|
160
|
-
paramsSchema: void 0,
|
|
161
|
-
querySchema: void 0,
|
|
162
|
-
headersSchema: void 0,
|
|
163
|
-
bodySchema: inputSchema
|
|
164
|
-
};
|
|
165
172
|
}
|
|
166
|
-
if (!this.schemaUtils.isObjectSchema(inputSchema)) {
|
|
167
|
-
throw new OpenAPIError(`When input structure is 'compact' and path has dynamic parameters, input schema must be an object.`);
|
|
168
|
-
}
|
|
169
|
-
const [params, rest] = this.schemaUtils.separateObjectSchema(inputSchema, dynamic.map((v) => v.name));
|
|
170
|
-
return {
|
|
171
|
-
paramsSchema: params,
|
|
172
|
-
querySchema: method === "GET" ? rest : void 0,
|
|
173
|
-
headersSchema: void 0,
|
|
174
|
-
bodySchema: method !== "GET" ? rest : void 0
|
|
175
|
-
};
|
|
176
173
|
}
|
|
174
|
+
return [matches, schema];
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
function toOpenAPIPath(path) {
|
|
178
|
+
return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
|
|
179
|
+
}
|
|
180
|
+
function toOpenAPIMethod(method) {
|
|
181
|
+
return method.toLocaleLowerCase();
|
|
182
|
+
}
|
|
183
|
+
function toOpenAPIContent(schema) {
|
|
184
|
+
const content = {};
|
|
185
|
+
const [matches, restSchema] = filterSchemaBranches(schema, isFileSchema);
|
|
186
|
+
for (const file of matches) {
|
|
187
|
+
content[file.contentMediaType] = {
|
|
188
|
+
schema: toOpenAPISchema(file)
|
|
189
|
+
};
|
|
183
190
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
if (restSchema !== void 0) {
|
|
192
|
+
content["application/json"] = {
|
|
193
|
+
schema: toOpenAPISchema(restSchema)
|
|
194
|
+
};
|
|
195
|
+
const isStillHasFileSchema = findDeepMatches((v) => isObject(v) && isFileSchema(v), restSchema).values.length > 0;
|
|
196
|
+
if (isStillHasFileSchema) {
|
|
197
|
+
content["multipart/form-data"] = {
|
|
198
|
+
schema: toOpenAPISchema(restSchema)
|
|
190
199
|
};
|
|
191
200
|
}
|
|
192
|
-
if (structure === "detailed") {
|
|
193
|
-
return this.parseDetailedSchema(outputSchema);
|
|
194
|
-
} else {
|
|
195
|
-
return this.parseCompactSchema(outputSchema);
|
|
196
|
-
}
|
|
197
201
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
202
|
+
return content;
|
|
203
|
+
}
|
|
204
|
+
function toOpenAPIEventIteratorContent([yieldsRequired, yieldsSchema], [returnsRequired, returnsSchema]) {
|
|
205
|
+
return {
|
|
206
|
+
"text/event-stream": {
|
|
207
|
+
schema: toOpenAPISchema({
|
|
208
|
+
oneOf: [
|
|
209
|
+
{
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
event: { const: "message" },
|
|
213
|
+
data: yieldsSchema,
|
|
214
|
+
id: { type: "string" },
|
|
215
|
+
retry: { type: "number" }
|
|
216
|
+
},
|
|
217
|
+
required: yieldsRequired ? ["event", "data"] : ["event"]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: {
|
|
222
|
+
event: { const: "done" },
|
|
223
|
+
data: returnsSchema,
|
|
224
|
+
id: { type: "string" },
|
|
225
|
+
retry: { type: "number" }
|
|
226
|
+
},
|
|
227
|
+
required: returnsRequired ? ["event", "data"] : ["event"]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
event: { const: "error" },
|
|
233
|
+
data: {},
|
|
234
|
+
id: { type: "string" },
|
|
235
|
+
retry: { type: "number" }
|
|
236
|
+
},
|
|
237
|
+
required: ["event"]
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
})
|
|
212
241
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function toOpenAPIParameters(schema, parameterIn) {
|
|
245
|
+
const parameters = [];
|
|
246
|
+
for (const key in schema.properties) {
|
|
247
|
+
const keySchema = schema.properties[key];
|
|
248
|
+
parameters.push({
|
|
249
|
+
name: key,
|
|
250
|
+
in: parameterIn,
|
|
251
|
+
required: schema.required?.includes(key),
|
|
252
|
+
style: parameterIn === "query" ? "deepObject" : void 0,
|
|
253
|
+
explode: parameterIn === "query" ? true : void 0,
|
|
254
|
+
schema: toOpenAPISchema(keySchema)
|
|
255
|
+
});
|
|
220
256
|
}
|
|
257
|
+
return parameters;
|
|
221
258
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const schema = jsonSchema.properties[name];
|
|
228
|
-
const paramExamples = jsonSchema.examples?.filter((example) => {
|
|
229
|
-
return isObject(example) && name in example;
|
|
230
|
-
}).map((example) => {
|
|
231
|
-
return example[name];
|
|
232
|
-
});
|
|
233
|
-
const paramSchema = {
|
|
234
|
-
examples: paramExamples?.length ? paramExamples : void 0,
|
|
235
|
-
...schema === true ? {} : schema === false ? { not: {} } : schema
|
|
236
|
-
};
|
|
237
|
-
const paramExample = get(options?.example, [name]);
|
|
238
|
-
parameters.push({
|
|
239
|
-
name,
|
|
240
|
-
in: paramIn,
|
|
241
|
-
required: typeof options?.required === "boolean" ? options.required : jsonSchema.required?.includes(name) ?? false,
|
|
242
|
-
schema: paramSchema,
|
|
243
|
-
example: paramExample,
|
|
244
|
-
style: options?.style
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
return parameters;
|
|
259
|
+
function checkParamsSchema(schema, params) {
|
|
260
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
261
|
+
const required = schema.required ?? [];
|
|
262
|
+
if (properties.length !== params.length || properties.some((v) => !params.includes(v))) {
|
|
263
|
+
return false;
|
|
248
264
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const headersObject = {};
|
|
252
|
-
for (const param of parameters) {
|
|
253
|
-
headersObject[param.name] = omit(param, ["name", "in"]);
|
|
254
|
-
}
|
|
255
|
-
return headersObject;
|
|
265
|
+
if (required.length !== params.length || required.some((v) => !params.includes(v))) {
|
|
266
|
+
return false;
|
|
256
267
|
}
|
|
268
|
+
return true;
|
|
257
269
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
parseDynamicParams(path) {
|
|
261
|
-
const raws = path.match(/\{([^}]+)\}/g) ?? [];
|
|
262
|
-
return raws.map((raw) => {
|
|
263
|
-
const name = raw.slice(1, -1).split(":")[0];
|
|
264
|
-
return { name, raw };
|
|
265
|
-
});
|
|
266
|
-
}
|
|
270
|
+
function toOpenAPISchema(schema) {
|
|
271
|
+
return schema === true ? {} : schema === false ? { not: {} } : schema;
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
class CompositeSchemaConverter {
|
|
@@ -271,327 +276,229 @@ class CompositeSchemaConverter {
|
|
|
271
276
|
constructor(converters) {
|
|
272
277
|
this.converters = converters;
|
|
273
278
|
}
|
|
274
|
-
condition() {
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
279
|
convert(schema, options) {
|
|
278
280
|
for (const converter of this.converters) {
|
|
279
281
|
if (converter.condition(schema, options)) {
|
|
280
282
|
return converter.convert(schema, options);
|
|
281
283
|
}
|
|
282
284
|
}
|
|
283
|
-
return {};
|
|
285
|
+
return [false, {}];
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// Value Keywords
|
|
296
|
-
"default",
|
|
297
|
-
"deprecated",
|
|
298
|
-
"examples",
|
|
299
|
-
// Metadata Keywords
|
|
300
|
-
"$schema",
|
|
301
|
-
"definitions",
|
|
302
|
-
// Legacy, but still used
|
|
303
|
-
"readOnly",
|
|
304
|
-
"writeOnly",
|
|
305
|
-
// Display and UI Hints
|
|
306
|
-
"contentMediaType",
|
|
307
|
-
"contentEncoding",
|
|
308
|
-
"format",
|
|
309
|
-
// Custom Extensions
|
|
310
|
-
"$vocabulary",
|
|
311
|
-
"$dynamicAnchor",
|
|
312
|
-
"$dynamicRef"
|
|
313
|
-
];
|
|
314
|
-
|
|
315
|
-
class SchemaUtils {
|
|
316
|
-
isFileSchema(schema) {
|
|
317
|
-
return isObject(schema) && schema.type === "string" && typeof schema.contentMediaType === "string";
|
|
318
|
-
}
|
|
319
|
-
isObjectSchema(schema) {
|
|
320
|
-
return isObject(schema) && schema.type === "object";
|
|
321
|
-
}
|
|
322
|
-
isAnySchema(schema) {
|
|
323
|
-
return schema === true || Object.keys(schema).filter((key) => !NON_LOGIC_KEYWORDS.includes(key)).length === 0;
|
|
289
|
+
class OpenAPIGeneratorError extends Error {
|
|
290
|
+
}
|
|
291
|
+
class OpenAPIGenerator {
|
|
292
|
+
serializer;
|
|
293
|
+
converter;
|
|
294
|
+
constructor(options = {}) {
|
|
295
|
+
this.serializer = new StandardOpenAPIJsonSerializer(options);
|
|
296
|
+
this.converter = new CompositeSchemaConverter(options.schemaConverters ?? []);
|
|
324
297
|
}
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
298
|
+
async generate(router, base) {
|
|
299
|
+
const doc = clone(base);
|
|
300
|
+
doc.openapi = "3.1.1";
|
|
301
|
+
const errors = [];
|
|
302
|
+
await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
|
|
303
|
+
const operationId = path.join(".");
|
|
304
|
+
try {
|
|
305
|
+
const def = contract["~orpc"];
|
|
306
|
+
const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
|
|
307
|
+
const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
|
|
308
|
+
const operationObjectRef = {
|
|
309
|
+
operationId,
|
|
310
|
+
summary: def.route.summary,
|
|
311
|
+
description: def.route.description,
|
|
312
|
+
deprecated: def.route.deprecated,
|
|
313
|
+
tags: def.route.tags?.map((tag) => tag)
|
|
314
|
+
};
|
|
315
|
+
this.#request(operationObjectRef, def);
|
|
316
|
+
this.#successResponse(operationObjectRef, def);
|
|
317
|
+
this.#errorResponse(operationObjectRef, def);
|
|
318
|
+
doc.paths ??= {};
|
|
319
|
+
doc.paths[httpPath] ??= {};
|
|
320
|
+
doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
if (!(e instanceof OpenAPIGeneratorError)) {
|
|
323
|
+
throw e;
|
|
324
|
+
}
|
|
325
|
+
errors.push(
|
|
326
|
+
`[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${operationId}
|
|
327
|
+
${e.message}`
|
|
328
|
+
);
|
|
329
329
|
}
|
|
330
|
-
return Object.keys(schema2).filter((key) => !NON_LOGIC_KEYWORDS.includes(key)).length === 0;
|
|
331
330
|
});
|
|
332
|
-
|
|
331
|
+
if (errors.length) {
|
|
332
|
+
throw new OpenAPIGeneratorError(
|
|
333
|
+
`Some error occurred during OpenAPI generation:
|
|
334
|
+
|
|
335
|
+
${errors.join("\n\n")}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return this.serializer.serialize(doc)[0];
|
|
333
339
|
}
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
340
|
+
#request(ref, def) {
|
|
341
|
+
const method = fallbackContractConfig("defaultMethod", def.route.method);
|
|
342
|
+
const details = getEventIteratorSchemaDetails(def.inputSchema);
|
|
343
|
+
if (details) {
|
|
344
|
+
ref.requestBody = {
|
|
345
|
+
required: true,
|
|
346
|
+
content: toOpenAPIEventIteratorContent(
|
|
347
|
+
this.converter.convert(details.yields, { strategy: "input" }),
|
|
348
|
+
this.converter.convert(details.returns, { strategy: "input" })
|
|
349
|
+
)
|
|
350
|
+
};
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
|
|
354
|
+
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
|
|
355
|
+
let [required, schema] = this.converter.convert(def.inputSchema, { strategy: "input" });
|
|
356
|
+
if (isAnySchema(schema) && !dynamicParams?.length) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (inputStructure === "compact") {
|
|
360
|
+
if (dynamicParams?.length) {
|
|
361
|
+
const error2 = new OpenAPIGeneratorError(
|
|
362
|
+
'When input structure is "compact", and path has dynamic params, input schema must be an object with all dynamic params as required.'
|
|
363
|
+
);
|
|
364
|
+
if (!isObjectSchema(schema)) {
|
|
365
|
+
throw error2;
|
|
366
|
+
}
|
|
367
|
+
const [paramsSchema, rest] = separateObjectSchema(schema, dynamicParams);
|
|
368
|
+
schema = rest;
|
|
369
|
+
required = rest.required ? rest.required.length !== 0 : false;
|
|
370
|
+
if (!checkParamsSchema(paramsSchema, dynamicParams)) {
|
|
371
|
+
throw error2;
|
|
372
|
+
}
|
|
373
|
+
ref.parameters ??= [];
|
|
374
|
+
ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
|
|
345
375
|
}
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
|
|
376
|
+
if (method === "GET") {
|
|
377
|
+
if (!isObjectSchema(schema)) {
|
|
378
|
+
throw new OpenAPIGeneratorError(
|
|
379
|
+
'When method is "GET", input schema must satisfy: object | any | unknown'
|
|
380
|
+
);
|
|
349
381
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
rest.required = schema.required?.filter((key) => !separatedProperties.includes(key));
|
|
358
|
-
rest.examples = schema.examples?.map((example) => {
|
|
359
|
-
if (!isObject(example)) {
|
|
360
|
-
return example;
|
|
382
|
+
ref.parameters ??= [];
|
|
383
|
+
ref.parameters.push(...toOpenAPIParameters(schema, "query"));
|
|
384
|
+
} else {
|
|
385
|
+
ref.requestBody = {
|
|
386
|
+
required,
|
|
387
|
+
content: toOpenAPIContent(schema)
|
|
388
|
+
};
|
|
361
389
|
}
|
|
362
|
-
return
|
|
363
|
-
|
|
364
|
-
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const error = new OpenAPIGeneratorError(
|
|
393
|
+
'When input structure is "detailed", input schema must satisfy: { params?: Record<string, unknown>, query?: Record<string, unknown>, headers?: Record<string, unknown>, body?: unknown }'
|
|
394
|
+
);
|
|
395
|
+
if (!isObjectSchema(schema)) {
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
|
|
399
|
+
throw new OpenAPIGeneratorError(
|
|
400
|
+
'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
for (const from of ["params", "query", "headers"]) {
|
|
404
|
+
const fromSchema = schema.properties?.[from];
|
|
405
|
+
if (fromSchema !== void 0) {
|
|
406
|
+
if (!isObjectSchema(fromSchema)) {
|
|
407
|
+
throw error;
|
|
365
408
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
409
|
+
const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
|
|
410
|
+
ref.parameters ??= [];
|
|
411
|
+
ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (schema.properties?.body !== void 0) {
|
|
415
|
+
ref.requestBody = {
|
|
416
|
+
required: schema.required?.includes("body"),
|
|
417
|
+
content: toOpenAPIContent(schema.properties.body)
|
|
418
|
+
};
|
|
419
|
+
}
|
|
370
420
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
421
|
+
#successResponse(ref, def) {
|
|
422
|
+
const outputSchema = def.outputSchema;
|
|
423
|
+
const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
|
|
424
|
+
const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
|
|
425
|
+
const eventIteratorSchemaDetails = getEventIteratorSchemaDetails(outputSchema);
|
|
426
|
+
const outputStructure = fallbackContractConfig("defaultOutputStructure", def.route.outputStructure);
|
|
427
|
+
if (eventIteratorSchemaDetails) {
|
|
428
|
+
ref.responses ??= {};
|
|
429
|
+
ref.responses[status] = {
|
|
430
|
+
description,
|
|
431
|
+
content: toOpenAPIEventIteratorContent(
|
|
432
|
+
this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
|
|
433
|
+
this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
|
|
434
|
+
)
|
|
435
|
+
};
|
|
436
|
+
return;
|
|
375
437
|
}
|
|
376
|
-
|
|
377
|
-
|
|
438
|
+
const [_, json] = this.converter.convert(outputSchema, { strategy: "output" });
|
|
439
|
+
ref.responses ??= {};
|
|
440
|
+
ref.responses[status] = {
|
|
441
|
+
description
|
|
442
|
+
};
|
|
443
|
+
if (outputStructure === "compact") {
|
|
444
|
+
ref.responses[status].content = toOpenAPIContent(json);
|
|
445
|
+
return;
|
|
378
446
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return [matches, { ...schema, anyOf: void 0, ...anyOf[0] }];
|
|
385
|
-
}
|
|
386
|
-
return [matches, { ...schema, anyOf }];
|
|
447
|
+
const error = new OpenAPIGeneratorError(
|
|
448
|
+
'When output structure is "detailed", output schema must satisfy: { headers?: Record<string, unknown>, body?: unknown }'
|
|
449
|
+
);
|
|
450
|
+
if (!isObjectSchema(json)) {
|
|
451
|
+
throw error;
|
|
387
452
|
}
|
|
388
|
-
if (
|
|
389
|
-
(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
453
|
+
if (json.properties?.headers !== void 0) {
|
|
454
|
+
if (!isObjectSchema(json.properties.headers)) {
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
for (const key in json.properties.headers.properties) {
|
|
458
|
+
ref.responses[status].headers ??= {};
|
|
459
|
+
ref.responses[status].headers[key] = {
|
|
460
|
+
schema: toOpenAPISchema(json.properties.headers.properties[key]),
|
|
461
|
+
required: json.properties.headers.required?.includes(key)
|
|
462
|
+
};
|
|
394
463
|
}
|
|
395
|
-
return [matches, { ...schema, oneOf }];
|
|
396
464
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
class OpenAPIGenerator {
|
|
402
|
-
contentBuilder;
|
|
403
|
-
parametersBuilder;
|
|
404
|
-
schemaConverter;
|
|
405
|
-
schemaUtils;
|
|
406
|
-
jsonSerializer;
|
|
407
|
-
pathParser;
|
|
408
|
-
inputStructureParser;
|
|
409
|
-
outputStructureParser;
|
|
410
|
-
errorHandlerStrategy;
|
|
411
|
-
ignoreUndefinedPathProcedures;
|
|
412
|
-
considerMissingTagDefinitionAsError;
|
|
413
|
-
strictErrorResponses;
|
|
414
|
-
constructor(options) {
|
|
415
|
-
this.parametersBuilder = options?.parametersBuilder ?? new OpenAPIParametersBuilder();
|
|
416
|
-
this.schemaConverter = new CompositeSchemaConverter(options?.schemaConverters ?? []);
|
|
417
|
-
this.schemaUtils = options?.schemaUtils ?? new SchemaUtils();
|
|
418
|
-
this.jsonSerializer = options?.jsonSerializer ?? new OpenAPIJsonSerializer();
|
|
419
|
-
this.contentBuilder = options?.contentBuilder ?? new OpenAPIContentBuilder(this.schemaUtils);
|
|
420
|
-
this.pathParser = new OpenAPIPathParser();
|
|
421
|
-
this.inputStructureParser = options?.inputStructureParser ?? new OpenAPIInputStructureParser(this.schemaConverter, this.schemaUtils, this.pathParser);
|
|
422
|
-
this.outputStructureParser = options?.outputStructureParser ?? new OpenAPIOutputStructureParser(this.schemaConverter, this.schemaUtils);
|
|
423
|
-
this.errorHandlerStrategy = options?.errorHandlerStrategy ?? "throw";
|
|
424
|
-
this.ignoreUndefinedPathProcedures = options?.ignoreUndefinedPathProcedures ?? false;
|
|
425
|
-
this.considerMissingTagDefinitionAsError = options?.considerMissingTagDefinitionAsError ?? false;
|
|
426
|
-
this.strictErrorResponses = options?.strictErrorResponses ?? true;
|
|
465
|
+
if (json.properties?.body !== void 0) {
|
|
466
|
+
ref.responses[status].content = toOpenAPIContent(json.properties.body);
|
|
467
|
+
}
|
|
427
468
|
}
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
},
|
|
462
|
-
required: ["event", "data"]
|
|
463
|
-
},
|
|
464
|
-
{
|
|
465
|
-
type: "object",
|
|
466
|
-
properties: {
|
|
467
|
-
event: { type: "string", const: "done" },
|
|
468
|
-
data: this.schemaConverter.convert(eventIteratorSchemaDetails.returns, { strategy: "input" }),
|
|
469
|
-
id: { type: "string" },
|
|
470
|
-
retry: { type: "number" }
|
|
471
|
-
},
|
|
472
|
-
required: ["event", "data"]
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
type: "object",
|
|
476
|
-
properties: {
|
|
477
|
-
event: { type: "string", const: "error" },
|
|
478
|
-
data: {},
|
|
479
|
-
id: { type: "string" },
|
|
480
|
-
retry: { type: "number" }
|
|
481
|
-
},
|
|
482
|
-
required: ["event", "data"]
|
|
483
|
-
}
|
|
484
|
-
]
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
return { requestBody: requestBody3, parameters: [] };
|
|
490
|
-
}
|
|
491
|
-
const inputStructure = fallbackContractConfig("defaultInputStructure", def.route?.inputStructure);
|
|
492
|
-
const { paramsSchema, querySchema, headersSchema, bodySchema } = this.inputStructureParser.parse(contract, inputStructure);
|
|
493
|
-
const params = paramsSchema ? this.parametersBuilder.build("path", paramsSchema, {
|
|
494
|
-
required: true
|
|
495
|
-
}) : [];
|
|
496
|
-
const query = querySchema ? this.parametersBuilder.build("query", querySchema) : [];
|
|
497
|
-
const headers = headersSchema ? this.parametersBuilder.build("header", headersSchema) : [];
|
|
498
|
-
const parameters2 = [...params, ...query, ...headers];
|
|
499
|
-
const requestBody2 = bodySchema !== void 0 ? {
|
|
500
|
-
required: this.schemaUtils.isUndefinableSchema(bodySchema),
|
|
501
|
-
content: this.contentBuilder.build(bodySchema)
|
|
502
|
-
} : void 0;
|
|
503
|
-
return { parameters: parameters2, requestBody: requestBody2 };
|
|
504
|
-
})();
|
|
505
|
-
const { responses } = (() => {
|
|
506
|
-
const eventIteratorSchemaDetails = getEventIteratorSchemaDetails(def.outputSchema);
|
|
507
|
-
if (eventIteratorSchemaDetails) {
|
|
508
|
-
const responses3 = {};
|
|
509
|
-
responses3[fallbackContractConfig("defaultSuccessStatus", def.route?.successStatus)] = {
|
|
510
|
-
description: fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription),
|
|
511
|
-
content: {
|
|
512
|
-
"text/event-stream": {
|
|
513
|
-
schema: {
|
|
514
|
-
oneOf: [
|
|
515
|
-
{
|
|
516
|
-
type: "object",
|
|
517
|
-
properties: {
|
|
518
|
-
event: { type: "string", const: "message" },
|
|
519
|
-
data: this.schemaConverter.convert(eventIteratorSchemaDetails.yields, { strategy: "input" }),
|
|
520
|
-
id: { type: "string" },
|
|
521
|
-
retry: { type: "number" }
|
|
522
|
-
},
|
|
523
|
-
required: ["event", "data"]
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
type: "object",
|
|
527
|
-
properties: {
|
|
528
|
-
event: { type: "string", const: "done" },
|
|
529
|
-
data: this.schemaConverter.convert(eventIteratorSchemaDetails.returns, { strategy: "input" }),
|
|
530
|
-
id: { type: "string" },
|
|
531
|
-
retry: { type: "number" }
|
|
532
|
-
},
|
|
533
|
-
required: ["event", "data"]
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
type: "object",
|
|
537
|
-
properties: {
|
|
538
|
-
event: { type: "string", const: "error" },
|
|
539
|
-
data: {},
|
|
540
|
-
id: { type: "string" },
|
|
541
|
-
retry: { type: "number" }
|
|
542
|
-
},
|
|
543
|
-
required: ["event", "data"]
|
|
544
|
-
}
|
|
545
|
-
]
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
return { responses: responses3 };
|
|
551
|
-
}
|
|
552
|
-
const outputStructure = fallbackContractConfig("defaultOutputStructure", def.route?.outputStructure);
|
|
553
|
-
const { headersSchema: resHeadersSchema, bodySchema: resBodySchema } = this.outputStructureParser.parse(contract, outputStructure);
|
|
554
|
-
const responses2 = {};
|
|
555
|
-
responses2[fallbackContractConfig("defaultSuccessStatus", def.route?.successStatus)] = {
|
|
556
|
-
description: fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription),
|
|
557
|
-
content: resBodySchema !== void 0 ? this.contentBuilder.build(resBodySchema) : void 0,
|
|
558
|
-
headers: resHeadersSchema !== void 0 ? this.parametersBuilder.buildHeadersObject(resHeadersSchema) : void 0
|
|
559
|
-
};
|
|
560
|
-
return { responses: responses2 };
|
|
561
|
-
})();
|
|
562
|
-
const errors = group(Object.entries(def.errorMap ?? {}).filter(([_, config]) => config).map(([code, config]) => ({
|
|
563
|
-
...config,
|
|
564
|
-
code,
|
|
565
|
-
status: fallbackORPCErrorStatus(code, config?.status)
|
|
566
|
-
})), (error) => error.status);
|
|
567
|
-
for (const status in errors) {
|
|
568
|
-
const configs = errors[status];
|
|
569
|
-
if (!configs || configs.length === 0) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
const schemas = configs.map(({ data, code, message }) => {
|
|
573
|
-
const json = {
|
|
574
|
-
type: "object",
|
|
575
|
-
properties: {
|
|
576
|
-
defined: { const: true },
|
|
577
|
-
code: { const: code },
|
|
578
|
-
status: { const: Number(status) },
|
|
579
|
-
message: { type: "string", default: message },
|
|
580
|
-
data: {}
|
|
581
|
-
},
|
|
582
|
-
required: ["defined", "code", "status", "message"]
|
|
583
|
-
};
|
|
584
|
-
if (data) {
|
|
585
|
-
const dataJson = this.schemaConverter.convert(data, { strategy: "output" });
|
|
586
|
-
json.properties.data = dataJson;
|
|
587
|
-
if (!this.schemaUtils.isUndefinableSchema(dataJson)) {
|
|
588
|
-
json.required.push("data");
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
return json;
|
|
592
|
-
});
|
|
593
|
-
if (this.strictErrorResponses) {
|
|
594
|
-
schemas.push({
|
|
469
|
+
#errorResponse(ref, def) {
|
|
470
|
+
const errorMap = def.errorMap;
|
|
471
|
+
const errors = {};
|
|
472
|
+
for (const code in errorMap) {
|
|
473
|
+
const config = errorMap[code];
|
|
474
|
+
if (!config) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const status = fallbackORPCErrorStatus(code, config.status);
|
|
478
|
+
const message = fallbackORPCErrorMessage(code, config.message);
|
|
479
|
+
const [dataRequired, dataSchema] = this.converter.convert(config.data, { strategy: "output" });
|
|
480
|
+
errors[status] ??= [];
|
|
481
|
+
errors[status].push({
|
|
482
|
+
type: "object",
|
|
483
|
+
properties: {
|
|
484
|
+
defined: { const: true },
|
|
485
|
+
code: { const: code },
|
|
486
|
+
status: { const: status },
|
|
487
|
+
message: { type: "string", default: message },
|
|
488
|
+
data: dataSchema
|
|
489
|
+
},
|
|
490
|
+
required: dataRequired ? ["defined", "code", "status", "message", "data"] : ["defined", "code", "status", "message"]
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
ref.responses ??= {};
|
|
494
|
+
for (const status in errors) {
|
|
495
|
+
const schemas = errors[status];
|
|
496
|
+
ref.responses[status] = {
|
|
497
|
+
description: status,
|
|
498
|
+
content: toOpenAPIContent({
|
|
499
|
+
oneOf: [
|
|
500
|
+
...schemas,
|
|
501
|
+
{
|
|
595
502
|
type: "object",
|
|
596
503
|
properties: {
|
|
597
504
|
defined: { const: false },
|
|
@@ -601,61 +508,16 @@ class OpenAPIGenerator {
|
|
|
601
508
|
data: {}
|
|
602
509
|
},
|
|
603
510
|
required: ["defined", "code", "status", "message"]
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
responses[status] = {
|
|
610
|
-
description: status,
|
|
611
|
-
content: this.contentBuilder.build(contentSchema)
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
if (this.considerMissingTagDefinitionAsError && def.route?.tags) {
|
|
615
|
-
const missingTag = def.route?.tags.find((tag) => !rootTags.includes(tag));
|
|
616
|
-
if (missingTag !== void 0) {
|
|
617
|
-
throw new OpenAPIError(
|
|
618
|
-
`Tag "${missingTag}" is missing definition. Please define it in OpenAPI root tags object`
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
const operation = {
|
|
623
|
-
summary: def.route?.summary,
|
|
624
|
-
description: def.route?.description,
|
|
625
|
-
deprecated: def.route?.deprecated,
|
|
626
|
-
tags: def.route?.tags ? [...def.route.tags] : void 0,
|
|
627
|
-
operationId: path.join("."),
|
|
628
|
-
parameters: parameters.length ? parameters : void 0,
|
|
629
|
-
requestBody,
|
|
630
|
-
responses
|
|
631
|
-
};
|
|
632
|
-
const extendedOperation = extendOperation(operation, contract);
|
|
633
|
-
builder.addPath(httpPath, {
|
|
634
|
-
[method.toLocaleLowerCase()]: extendedOperation
|
|
635
|
-
});
|
|
636
|
-
} catch (e) {
|
|
637
|
-
if (e instanceof OpenAPIError) {
|
|
638
|
-
const error = new OpenAPIError(`
|
|
639
|
-
Generate OpenAPI Error: ${e.message}
|
|
640
|
-
Happened at path: ${path.join(".")}
|
|
641
|
-
`, { cause: e });
|
|
642
|
-
if (this.errorHandlerStrategy === "throw") {
|
|
643
|
-
throw error;
|
|
644
|
-
}
|
|
645
|
-
if (this.errorHandlerStrategy === "log") {
|
|
646
|
-
console.error(error);
|
|
647
|
-
}
|
|
648
|
-
} else {
|
|
649
|
-
throw e;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
return this.jsonSerializer.serialize(builder.getSpec())[0];
|
|
511
|
+
}
|
|
512
|
+
]
|
|
513
|
+
})
|
|
514
|
+
};
|
|
515
|
+
}
|
|
654
516
|
}
|
|
655
517
|
}
|
|
656
518
|
|
|
657
519
|
const oo = {
|
|
658
|
-
spec:
|
|
520
|
+
spec: customOpenAPIOperation
|
|
659
521
|
};
|
|
660
522
|
|
|
661
|
-
export { CompositeSchemaConverter,
|
|
523
|
+
export { CompositeSchemaConverter, LOGIC_KEYWORDS, OpenAPIGenerator, applyCustomOpenAPIOperation, checkParamsSchema, customOpenAPIOperation, filterSchemaBranches, getCustomOpenAPIOperation, isAnySchema, isFileSchema, isObjectSchema, oo, separateObjectSchema, toOpenAPIContent, toOpenAPIEventIteratorContent, toOpenAPIMethod, toOpenAPIParameters, toOpenAPIPath, toOpenAPISchema };
|