hono-openapi 0.5.0-rc.2 → 0.5.0-rc.4
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/dist/index.cjs +143 -107
- package/dist/index.d.cts +33 -196
- package/dist/index.d.ts +33 -196
- package/dist/index.js +143 -107
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -31,12 +31,7 @@ const toOpenAPIPath = (path) => path.split("/").map((x) => {
|
|
|
31
31
|
return tmp;
|
|
32
32
|
}).join("/");
|
|
33
33
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
34
|
-
const generateOperationIdCache = /* @__PURE__ */ new Map();
|
|
35
34
|
const generateOperationId = (route) => {
|
|
36
|
-
const operationIdKey = `${route.method}:${route.path}`;
|
|
37
|
-
if (generateOperationIdCache.has(operationIdKey)) {
|
|
38
|
-
return generateOperationIdCache.get(operationIdKey);
|
|
39
|
-
}
|
|
40
35
|
let operationId = route.method;
|
|
41
36
|
if (route.path === "/") return `${operationId}Index`;
|
|
42
37
|
for (const segment of route.path.split("/")) {
|
|
@@ -46,61 +41,61 @@ const generateOperationId = (route) => {
|
|
|
46
41
|
operationId += capitalize(segment);
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
|
-
generateOperationIdCache.set(operationIdKey, operationId);
|
|
50
44
|
return operationId;
|
|
51
45
|
};
|
|
52
46
|
const paramKey = (param) => "$ref" in param ? param.$ref : `${param.in} ${param.name}`;
|
|
53
47
|
function mergeParameters(...params) {
|
|
54
|
-
const
|
|
55
|
-
const merged = _params.reduce((acc, param) => {
|
|
48
|
+
const merged = params.flatMap((x) => x ?? []).reduce((acc, param) => {
|
|
56
49
|
acc.set(paramKey(param), param);
|
|
57
50
|
return acc;
|
|
58
51
|
}, /* @__PURE__ */ new Map());
|
|
59
52
|
return Array.from(merged.values());
|
|
60
53
|
}
|
|
61
|
-
function getProperty(obj, key, defaultValue) {
|
|
62
|
-
if (obj != null && key in obj) {
|
|
63
|
-
return obj[key];
|
|
64
|
-
}
|
|
65
|
-
return defaultValue;
|
|
66
|
-
}
|
|
67
54
|
const specsByPathContext = /* @__PURE__ */ new Map();
|
|
68
55
|
function getPathContext(path) {
|
|
69
|
-
const keys = Array.from(specsByPathContext.keys());
|
|
70
56
|
const context = [];
|
|
71
|
-
for (const key of
|
|
72
|
-
if (path.match(key)) {
|
|
73
|
-
const data = specsByPathContext.get(key);
|
|
74
|
-
if (!data) continue;
|
|
57
|
+
for (const [key, data] of specsByPathContext) {
|
|
58
|
+
if (data && path.match(key)) {
|
|
75
59
|
context.push(data);
|
|
76
60
|
}
|
|
77
61
|
}
|
|
78
62
|
return context;
|
|
79
63
|
}
|
|
80
|
-
function mergeSpecs(...specs) {
|
|
64
|
+
function mergeSpecs(route, ...specs) {
|
|
81
65
|
return specs.reduce(
|
|
82
66
|
(prev, spec) => {
|
|
83
|
-
if (!spec) return prev;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
if (!spec || !prev) return prev;
|
|
68
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
69
|
+
if (value == null) continue;
|
|
70
|
+
if (key in prev && (typeof value === "object" || typeof value === "function" && key === "operationId")) {
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
const values = [...prev[key] ?? [], ...value];
|
|
73
|
+
if (key === "tags") {
|
|
74
|
+
prev[key] = Array.from(new Set(values));
|
|
75
|
+
} else {
|
|
76
|
+
prev[key] = values;
|
|
77
|
+
}
|
|
78
|
+
} else if (typeof value === "function") {
|
|
79
|
+
prev[key] = value(route);
|
|
80
|
+
} else {
|
|
81
|
+
if (key === "parameters") {
|
|
82
|
+
prev[key] = mergeParameters(prev[key], value);
|
|
83
|
+
} else {
|
|
84
|
+
prev[key] = {
|
|
85
|
+
...prev[key],
|
|
86
|
+
...value
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
prev[key] = value;
|
|
100
92
|
}
|
|
101
|
-
}
|
|
93
|
+
}
|
|
94
|
+
return prev;
|
|
102
95
|
},
|
|
103
|
-
{
|
|
96
|
+
{
|
|
97
|
+
operationId: generateOperationId(route)
|
|
98
|
+
}
|
|
104
99
|
);
|
|
105
100
|
}
|
|
106
101
|
function registerSchemaPath({
|
|
@@ -114,19 +109,23 @@ function registerSchemaPath({
|
|
|
114
109
|
if (!specs) return;
|
|
115
110
|
if (specsByPathContext.has(path)) {
|
|
116
111
|
const prev = specsByPathContext.get(path) ?? {};
|
|
117
|
-
specsByPathContext.set(path, mergeSpecs(prev, specs));
|
|
112
|
+
specsByPathContext.set(path, mergeSpecs(route, prev, specs));
|
|
118
113
|
} else {
|
|
119
114
|
specsByPathContext.set(path, specs);
|
|
120
115
|
}
|
|
121
116
|
} else {
|
|
122
117
|
const pathContext = getPathContext(path);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
118
|
+
if (!(path in paths)) {
|
|
119
|
+
paths[path] = {};
|
|
120
|
+
}
|
|
121
|
+
if (paths[path]) {
|
|
122
|
+
paths[path][method] = mergeSpecs(
|
|
123
|
+
route,
|
|
124
|
+
...pathContext,
|
|
125
|
+
paths[path]?.[method],
|
|
126
|
+
specs
|
|
127
|
+
);
|
|
128
|
+
}
|
|
130
129
|
}
|
|
131
130
|
}
|
|
132
131
|
function removeExcludedPaths(paths, ctx) {
|
|
@@ -152,10 +151,21 @@ function removeExcludedPaths(paths, ctx) {
|
|
|
152
151
|
for (const param of pathParameters) {
|
|
153
152
|
const paramName = param.slice(1, param.length - 1);
|
|
154
153
|
const index = schema.parameters.findIndex(
|
|
155
|
-
(x) =>
|
|
154
|
+
(x) => {
|
|
155
|
+
if ("$ref" in x) {
|
|
156
|
+
const pos = x.$ref.split("/").pop();
|
|
157
|
+
if (pos) {
|
|
158
|
+
const param2 = ctx.components.parameters?.[pos];
|
|
159
|
+
if (param2 && !("$ref" in param2)) {
|
|
160
|
+
return param2.in === "path" && param2.name === paramName;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return x.in === "path" && x.name === paramName;
|
|
166
|
+
}
|
|
156
167
|
);
|
|
157
|
-
if (index
|
|
158
|
-
else {
|
|
168
|
+
if (index === -1) {
|
|
159
169
|
schema.parameters.push({
|
|
160
170
|
schema: { type: "string" },
|
|
161
171
|
in: "path",
|
|
@@ -202,20 +212,24 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
|
202
212
|
}
|
|
203
213
|
};
|
|
204
214
|
const _documentation = ctx.options.documentation ?? {};
|
|
205
|
-
const
|
|
206
|
-
for (const path in
|
|
207
|
-
for (const method in
|
|
215
|
+
const paths = await generatePaths(hono, ctx);
|
|
216
|
+
for (const path in paths) {
|
|
217
|
+
for (const method in paths[path]) {
|
|
208
218
|
const isHidden = getHiddenValue({
|
|
209
|
-
valueOrFunc:
|
|
219
|
+
valueOrFunc: paths[path][method]?.hide,
|
|
210
220
|
method,
|
|
211
221
|
path,
|
|
212
222
|
c
|
|
213
223
|
});
|
|
214
224
|
if (isHidden) {
|
|
215
|
-
|
|
225
|
+
paths[path][method] = void 0;
|
|
216
226
|
}
|
|
217
227
|
}
|
|
218
228
|
}
|
|
229
|
+
const components = mergeComponentsObjects(
|
|
230
|
+
_documentation.components,
|
|
231
|
+
ctx.components
|
|
232
|
+
);
|
|
219
233
|
return {
|
|
220
234
|
openapi: "3.1.0",
|
|
221
235
|
..._documentation,
|
|
@@ -229,22 +243,17 @@ async function generateSpecs(hono, options = DEFAULT_OPTIONS, c) {
|
|
|
229
243
|
..._documentation.info
|
|
230
244
|
},
|
|
231
245
|
paths: {
|
|
232
|
-
...removeExcludedPaths(
|
|
246
|
+
...removeExcludedPaths(paths, ctx),
|
|
233
247
|
..._documentation.paths
|
|
234
248
|
},
|
|
235
|
-
components
|
|
236
|
-
..._documentation.components,
|
|
237
|
-
schemas: {
|
|
238
|
-
...ctx.components.schemas,
|
|
239
|
-
..._documentation.components?.schemas
|
|
240
|
-
}
|
|
241
|
-
}
|
|
249
|
+
components
|
|
242
250
|
};
|
|
243
251
|
}
|
|
244
252
|
async function generatePaths(hono, ctx) {
|
|
245
253
|
const paths = {};
|
|
246
254
|
for (const route of hono.routes) {
|
|
247
|
-
|
|
255
|
+
const middlewareHandler = route.handler[uniqueSymbol];
|
|
256
|
+
if (!middlewareHandler) {
|
|
248
257
|
if (ctx.options.includeEmptyPaths) {
|
|
249
258
|
registerSchemaPath({
|
|
250
259
|
route,
|
|
@@ -262,20 +271,12 @@ async function generatePaths(hono, ctx) {
|
|
|
262
271
|
continue;
|
|
263
272
|
}
|
|
264
273
|
}
|
|
265
|
-
const middlewareHandler = route.handler[uniqueSymbol];
|
|
266
274
|
const defaultOptionsForThisMethod = ctx.options.defaultOptions?.[routeMethod];
|
|
267
275
|
const { schema: routeSpecs, components = {} } = await getSpec(
|
|
268
276
|
middlewareHandler,
|
|
269
277
|
defaultOptionsForThisMethod
|
|
270
278
|
);
|
|
271
|
-
ctx.components =
|
|
272
|
-
...ctx.components,
|
|
273
|
-
...components,
|
|
274
|
-
schemas: {
|
|
275
|
-
...ctx.components.schemas,
|
|
276
|
-
...components.schemas
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
+
ctx.components = mergeComponentsObjects(ctx.components, components);
|
|
279
280
|
registerSchemaPath({
|
|
280
281
|
route,
|
|
281
282
|
specs: routeSpecs,
|
|
@@ -289,14 +290,9 @@ function getHiddenValue(options) {
|
|
|
289
290
|
if (valueOrFunc != null) {
|
|
290
291
|
if (typeof valueOrFunc === "boolean") {
|
|
291
292
|
return valueOrFunc;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
} else {
|
|
296
|
-
console.warn(
|
|
297
|
-
`'c' is not defined, cannot evaluate hide function for ${method} ${path}`
|
|
298
|
-
);
|
|
299
|
-
}
|
|
293
|
+
}
|
|
294
|
+
if (typeof valueOrFunc === "function") {
|
|
295
|
+
return valueOrFunc({ c, method, path });
|
|
300
296
|
}
|
|
301
297
|
}
|
|
302
298
|
return false;
|
|
@@ -320,7 +316,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
320
316
|
const raw = response.content?.[contentKey];
|
|
321
317
|
if (!raw) continue;
|
|
322
318
|
if (raw.schema && "toOpenAPISchema" in raw.schema) {
|
|
323
|
-
const result2 = await raw.schema.toOpenAPISchema(
|
|
319
|
+
const result2 = await raw.schema.toOpenAPISchema();
|
|
324
320
|
raw.schema = result2.schema;
|
|
325
321
|
if (result2.components) {
|
|
326
322
|
components = {
|
|
@@ -337,7 +333,7 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
337
333
|
const result = await middlewareHandler.toOpenAPISchema();
|
|
338
334
|
const docs = {};
|
|
339
335
|
if (middlewareHandler.target === "form" || middlewareHandler.target === "json") {
|
|
340
|
-
const media = middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
336
|
+
const media = middlewareHandler.options?.media ?? middlewareHandler.target === "json" ? "application/json" : "multipart/form-data";
|
|
341
337
|
if (!docs.requestBody || !("content" in docs.requestBody) || !docs.requestBody.content) {
|
|
342
338
|
docs.requestBody = {
|
|
343
339
|
content: {
|
|
@@ -352,39 +348,78 @@ async function getSpec(middlewareHandler, defaultOptions) {
|
|
|
352
348
|
};
|
|
353
349
|
}
|
|
354
350
|
} else {
|
|
355
|
-
|
|
351
|
+
let parameters = [];
|
|
356
352
|
if ("$ref" in result.schema) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
353
|
+
const ref = result.schema.$ref;
|
|
354
|
+
const pos = ref.split("/").pop();
|
|
355
|
+
if (pos && result.components?.schemas?.[pos]) {
|
|
356
|
+
const schema = result.components.schemas[pos];
|
|
357
|
+
const newParameters = generateParameters(
|
|
358
|
+
middlewareHandler.target,
|
|
359
|
+
schema
|
|
360
|
+
)[0];
|
|
361
|
+
if (!result.components.parameters) {
|
|
362
|
+
result.components.parameters = {};
|
|
363
|
+
}
|
|
364
|
+
result.components.parameters[pos] = newParameters;
|
|
365
|
+
delete result.components.schemas[pos];
|
|
368
366
|
parameters.push({
|
|
369
|
-
|
|
370
|
-
name: key,
|
|
371
|
-
// @ts-expect-error
|
|
372
|
-
schema: value,
|
|
373
|
-
required: result.schema.required?.includes(key)
|
|
367
|
+
$ref: `#/components/parameters/${pos}`
|
|
374
368
|
});
|
|
375
369
|
}
|
|
370
|
+
} else {
|
|
371
|
+
parameters = generateParameters(middlewareHandler.target, result.schema);
|
|
376
372
|
}
|
|
377
373
|
docs.parameters = parameters;
|
|
378
374
|
}
|
|
379
375
|
return { schema: docs, components: result.components };
|
|
380
376
|
}
|
|
377
|
+
function generateParameters(target, schema) {
|
|
378
|
+
const parameters = [];
|
|
379
|
+
for (const [key, value] of Object.entries(schema.properties ?? {})) {
|
|
380
|
+
const def = {
|
|
381
|
+
in: target === "param" ? "path" : target,
|
|
382
|
+
name: key,
|
|
383
|
+
// @ts-expect-error
|
|
384
|
+
schema: value,
|
|
385
|
+
required: schema.required?.includes(key)
|
|
386
|
+
};
|
|
387
|
+
if (def.schema && "description" in def.schema && def.schema.description) {
|
|
388
|
+
def.description = def.schema.description;
|
|
389
|
+
def.schema.description = void 0;
|
|
390
|
+
}
|
|
391
|
+
parameters.push(def);
|
|
392
|
+
}
|
|
393
|
+
return parameters;
|
|
394
|
+
}
|
|
395
|
+
function mergeComponentsObjects(...components) {
|
|
396
|
+
return components.reduce(
|
|
397
|
+
(prev, component, index) => {
|
|
398
|
+
if (!component || index === 0) return prev;
|
|
399
|
+
if (prev.schemas && Object.keys(prev.schemas).length > 0 || component.schemas && Object.keys(component.schemas).length > 0) {
|
|
400
|
+
prev.schemas = {
|
|
401
|
+
...prev.schemas,
|
|
402
|
+
...component.schemas
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (prev.parameters && Object.keys(prev.parameters).length > 0 || component.parameters && Object.keys(component.parameters).length > 0) {
|
|
406
|
+
prev.parameters = {
|
|
407
|
+
...prev.parameters,
|
|
408
|
+
...component.parameters
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return prev;
|
|
412
|
+
},
|
|
413
|
+
components[0] ?? {}
|
|
414
|
+
);
|
|
415
|
+
}
|
|
381
416
|
|
|
382
|
-
function resolver(schema) {
|
|
417
|
+
function resolver(schema, options) {
|
|
383
418
|
return {
|
|
384
419
|
vendor: schema["~standard"].vendor,
|
|
385
420
|
validate: schema["~standard"].validate,
|
|
386
|
-
toJSONSchema: (
|
|
387
|
-
toOpenAPISchema: (
|
|
421
|
+
toJSONSchema: () => standardJson.toJsonSchema(schema, options),
|
|
422
|
+
toOpenAPISchema: () => standardOpenapi.toOpenAPISchema(schema, options)
|
|
388
423
|
};
|
|
389
424
|
}
|
|
390
425
|
function validator(target, schema, hook, options) {
|
|
@@ -392,7 +427,7 @@ function validator(target, schema, hook, options) {
|
|
|
392
427
|
return Object.assign(middleware, {
|
|
393
428
|
[uniqueSymbol]: {
|
|
394
429
|
target,
|
|
395
|
-
...resolver(schema),
|
|
430
|
+
...resolver(schema, options),
|
|
396
431
|
options
|
|
397
432
|
}
|
|
398
433
|
});
|
|
@@ -407,16 +442,17 @@ function describeRoute(spec) {
|
|
|
407
442
|
}
|
|
408
443
|
});
|
|
409
444
|
}
|
|
410
|
-
function describeResponse(handler, responses) {
|
|
445
|
+
function describeResponse(handler, responses, options) {
|
|
411
446
|
const _responses = Object.entries(responses).reduce(
|
|
412
447
|
(acc, [statusCode, response]) => {
|
|
413
448
|
if (response.content) {
|
|
414
449
|
const content = Object.entries(response.content).reduce(
|
|
415
450
|
(contentAcc, [mediaType, media]) => {
|
|
416
451
|
if (media.vSchema) {
|
|
452
|
+
const { vSchema, ...rest } = media;
|
|
417
453
|
contentAcc[mediaType] = {
|
|
418
|
-
...
|
|
419
|
-
schema: resolver(
|
|
454
|
+
...rest,
|
|
455
|
+
schema: resolver(vSchema, options)
|
|
420
456
|
};
|
|
421
457
|
} else {
|
|
422
458
|
contentAcc[mediaType] = media;
|