@tetsujs/openapi 0.4.1 → 0.5.0
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 +140 -3
- package/dist/src/annotations.d.ts +40 -0
- package/dist/src/annotations.d.ts.map +1 -1
- package/dist/src/controller.d.ts +32 -9
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/document.d.ts +7 -0
- package/dist/src/document.d.ts.map +1 -1
- package/dist/src/emit.d.ts +3 -1
- package/dist/src/emit.d.ts.map +1 -1
- package/dist/src/envelopes.d.ts +72 -0
- package/dist/src/envelopes.d.ts.map +1 -0
- package/dist/src/errors.d.ts +74 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/index.d.ts +31 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +374 -135
- package/dist/src/index.js.map +11 -9
- package/dist/src/json-schema.d.ts +91 -0
- package/dist/src/json-schema.d.ts.map +1 -0
- package/dist/src/operation.d.ts +10 -1
- package/dist/src/operation.d.ts.map +1 -1
- package/dist/src/page.d.ts +20 -0
- package/dist/src/page.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/annotations.ts +43 -0
- package/src/controller.ts +69 -22
- package/src/document.ts +9 -0
- package/src/emit.ts +3 -1
- package/src/envelopes.ts +268 -0
- package/src/errors.ts +177 -0
- package/src/index.ts +52 -6
- package/src/json-schema.ts +108 -0
- package/src/operation.ts +328 -187
- package/src/page.ts +65 -6
package/dist/src/index.js
CHANGED
|
@@ -102,6 +102,164 @@ function failureName(status, error) {
|
|
|
102
102
|
return name || `Failure${status}`;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// packages/openapi/src/envelopes.ts
|
|
106
|
+
function envelopes(components, format) {
|
|
107
|
+
const declared = new Map;
|
|
108
|
+
const defined = new Map;
|
|
109
|
+
const refs = new Map;
|
|
110
|
+
const codes = new Map;
|
|
111
|
+
const names = new Map;
|
|
112
|
+
const reported = new Set;
|
|
113
|
+
return {
|
|
114
|
+
format,
|
|
115
|
+
declare(status, code, schema) {
|
|
116
|
+
const key = keyOf(status, code);
|
|
117
|
+
if (!declared.has(key)) {
|
|
118
|
+
declared.set(key, schema);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
ref(status, code, schema, warn) {
|
|
122
|
+
const key = keyOf(status, code);
|
|
123
|
+
const definition = declared.get(key) ?? defined.get(key) ?? schema;
|
|
124
|
+
defined.set(key, definition);
|
|
125
|
+
const difference = differenceOf(definition, schema);
|
|
126
|
+
if (difference && !reported.has(`${key} ${difference}`)) {
|
|
127
|
+
reported.add(`${key} ${difference}`);
|
|
128
|
+
warn(`the ${status} "${code}" is described by one definition in the document, and a definition that differs from it in ${difference} is not described`);
|
|
129
|
+
}
|
|
130
|
+
const existing = refs.get(key);
|
|
131
|
+
if (existing) {
|
|
132
|
+
return existing;
|
|
133
|
+
}
|
|
134
|
+
const created = components.ref(nameOf(status, code), definition);
|
|
135
|
+
refs.set(key, created);
|
|
136
|
+
codes.set(created.$ref, code);
|
|
137
|
+
return created;
|
|
138
|
+
},
|
|
139
|
+
codeOf(ref) {
|
|
140
|
+
return codes.get(ref);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
function nameOf(status, code) {
|
|
144
|
+
const name = failureName(status, code);
|
|
145
|
+
const holder = names.get(name);
|
|
146
|
+
if (holder === undefined || holder === keyOf(status, code)) {
|
|
147
|
+
names.set(name, keyOf(status, code));
|
|
148
|
+
return name;
|
|
149
|
+
}
|
|
150
|
+
return `${name}${status}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function branchesOf(schema, code) {
|
|
154
|
+
const keys = Object.keys(schema);
|
|
155
|
+
if (keys.length !== 1) {
|
|
156
|
+
return [schema];
|
|
157
|
+
}
|
|
158
|
+
const within = (branch) => branchesOf(branch, code);
|
|
159
|
+
if (Array.isArray(schema.anyOf)) {
|
|
160
|
+
return schema.anyOf.flatMap(within);
|
|
161
|
+
}
|
|
162
|
+
if (Array.isArray(schema.oneOf)) {
|
|
163
|
+
const branches = schema.oneOf.flatMap(within);
|
|
164
|
+
const codes = branches.map(code);
|
|
165
|
+
if (codes.every((code) => code !== undefined) && new Set(codes).size === codes.length) {
|
|
166
|
+
return branches;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return [schema];
|
|
170
|
+
}
|
|
171
|
+
function keyOf(status, code) {
|
|
172
|
+
return `${status} ${code}`;
|
|
173
|
+
}
|
|
174
|
+
function differenceOf(kept, other) {
|
|
175
|
+
const keptShape = shapeOf(kept);
|
|
176
|
+
const otherShape = shapeOf(other);
|
|
177
|
+
const changed = [
|
|
178
|
+
...otherShape.fields.filter((path) => !keptShape.fields.includes(path)),
|
|
179
|
+
...keptShape.fields.filter((path) => !otherShape.fields.includes(path))
|
|
180
|
+
];
|
|
181
|
+
if (changed.length > 0) {
|
|
182
|
+
return `fields: ${changed.join(", ")}`;
|
|
183
|
+
}
|
|
184
|
+
return JSON.stringify(keptShape.required) === JSON.stringify(otherShape.required) ? undefined : "which fields are required";
|
|
185
|
+
}
|
|
186
|
+
var shapeDepth = 8;
|
|
187
|
+
function shapeOf(schema) {
|
|
188
|
+
const fields = [];
|
|
189
|
+
const required = [];
|
|
190
|
+
const walk = (node, prefix, depth) => {
|
|
191
|
+
const properties = node.properties;
|
|
192
|
+
if (depth > shapeDepth || properties === null || typeof properties !== "object") {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const requiredHere = Array.isArray(node.required) ? node.required : [];
|
|
196
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
197
|
+
const path = `${prefix}${name}`;
|
|
198
|
+
fields.push(path);
|
|
199
|
+
if (requiredHere.includes(name)) {
|
|
200
|
+
required.push(path);
|
|
201
|
+
}
|
|
202
|
+
if (child !== null && typeof child === "object") {
|
|
203
|
+
walk(child, `${path}.`, depth + 1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
walk(schema, "", 0);
|
|
208
|
+
return { fields: fields.sort(), required: required.sort() };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// packages/openapi/src/errors.ts
|
|
212
|
+
function resolveFormat(format) {
|
|
213
|
+
if (format === undefined) {
|
|
214
|
+
return {
|
|
215
|
+
describe: envelope,
|
|
216
|
+
code: codeIn("error"),
|
|
217
|
+
discriminator: "error"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const discriminator = format.discriminator;
|
|
221
|
+
const read = format.code;
|
|
222
|
+
return {
|
|
223
|
+
describe: (failure) => format.schema(failure),
|
|
224
|
+
code: read ? (schema) => read(schema) : discriminator === undefined ? () => {
|
|
225
|
+
return;
|
|
226
|
+
} : codeIn(discriminator),
|
|
227
|
+
discriminator
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function envelope(failure) {
|
|
231
|
+
const own = {
|
|
232
|
+
status: { type: "integer", const: failure.status },
|
|
233
|
+
message: failure.message ? { type: "string", examples: [failure.message] } : { type: "string" },
|
|
234
|
+
error: failure.error ? { type: "string", const: failure.error } : { type: "string" }
|
|
235
|
+
};
|
|
236
|
+
const added = Object.keys(failure.fields).filter((name) => !Object.hasOwn(own, name));
|
|
237
|
+
return {
|
|
238
|
+
type: "object",
|
|
239
|
+
required: ["status", "message", "error", ...added],
|
|
240
|
+
properties: { ...failure.fields, ...own }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function codeIn(field) {
|
|
244
|
+
return (schema) => {
|
|
245
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
246
|
+
if (!required.includes(field)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const value = schema.properties?.[field];
|
|
250
|
+
if (typeof value?.const === "string") {
|
|
251
|
+
return value.const;
|
|
252
|
+
}
|
|
253
|
+
if (Array.isArray(value?.enum) && value.enum.length === 1 && typeof value.enum[0] === "string") {
|
|
254
|
+
return value.enum[0];
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// packages/openapi/src/operation.ts
|
|
261
|
+
import { errorBody } from "@tetsujs/core";
|
|
262
|
+
|
|
105
263
|
// packages/openapi/src/emit.ts
|
|
106
264
|
import { toJsonSchema } from "@tetsujs/core";
|
|
107
265
|
var target = { target: "draft-2020-12" };
|
|
@@ -192,7 +350,7 @@ function objectProperties(schema, where, warn) {
|
|
|
192
350
|
}
|
|
193
351
|
|
|
194
352
|
// packages/openapi/src/operation.ts
|
|
195
|
-
function operationOf(entry, options, components, ids, warn) {
|
|
353
|
+
function operationOf(entry, options, components, envelopes, ids, warn) {
|
|
196
354
|
const schema = entry.def.schema;
|
|
197
355
|
const parameters = [
|
|
198
356
|
...pathParameters(entry.path, schema?.params, warn),
|
|
@@ -211,9 +369,37 @@ function operationOf(entry, options, components, ids, warn) {
|
|
|
211
369
|
...parameters.length > 0 ? { parameters } : {},
|
|
212
370
|
...body ? { requestBody: body } : {},
|
|
213
371
|
...contributed.security.length > 0 ? { security: securityOf(contributed.conditions) } : {},
|
|
214
|
-
responses: responses(entry, options, contributed, components, warn)
|
|
372
|
+
responses: responses(entry, options, contributed, components, envelopes, warn)
|
|
215
373
|
};
|
|
216
374
|
}
|
|
375
|
+
function declareEnvelopes(entry, envelopes) {
|
|
376
|
+
for (const [status, schema] of declaredResponses(entry)) {
|
|
377
|
+
const described = schema === null ? undefined : emitted(schema, "", () => {});
|
|
378
|
+
const code = envelopes.format.code;
|
|
379
|
+
for (const branch of described ? branchesOf(described, code) : []) {
|
|
380
|
+
const found = code(branch);
|
|
381
|
+
if (found !== undefined) {
|
|
382
|
+
envelopes.declare(Number(status), found, branch);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function declaredResponses(entry) {
|
|
388
|
+
const response = entry.def.schema?.response;
|
|
389
|
+
if (!response) {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
if ("~standard" in response) {
|
|
393
|
+
return [["200", response]];
|
|
394
|
+
}
|
|
395
|
+
return Object.entries(response);
|
|
396
|
+
}
|
|
397
|
+
function branchesFor(schema, status, envelopes, warn) {
|
|
398
|
+
return branchesOf(schema, envelopes.format.code).map((branch) => {
|
|
399
|
+
const code = envelopes.format.code(branch);
|
|
400
|
+
return code === undefined ? branch : envelopes.ref(status, code, branch, warn);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
217
403
|
function securityOf(conditions) {
|
|
218
404
|
let combinations = [{}];
|
|
219
405
|
for (const alternatives of conditions) {
|
|
@@ -317,94 +503,161 @@ function carriesBinary(root) {
|
|
|
317
503
|
}
|
|
318
504
|
return false;
|
|
319
505
|
}
|
|
320
|
-
function responses(entry, options, contributed, components, warn) {
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
506
|
+
function responses(entry, options, contributed, components, envelopes, warn) {
|
|
507
|
+
const answers = new Map;
|
|
508
|
+
const add = (status, answer) => {
|
|
509
|
+
answers.set(status, [...answers.get(status) ?? [], answer]);
|
|
510
|
+
};
|
|
511
|
+
for (const [status, schema] of declaredResponses(entry)) {
|
|
512
|
+
const described = schema === null ? undefined : emitted(schema, "a response", warn);
|
|
513
|
+
add(status, {
|
|
514
|
+
description: describeStatus(status),
|
|
515
|
+
placeholder: true,
|
|
516
|
+
schemas: described ? branchesFor(described, Number(status), envelopes, warn) : []
|
|
517
|
+
});
|
|
329
518
|
}
|
|
330
|
-
if (!
|
|
331
|
-
|
|
519
|
+
if (![...answers.keys()].some((status) => status.startsWith("2"))) {
|
|
520
|
+
add("200", {
|
|
521
|
+
description: "Successful response",
|
|
522
|
+
placeholder: true,
|
|
523
|
+
schemas: []
|
|
524
|
+
});
|
|
332
525
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
};
|
|
526
|
+
for (const [status, answer] of failures(entry, options, contributed, components, envelopes, warn)) {
|
|
527
|
+
add(String(status), answer);
|
|
528
|
+
}
|
|
529
|
+
const described = {};
|
|
530
|
+
for (const [status, list] of answers) {
|
|
531
|
+
described[status] = merge(list, envelopes);
|
|
532
|
+
}
|
|
533
|
+
return described;
|
|
337
534
|
}
|
|
338
|
-
function
|
|
535
|
+
function failures(entry, options, contributed, components, envelopes, warn) {
|
|
339
536
|
const schema = entry.def.schema;
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
537
|
+
const found = [];
|
|
538
|
+
const envelopeRef = (status, error, message, fields) => {
|
|
539
|
+
const described = envelopes.format.describe({
|
|
540
|
+
status,
|
|
541
|
+
...error === undefined ? {} : { error },
|
|
542
|
+
...message === undefined ? {} : { message },
|
|
543
|
+
fields: fields ?? {}
|
|
544
|
+
});
|
|
545
|
+
const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
|
|
546
|
+
return ref;
|
|
344
547
|
};
|
|
345
|
-
const envelopeRef = (status, error, message) => components.ref(failureName(status, error), envelope(status, error, message));
|
|
346
548
|
if (schema?.params || schema?.query || schema?.headers || schema?.body) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
549
|
+
found.push([
|
|
550
|
+
options.validationStatus,
|
|
551
|
+
{
|
|
552
|
+
description: "Request failed schema validation",
|
|
553
|
+
schemas: [
|
|
554
|
+
envelopes.ref(options.validationStatus, "VALIDATION_FAILED", envelopes.format.describe({
|
|
555
|
+
status: options.validationStatus,
|
|
556
|
+
error: "VALIDATION_FAILED",
|
|
557
|
+
message: "Validation failed",
|
|
558
|
+
fields: { issues }
|
|
559
|
+
}), warn)
|
|
560
|
+
]
|
|
561
|
+
}
|
|
562
|
+
]);
|
|
351
563
|
}
|
|
352
564
|
for (const requirement of contributed.security) {
|
|
353
565
|
const status = requirement.status ?? 401;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
566
|
+
found.push([
|
|
567
|
+
status,
|
|
568
|
+
{
|
|
569
|
+
description: requirement.description ?? `Request did not satisfy ${requirement.name}`,
|
|
570
|
+
schemas: [envelopeRef(status, requirement.error, requirement.message)]
|
|
571
|
+
}
|
|
572
|
+
]);
|
|
358
573
|
}
|
|
359
574
|
for (const response of contributed.responses) {
|
|
360
575
|
const described = response.schema ? emitted(response.schema, "a hook's response", warn) : undefined;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
576
|
+
found.push([
|
|
577
|
+
response.status,
|
|
578
|
+
{
|
|
579
|
+
description: response.description,
|
|
580
|
+
schemas: described ? branchesFor(described, response.status, envelopes, warn) : [
|
|
581
|
+
envelopeRef(response.status, response.error, response.message, response.fields)
|
|
582
|
+
],
|
|
583
|
+
...response.headers ? { headers: response.headers } : {}
|
|
584
|
+
}
|
|
585
|
+
]);
|
|
365
586
|
}
|
|
366
587
|
if (schema?.body || entry.def.bodyType) {
|
|
367
588
|
const unparsable = parseFailure(entry.def.bodyType);
|
|
368
589
|
if (unparsable) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
590
|
+
found.push([
|
|
591
|
+
400,
|
|
592
|
+
{
|
|
593
|
+
description: "Body could not be parsed in the declared shape",
|
|
594
|
+
schemas: [envelopeRef(400, unparsable.error, unparsable.message)]
|
|
595
|
+
}
|
|
596
|
+
]);
|
|
597
|
+
}
|
|
598
|
+
found.push([
|
|
599
|
+
413,
|
|
600
|
+
{
|
|
601
|
+
description: "Body exceeded the configured size limit",
|
|
602
|
+
schemas: [
|
|
603
|
+
envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
]);
|
|
607
|
+
}
|
|
608
|
+
found.push([
|
|
609
|
+
500,
|
|
610
|
+
{
|
|
611
|
+
description: "The request failed and nothing mapped the failure",
|
|
612
|
+
schemas: [
|
|
613
|
+
envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
|
|
614
|
+
]
|
|
615
|
+
}
|
|
616
|
+
]);
|
|
617
|
+
return found;
|
|
618
|
+
}
|
|
619
|
+
function merge(list, envelopes) {
|
|
620
|
+
const schemas = distinct(list.flatMap((answer) => answer.schemas));
|
|
621
|
+
const worded = list.filter((answer) => !answer.placeholder);
|
|
622
|
+
const headers = {};
|
|
623
|
+
for (const answer of list) {
|
|
624
|
+
for (const [name, header] of Object.entries(answer.headers ?? {})) {
|
|
625
|
+
headers[name] ??= header;
|
|
373
626
|
}
|
|
374
|
-
add(413, {
|
|
375
|
-
description: "Body exceeded the configured size limit",
|
|
376
|
-
schema: envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
add(500, {
|
|
380
|
-
description: "The request failed and nothing mapped the failure",
|
|
381
|
-
schema: envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
|
|
382
|
-
});
|
|
383
|
-
const responses = {};
|
|
384
|
-
for (const [status, list] of failures) {
|
|
385
|
-
responses[status] = merge(declared[status], list);
|
|
386
627
|
}
|
|
387
|
-
return
|
|
628
|
+
return {
|
|
629
|
+
description: (worded.length > 0 ? worded : list).map((answer) => answer.description).join("; "),
|
|
630
|
+
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
631
|
+
...schemas.length > 0 ? {
|
|
632
|
+
content: {
|
|
633
|
+
"application/json": { schema: unionOf(schemas, envelopes) }
|
|
634
|
+
}
|
|
635
|
+
} : {}
|
|
636
|
+
};
|
|
388
637
|
}
|
|
389
|
-
function
|
|
390
|
-
const descriptions = [
|
|
391
|
-
...declared ? [declared.description] : [],
|
|
392
|
-
...list.map((failure) => failure.description)
|
|
393
|
-
];
|
|
394
|
-
const schemas = distinct([
|
|
395
|
-
...declaredSchemas(declared),
|
|
396
|
-
...list.map((failure) => failure.schema)
|
|
397
|
-
]);
|
|
638
|
+
function unionOf(schemas, envelopes) {
|
|
398
639
|
const [only] = schemas;
|
|
640
|
+
if (schemas.length === 1 && only) {
|
|
641
|
+
return only;
|
|
642
|
+
}
|
|
643
|
+
const propertyName = envelopes.format.discriminator;
|
|
644
|
+
if (propertyName === undefined) {
|
|
645
|
+
return { anyOf: schemas };
|
|
646
|
+
}
|
|
647
|
+
const mapping = {};
|
|
648
|
+
for (const schema of schemas) {
|
|
649
|
+
const ref = schema.$ref;
|
|
650
|
+
const code = typeof ref === "string" ? envelopes.codeOf(ref) : undefined;
|
|
651
|
+
if (typeof ref !== "string" || code === undefined) {
|
|
652
|
+
return { anyOf: schemas };
|
|
653
|
+
}
|
|
654
|
+
mapping[code] = ref;
|
|
655
|
+
}
|
|
399
656
|
return {
|
|
400
|
-
|
|
401
|
-
|
|
657
|
+
anyOf: schemas,
|
|
658
|
+
discriminator: { propertyName, mapping }
|
|
402
659
|
};
|
|
403
660
|
}
|
|
404
|
-
function declaredSchemas(declared) {
|
|
405
|
-
const schema = declared?.content?.["application/json"]?.schema;
|
|
406
|
-
return schema ? [schema] : [];
|
|
407
|
-
}
|
|
408
661
|
function distinct(schemas) {
|
|
409
662
|
const seen = new Set;
|
|
410
663
|
return schemas.filter((schema) => {
|
|
@@ -416,57 +669,23 @@ function distinct(schemas) {
|
|
|
416
669
|
return true;
|
|
417
670
|
});
|
|
418
671
|
}
|
|
419
|
-
function jsonResponse(schema, description, warn) {
|
|
420
|
-
const described = emitted(schema, "a response", warn);
|
|
421
|
-
if (!described) {
|
|
422
|
-
return { description };
|
|
423
|
-
}
|
|
424
|
-
return {
|
|
425
|
-
description,
|
|
426
|
-
content: { "application/json": { schema: described } }
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
function hasSuccess(declared) {
|
|
430
|
-
return Object.keys(declared).some((status) => status.startsWith("2"));
|
|
431
|
-
}
|
|
432
672
|
function describeStatus(status) {
|
|
433
673
|
if (status === "204") {
|
|
434
674
|
return "No content";
|
|
435
675
|
}
|
|
436
|
-
return status.startsWith("2") ? "Successful response" :
|
|
676
|
+
return status.startsWith("2") ? "Successful response" : errorBody(Number(status)).message;
|
|
437
677
|
}
|
|
438
|
-
|
|
439
|
-
|
|
678
|
+
var issues = {
|
|
679
|
+
type: "array",
|
|
680
|
+
items: {
|
|
440
681
|
type: "object",
|
|
441
|
-
required: ["
|
|
682
|
+
required: ["message", "path"],
|
|
442
683
|
properties: {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
error: error ? { type: "string", const: error } : { type: "string" }
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
function validationFailed(status) {
|
|
450
|
-
const base = envelope(status, "VALIDATION_FAILED", "Validation failed");
|
|
451
|
-
return {
|
|
452
|
-
...base,
|
|
453
|
-
required: ["status", "message", "error", "issues"],
|
|
454
|
-
properties: {
|
|
455
|
-
...base.properties,
|
|
456
|
-
issues: {
|
|
457
|
-
type: "array",
|
|
458
|
-
items: {
|
|
459
|
-
type: "object",
|
|
460
|
-
required: ["message", "path"],
|
|
461
|
-
properties: {
|
|
462
|
-
message: { type: "string" },
|
|
463
|
-
path: { type: "array", items: { type: ["string", "number"] } }
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
684
|
+
message: { type: "string" },
|
|
685
|
+
path: { type: "array", items: { type: ["string", "number"] } }
|
|
467
686
|
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
470
689
|
function parseFailure(bodyType) {
|
|
471
690
|
if (bodyType === "text" || bodyType === "stream") {
|
|
472
691
|
return;
|
|
@@ -485,14 +704,24 @@ import { onMount, route } from "@tetsujs/core";
|
|
|
485
704
|
// packages/openapi/src/page.ts
|
|
486
705
|
var defaults = {
|
|
487
706
|
scalar: {
|
|
488
|
-
script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference"
|
|
707
|
+
script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.1/dist/browser/standalone.js",
|
|
708
|
+
integrity: {
|
|
709
|
+
script: "sha384-U11tb2XnKvmwt8RlTvnwUnYgrN+ur4Xyh9htLhjajWNR/Oyl5AX5DEz00qRmlrmK"
|
|
710
|
+
}
|
|
489
711
|
},
|
|
490
712
|
"swagger-ui": {
|
|
491
|
-
script: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
|
|
492
|
-
style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css"
|
|
713
|
+
script: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui-bundle.js",
|
|
714
|
+
style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui.css",
|
|
715
|
+
integrity: {
|
|
716
|
+
script: "sha384-YDALVcy8kj8yltLBVi1vBiBAUqdxvus673gM8XKwiy6aDUJFXivF/KCufekjYbVf",
|
|
717
|
+
style: "sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW"
|
|
718
|
+
}
|
|
493
719
|
},
|
|
494
720
|
redoc: {
|
|
495
|
-
script: "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js"
|
|
721
|
+
script: "https://cdn.jsdelivr.net/npm/redoc@2.5.4/bundles/redoc.standalone.js",
|
|
722
|
+
integrity: {
|
|
723
|
+
script: "sha384-w447zOpYfw/1Tv/5AK9NfHTlQIqE3RVR6KY62jCyy9zNDgO64cMwGGP1Fj0zJVf5"
|
|
724
|
+
}
|
|
496
725
|
}
|
|
497
726
|
};
|
|
498
727
|
function docsPage(options) {
|
|
@@ -506,11 +735,11 @@ function docsPage(options) {
|
|
|
506
735
|
'<meta charset="utf-8" />',
|
|
507
736
|
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
508
737
|
`<title>${title}</title>`,
|
|
509
|
-
assets.style ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}" />` : "",
|
|
738
|
+
assets.style ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}"${verified(assets.integrity?.style)} />` : "",
|
|
510
739
|
"</head>",
|
|
511
740
|
"<body>",
|
|
512
741
|
body(options.ui, url),
|
|
513
|
-
`<script src="${escapeHtml(assets.script)}"></script>`,
|
|
742
|
+
`<script src="${escapeHtml(assets.script)}"${verified(assets.integrity?.script)}></script>`,
|
|
514
743
|
bootstrap(options.ui, url),
|
|
515
744
|
"</body>",
|
|
516
745
|
"</html>"
|
|
@@ -537,6 +766,9 @@ function bootstrap(ui, url) {
|
|
|
537
766
|
].join(`
|
|
538
767
|
`);
|
|
539
768
|
}
|
|
769
|
+
function verified(integrity) {
|
|
770
|
+
return integrity ? ` integrity="${escapeHtml(integrity)}" crossorigin="anonymous"` : "";
|
|
771
|
+
}
|
|
540
772
|
function escapeHtml(value) {
|
|
541
773
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
542
774
|
}
|
|
@@ -547,7 +779,8 @@ class DocsController {
|
|
|
547
779
|
json;
|
|
548
780
|
ui;
|
|
549
781
|
document;
|
|
550
|
-
|
|
782
|
+
page;
|
|
783
|
+
constructor(options, documentPath, uiPath) {
|
|
551
784
|
this.options = options;
|
|
552
785
|
const hidden = !options.documentSelf;
|
|
553
786
|
const json = route({
|
|
@@ -560,19 +793,31 @@ class DocsController {
|
|
|
560
793
|
method: "GET",
|
|
561
794
|
path: "/docs",
|
|
562
795
|
docs: { summary: "Rendered documentation", tags: ["docs"], hidden },
|
|
563
|
-
handler: () => new Response(page, {
|
|
796
|
+
handler: () => new Response(this.page, {
|
|
564
797
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
565
798
|
})
|
|
566
799
|
});
|
|
567
800
|
this.json = { ...json, path: documentPath };
|
|
568
|
-
this.ui = { ...ui, path: uiPath };
|
|
801
|
+
this.ui = options.ui === false ? undefined : { ...ui, path: uiPath };
|
|
569
802
|
}
|
|
570
803
|
[onMount](app) {
|
|
571
804
|
const generated = openapi(app, {
|
|
572
805
|
info: this.options.info,
|
|
573
|
-
...this.options.servers ? { servers: this.options.servers } : {}
|
|
806
|
+
...this.options.servers ? { servers: this.options.servers } : {},
|
|
807
|
+
...this.options.errors ? { errors: this.options.errors } : {}
|
|
574
808
|
});
|
|
575
809
|
this.document = generated.document;
|
|
810
|
+
const ui = this.options.ui ?? "scalar";
|
|
811
|
+
const json = this.json;
|
|
812
|
+
const served = app.entries.find((entry) => entry.def === json);
|
|
813
|
+
if (ui !== false && served) {
|
|
814
|
+
this.page = docsPage({
|
|
815
|
+
ui,
|
|
816
|
+
documentUrl: served.path,
|
|
817
|
+
title: this.options.title ?? this.options.info.title,
|
|
818
|
+
...this.options.assets ? { assets: this.options.assets } : {}
|
|
819
|
+
});
|
|
820
|
+
}
|
|
576
821
|
const report = this.options.onWarning ?? warn;
|
|
577
822
|
for (const warning of generated.warnings) {
|
|
578
823
|
report(warning);
|
|
@@ -580,15 +825,7 @@ class DocsController {
|
|
|
580
825
|
}
|
|
581
826
|
}
|
|
582
827
|
function docs(options) {
|
|
583
|
-
|
|
584
|
-
const uiPath = options.uiPath ?? "/docs";
|
|
585
|
-
const page = docsPage({
|
|
586
|
-
ui: options.ui ?? "scalar",
|
|
587
|
-
documentUrl: documentPath,
|
|
588
|
-
title: options.title ?? options.info.title,
|
|
589
|
-
...options.assets ? { assets: options.assets } : {}
|
|
590
|
-
});
|
|
591
|
-
return new DocsController(options, documentPath, uiPath, page);
|
|
828
|
+
return new DocsController(options, options.path ?? "/openapi.json", options.uiPath ?? "/docs");
|
|
592
829
|
}
|
|
593
830
|
function warn(warning) {
|
|
594
831
|
console.warn(`[openapi] ${warning.route}: ${warning.message}`);
|
|
@@ -600,15 +837,17 @@ function openapi(app, options) {
|
|
|
600
837
|
const warnings = [];
|
|
601
838
|
const schemes = {};
|
|
602
839
|
const components = schemaComponents();
|
|
840
|
+
const envelopes2 = envelopes(components, resolveFormat(options.errors));
|
|
603
841
|
const ids = operationIds();
|
|
842
|
+
const described = app.entries.filter((entry) => !entry.ws && !entry.def.docs?.hidden);
|
|
843
|
+
for (const entry of described) {
|
|
844
|
+
declareEnvelopes(entry, envelopes2);
|
|
845
|
+
}
|
|
604
846
|
const occupied = new Map;
|
|
605
|
-
for (const entry of
|
|
606
|
-
if (entry.ws || entry.def.docs?.hidden) {
|
|
607
|
-
continue;
|
|
608
|
-
}
|
|
847
|
+
for (const entry of described) {
|
|
609
848
|
const template = toTemplate(entry.path);
|
|
610
849
|
const route = `${entry.method} ${entry.path}`;
|
|
611
|
-
const operation = operationOf(entry, app.options, components, ids, (message) => {
|
|
850
|
+
const operation = operationOf(entry, app.options, components, envelopes2, ids, (message) => {
|
|
612
851
|
warnings.push({ route, message });
|
|
613
852
|
});
|
|
614
853
|
for (const requirement of contributionsOf(entry.hooks).security) {
|
|
@@ -665,5 +904,5 @@ export {
|
|
|
665
904
|
secured
|
|
666
905
|
};
|
|
667
906
|
|
|
668
|
-
//# debugId=
|
|
907
|
+
//# debugId=B1D856E7F5B6819164756E2164756E21
|
|
669
908
|
//# sourceMappingURL=index.js.map
|