@tetsujs/openapi 0.4.0 → 0.4.2
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 +82 -3
- package/dist/src/annotations.d.ts +93 -10
- package/dist/src/annotations.d.ts.map +1 -1
- package/dist/src/controller.d.ts +26 -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 +74 -0
- package/dist/src/envelopes.d.ts.map +1 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +360 -122
- package/dist/src/index.js.map +10 -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 +162 -21
- package/src/controller.ts +61 -22
- package/src/document.ts +9 -0
- package/src/emit.ts +3 -1
- package/src/envelopes.ts +242 -0
- package/src/index.ts +20 -6
- package/src/json-schema.ts +108 -0
- package/src/operation.ts +381 -152
- package/src/page.ts +65 -6
package/dist/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ function docsOf(hook) {
|
|
|
17
17
|
}
|
|
18
18
|
function contributionsOf(chains, warn) {
|
|
19
19
|
const security = new Map;
|
|
20
|
+
const conditions = new Map;
|
|
20
21
|
const responses = new Map;
|
|
21
22
|
for (const slot of Object.values(chains)) {
|
|
22
23
|
for (const hook of slot) {
|
|
@@ -25,11 +26,20 @@ function contributionsOf(chains, warn) {
|
|
|
25
26
|
continue;
|
|
26
27
|
}
|
|
27
28
|
if (docs.security) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const alternatives = alternativesOf(docs.security);
|
|
30
|
+
conditions.set(conditionKey(alternatives), alternatives);
|
|
31
|
+
for (const requirement of alternatives) {
|
|
32
|
+
const claimed = security.get(requirement.name);
|
|
33
|
+
if (!claimed) {
|
|
34
|
+
security.set(requirement.name, requirement);
|
|
35
|
+
} else if (JSON.stringify(claimed.scheme) === JSON.stringify(requirement.scheme)) {
|
|
36
|
+
security.set(requirement.name, {
|
|
37
|
+
...claimed,
|
|
38
|
+
scopes: joinScopes(claimed.scopes, requirement.scopes)
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
warn?.(`two hooks claim the security scheme "${requirement.name}" with different definitions; one of them is not described`);
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
}
|
|
35
45
|
for (const response of docs.responses ?? []) {
|
|
@@ -42,9 +52,28 @@ function contributionsOf(chains, warn) {
|
|
|
42
52
|
}
|
|
43
53
|
return {
|
|
44
54
|
security: [...security.values()],
|
|
55
|
+
conditions: [...conditions.values()],
|
|
45
56
|
responses: [...responses.values()]
|
|
46
57
|
};
|
|
47
58
|
}
|
|
59
|
+
function alternativesOf(security) {
|
|
60
|
+
return "anyOf" in security ? security.anyOf : [security];
|
|
61
|
+
}
|
|
62
|
+
function conditionKey(alternatives) {
|
|
63
|
+
return JSON.stringify(alternatives.map((requirement) => [
|
|
64
|
+
requirement.name,
|
|
65
|
+
[...requirement.scopes ?? []].sort()
|
|
66
|
+
]));
|
|
67
|
+
}
|
|
68
|
+
function joinScopes(first, second) {
|
|
69
|
+
const joined = [...first ?? []];
|
|
70
|
+
for (const scope of second ?? []) {
|
|
71
|
+
if (!joined.includes(scope)) {
|
|
72
|
+
joined.push(scope);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return joined;
|
|
76
|
+
}
|
|
48
77
|
|
|
49
78
|
// packages/openapi/src/components.ts
|
|
50
79
|
function schemaComponents() {
|
|
@@ -73,6 +102,104 @@ function failureName(status, error) {
|
|
|
73
102
|
return name || `Failure${status}`;
|
|
74
103
|
}
|
|
75
104
|
|
|
105
|
+
// packages/openapi/src/envelopes.ts
|
|
106
|
+
function envelopes(components) {
|
|
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
|
+
declare(status, code, schema) {
|
|
115
|
+
const key = keyOf(status, code);
|
|
116
|
+
if (!declared.has(key)) {
|
|
117
|
+
declared.set(key, schema);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
ref(status, code, schema, warn) {
|
|
121
|
+
const key = keyOf(status, code);
|
|
122
|
+
const definition = declared.get(key) ?? defined.get(key) ?? schema;
|
|
123
|
+
defined.set(key, definition);
|
|
124
|
+
const difference = differenceOf(definition, schema);
|
|
125
|
+
if (difference && !reported.has(`${key} ${difference}`)) {
|
|
126
|
+
reported.add(`${key} ${difference}`);
|
|
127
|
+
warn(`the ${status} "${code}" is described by one definition in the document, and a definition that differs from it in ${difference} is not described`);
|
|
128
|
+
}
|
|
129
|
+
const existing = refs.get(key);
|
|
130
|
+
if (existing) {
|
|
131
|
+
return existing;
|
|
132
|
+
}
|
|
133
|
+
const created = components.ref(nameOf(status, code), definition);
|
|
134
|
+
refs.set(key, created);
|
|
135
|
+
codes.set(created.$ref, code);
|
|
136
|
+
return created;
|
|
137
|
+
},
|
|
138
|
+
codeOf(ref) {
|
|
139
|
+
return codes.get(ref);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
function nameOf(status, code) {
|
|
143
|
+
const name = failureName(status, code);
|
|
144
|
+
const holder = names.get(name);
|
|
145
|
+
if (holder === undefined || holder === keyOf(status, code)) {
|
|
146
|
+
names.set(name, keyOf(status, code));
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
return `${name}${status}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function envelopeCode(schema) {
|
|
153
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
154
|
+
if (!required.includes("error")) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const error = schema.properties?.error;
|
|
158
|
+
if (typeof error?.const === "string") {
|
|
159
|
+
return error.const;
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(error?.enum) && error.enum.length === 1 && typeof error.enum[0] === "string") {
|
|
162
|
+
return error.enum[0];
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
function branchesOf(schema) {
|
|
167
|
+
const keys = Object.keys(schema);
|
|
168
|
+
if (keys.length !== 1) {
|
|
169
|
+
return [schema];
|
|
170
|
+
}
|
|
171
|
+
if (Array.isArray(schema.anyOf)) {
|
|
172
|
+
return schema.anyOf.flatMap(branchesOf);
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(schema.oneOf)) {
|
|
175
|
+
const branches = schema.oneOf.flatMap(branchesOf);
|
|
176
|
+
const codes = branches.map(envelopeCode);
|
|
177
|
+
if (codes.every((code) => code !== undefined) && new Set(codes).size === codes.length) {
|
|
178
|
+
return branches;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [schema];
|
|
182
|
+
}
|
|
183
|
+
function keyOf(status, code) {
|
|
184
|
+
return `${status} ${code}`;
|
|
185
|
+
}
|
|
186
|
+
function differenceOf(kept, other) {
|
|
187
|
+
const fields = (schema) => Object.keys(schema.properties ?? {});
|
|
188
|
+
const keptFields = fields(kept);
|
|
189
|
+
const otherFields = fields(other);
|
|
190
|
+
const missing = otherFields.filter((name) => !keptFields.includes(name));
|
|
191
|
+
const extra = keptFields.filter((name) => !otherFields.includes(name));
|
|
192
|
+
const changed = [...missing, ...extra];
|
|
193
|
+
if (changed.length > 0) {
|
|
194
|
+
return `fields: ${changed.join(", ")}`;
|
|
195
|
+
}
|
|
196
|
+
const required = (schema) => JSON.stringify([...Array.isArray(schema.required) ? schema.required : []].sort());
|
|
197
|
+
return required(kept) === required(other) ? undefined : "which fields are required";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// packages/openapi/src/operation.ts
|
|
201
|
+
import { errorBody } from "@tetsujs/core";
|
|
202
|
+
|
|
76
203
|
// packages/openapi/src/emit.ts
|
|
77
204
|
import { toJsonSchema } from "@tetsujs/core";
|
|
78
205
|
var target = { target: "draft-2020-12" };
|
|
@@ -163,7 +290,7 @@ function objectProperties(schema, where, warn) {
|
|
|
163
290
|
}
|
|
164
291
|
|
|
165
292
|
// packages/openapi/src/operation.ts
|
|
166
|
-
function operationOf(entry, options, components, ids, warn) {
|
|
293
|
+
function operationOf(entry, options, components, envelopes, ids, warn) {
|
|
167
294
|
const schema = entry.def.schema;
|
|
168
295
|
const parameters = [
|
|
169
296
|
...pathParameters(entry.path, schema?.params, warn),
|
|
@@ -181,14 +308,64 @@ function operationOf(entry, options, components, ids, warn) {
|
|
|
181
308
|
...entry.def.docs?.deprecated === undefined ? {} : { deprecated: entry.def.docs.deprecated },
|
|
182
309
|
...parameters.length > 0 ? { parameters } : {},
|
|
183
310
|
...body ? { requestBody: body } : {},
|
|
184
|
-
...contributed.security.length > 0 ? {
|
|
185
|
-
|
|
186
|
-
[requirement.name]: requirement.scopes ?? []
|
|
187
|
-
}))
|
|
188
|
-
} : {},
|
|
189
|
-
responses: responses(entry, options, contributed, components, warn)
|
|
311
|
+
...contributed.security.length > 0 ? { security: securityOf(contributed.conditions) } : {},
|
|
312
|
+
responses: responses(entry, options, contributed, components, envelopes, warn)
|
|
190
313
|
};
|
|
191
314
|
}
|
|
315
|
+
function declareEnvelopes(entry, envelopes) {
|
|
316
|
+
for (const [status, schema] of declaredResponses(entry)) {
|
|
317
|
+
const described = schema === null ? undefined : emitted(schema, "", () => {});
|
|
318
|
+
for (const branch of described ? branchesOf(described) : []) {
|
|
319
|
+
const code = envelopeCode(branch);
|
|
320
|
+
if (code !== undefined) {
|
|
321
|
+
envelopes.declare(Number(status), code, branch);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function declaredResponses(entry) {
|
|
327
|
+
const response = entry.def.schema?.response;
|
|
328
|
+
if (!response) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
if ("~standard" in response) {
|
|
332
|
+
return [["200", response]];
|
|
333
|
+
}
|
|
334
|
+
return Object.entries(response);
|
|
335
|
+
}
|
|
336
|
+
function branchesFor(schema, status, envelopes, warn) {
|
|
337
|
+
return branchesOf(schema).map((branch) => {
|
|
338
|
+
const code = envelopeCode(branch);
|
|
339
|
+
return code === undefined ? branch : envelopes.ref(status, code, branch, warn);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function securityOf(conditions) {
|
|
343
|
+
let combinations = [{}];
|
|
344
|
+
for (const alternatives of conditions) {
|
|
345
|
+
combinations = combinations.flatMap((combination) => alternatives.map((requirement) => ({
|
|
346
|
+
...combination,
|
|
347
|
+
[requirement.name]: joined(combination[requirement.name], requirement.scopes)
|
|
348
|
+
})));
|
|
349
|
+
}
|
|
350
|
+
const seen = new Set;
|
|
351
|
+
return combinations.filter((combination) => {
|
|
352
|
+
const key = JSON.stringify(Object.entries(combination).map(([name, scopes]) => [name, [...scopes].sort()]).sort());
|
|
353
|
+
if (seen.has(key)) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
seen.add(key);
|
|
357
|
+
return true;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function joined(present, added) {
|
|
361
|
+
const scopes = [...present ?? []];
|
|
362
|
+
for (const scope of added ?? []) {
|
|
363
|
+
if (!scopes.includes(scope)) {
|
|
364
|
+
scopes.push(scope);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return scopes;
|
|
368
|
+
}
|
|
192
369
|
function operationIds() {
|
|
193
370
|
const taken = new Map;
|
|
194
371
|
return {
|
|
@@ -265,94 +442,147 @@ function carriesBinary(root) {
|
|
|
265
442
|
}
|
|
266
443
|
return false;
|
|
267
444
|
}
|
|
268
|
-
function responses(entry, options, contributed, components, warn) {
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
445
|
+
function responses(entry, options, contributed, components, envelopes, warn) {
|
|
446
|
+
const answers = new Map;
|
|
447
|
+
const add = (status, answer) => {
|
|
448
|
+
answers.set(status, [...answers.get(status) ?? [], answer]);
|
|
449
|
+
};
|
|
450
|
+
for (const [status, schema] of declaredResponses(entry)) {
|
|
451
|
+
const described = schema === null ? undefined : emitted(schema, "a response", warn);
|
|
452
|
+
add(status, {
|
|
453
|
+
description: describeStatus(status),
|
|
454
|
+
placeholder: true,
|
|
455
|
+
schemas: described ? branchesFor(described, Number(status), envelopes, warn) : []
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (![...answers.keys()].some((status) => status.startsWith("2"))) {
|
|
459
|
+
add("200", {
|
|
460
|
+
description: "Successful response",
|
|
461
|
+
placeholder: true,
|
|
462
|
+
schemas: []
|
|
463
|
+
});
|
|
277
464
|
}
|
|
278
|
-
|
|
279
|
-
|
|
465
|
+
for (const [status, answer] of failures(entry, options, contributed, components, envelopes, warn)) {
|
|
466
|
+
add(String(status), answer);
|
|
280
467
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
468
|
+
const described = {};
|
|
469
|
+
for (const [status, list] of answers) {
|
|
470
|
+
described[status] = merge(list, envelopes);
|
|
471
|
+
}
|
|
472
|
+
return described;
|
|
285
473
|
}
|
|
286
|
-
function
|
|
474
|
+
function failures(entry, options, contributed, components, envelopes, warn) {
|
|
287
475
|
const schema = entry.def.schema;
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
476
|
+
const found = [];
|
|
477
|
+
const envelopeRef = (status, error, message, fields) => {
|
|
478
|
+
const described = envelope(status, error, message, fields);
|
|
479
|
+
const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
|
|
480
|
+
return ref;
|
|
292
481
|
};
|
|
293
|
-
const envelopeRef = (status, error, message) => components.ref(failureName(status, error), envelope(status, error, message));
|
|
294
482
|
if (schema?.params || schema?.query || schema?.headers || schema?.body) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
483
|
+
found.push([
|
|
484
|
+
options.validationStatus,
|
|
485
|
+
{
|
|
486
|
+
description: "Request failed schema validation",
|
|
487
|
+
schemas: [
|
|
488
|
+
envelopes.ref(options.validationStatus, "VALIDATION_FAILED", validationFailed(options.validationStatus), warn)
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
]);
|
|
299
492
|
}
|
|
300
493
|
for (const requirement of contributed.security) {
|
|
301
494
|
const status = requirement.status ?? 401;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
495
|
+
found.push([
|
|
496
|
+
status,
|
|
497
|
+
{
|
|
498
|
+
description: requirement.description ?? `Request did not satisfy ${requirement.name}`,
|
|
499
|
+
schemas: [envelopeRef(status, requirement.error, requirement.message)]
|
|
500
|
+
}
|
|
501
|
+
]);
|
|
306
502
|
}
|
|
307
503
|
for (const response of contributed.responses) {
|
|
308
504
|
const described = response.schema ? emitted(response.schema, "a hook's response", warn) : undefined;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
505
|
+
found.push([
|
|
506
|
+
response.status,
|
|
507
|
+
{
|
|
508
|
+
description: response.description,
|
|
509
|
+
schemas: described ? branchesFor(described, response.status, envelopes, warn) : [
|
|
510
|
+
envelopeRef(response.status, response.error, response.message, response.fields)
|
|
511
|
+
],
|
|
512
|
+
...response.headers ? { headers: response.headers } : {}
|
|
513
|
+
}
|
|
514
|
+
]);
|
|
313
515
|
}
|
|
314
516
|
if (schema?.body || entry.def.bodyType) {
|
|
315
517
|
const unparsable = parseFailure(entry.def.bodyType);
|
|
316
518
|
if (unparsable) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
519
|
+
found.push([
|
|
520
|
+
400,
|
|
521
|
+
{
|
|
522
|
+
description: "Body could not be parsed in the declared shape",
|
|
523
|
+
schemas: [envelopeRef(400, unparsable.error, unparsable.message)]
|
|
524
|
+
}
|
|
525
|
+
]);
|
|
526
|
+
}
|
|
527
|
+
found.push([
|
|
528
|
+
413,
|
|
529
|
+
{
|
|
530
|
+
description: "Body exceeded the configured size limit",
|
|
531
|
+
schemas: [
|
|
532
|
+
envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
|
|
533
|
+
]
|
|
534
|
+
}
|
|
535
|
+
]);
|
|
536
|
+
}
|
|
537
|
+
found.push([
|
|
538
|
+
500,
|
|
539
|
+
{
|
|
540
|
+
description: "The request failed and nothing mapped the failure",
|
|
541
|
+
schemas: [
|
|
542
|
+
envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
|
|
543
|
+
]
|
|
544
|
+
}
|
|
545
|
+
]);
|
|
546
|
+
return found;
|
|
547
|
+
}
|
|
548
|
+
function merge(list, envelopes) {
|
|
549
|
+
const schemas = distinct(list.flatMap((answer) => answer.schemas));
|
|
550
|
+
const worded = list.filter((answer) => !answer.placeholder);
|
|
551
|
+
const headers = {};
|
|
552
|
+
for (const answer of list) {
|
|
553
|
+
for (const [name, header] of Object.entries(answer.headers ?? {})) {
|
|
554
|
+
headers[name] ??= header;
|
|
321
555
|
}
|
|
322
|
-
add(413, {
|
|
323
|
-
description: "Body exceeded the configured size limit",
|
|
324
|
-
schema: envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
add(500, {
|
|
328
|
-
description: "The request failed and nothing mapped the failure",
|
|
329
|
-
schema: envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
|
|
330
|
-
});
|
|
331
|
-
const responses = {};
|
|
332
|
-
for (const [status, list] of failures) {
|
|
333
|
-
responses[status] = merge(declared[status], list);
|
|
334
556
|
}
|
|
335
|
-
return
|
|
557
|
+
return {
|
|
558
|
+
description: (worded.length > 0 ? worded : list).map((answer) => answer.description).join("; "),
|
|
559
|
+
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
560
|
+
...schemas.length > 0 ? {
|
|
561
|
+
content: {
|
|
562
|
+
"application/json": { schema: unionOf(schemas, envelopes) }
|
|
563
|
+
}
|
|
564
|
+
} : {}
|
|
565
|
+
};
|
|
336
566
|
}
|
|
337
|
-
function
|
|
338
|
-
const descriptions = [
|
|
339
|
-
...declared ? [declared.description] : [],
|
|
340
|
-
...list.map((failure) => failure.description)
|
|
341
|
-
];
|
|
342
|
-
const schemas = distinct([
|
|
343
|
-
...declaredSchemas(declared),
|
|
344
|
-
...list.map((failure) => failure.schema)
|
|
345
|
-
]);
|
|
567
|
+
function unionOf(schemas, envelopes) {
|
|
346
568
|
const [only] = schemas;
|
|
569
|
+
if (schemas.length === 1 && only) {
|
|
570
|
+
return only;
|
|
571
|
+
}
|
|
572
|
+
const mapping = {};
|
|
573
|
+
for (const schema of schemas) {
|
|
574
|
+
const ref = schema.$ref;
|
|
575
|
+
const code = typeof ref === "string" ? envelopes.codeOf(ref) : undefined;
|
|
576
|
+
if (typeof ref !== "string" || code === undefined) {
|
|
577
|
+
return { anyOf: schemas };
|
|
578
|
+
}
|
|
579
|
+
mapping[code] = ref;
|
|
580
|
+
}
|
|
347
581
|
return {
|
|
348
|
-
|
|
349
|
-
|
|
582
|
+
anyOf: schemas,
|
|
583
|
+
discriminator: { propertyName: "error", mapping }
|
|
350
584
|
};
|
|
351
585
|
}
|
|
352
|
-
function declaredSchemas(declared) {
|
|
353
|
-
const schema = declared?.content?.["application/json"]?.schema;
|
|
354
|
-
return schema ? [schema] : [];
|
|
355
|
-
}
|
|
356
586
|
function distinct(schemas) {
|
|
357
587
|
const seen = new Set;
|
|
358
588
|
return schemas.filter((schema) => {
|
|
@@ -364,34 +594,23 @@ function distinct(schemas) {
|
|
|
364
594
|
return true;
|
|
365
595
|
});
|
|
366
596
|
}
|
|
367
|
-
function jsonResponse(schema, description, warn) {
|
|
368
|
-
const described = emitted(schema, "a response", warn);
|
|
369
|
-
if (!described) {
|
|
370
|
-
return { description };
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
description,
|
|
374
|
-
content: { "application/json": { schema: described } }
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
function hasSuccess(declared) {
|
|
378
|
-
return Object.keys(declared).some((status) => status.startsWith("2"));
|
|
379
|
-
}
|
|
380
597
|
function describeStatus(status) {
|
|
381
598
|
if (status === "204") {
|
|
382
599
|
return "No content";
|
|
383
600
|
}
|
|
384
|
-
return status.startsWith("2") ? "Successful response" :
|
|
601
|
+
return status.startsWith("2") ? "Successful response" : errorBody(Number(status)).message;
|
|
385
602
|
}
|
|
386
|
-
function envelope(status, error, message) {
|
|
603
|
+
function envelope(status, error, message, fields = {}) {
|
|
604
|
+
const own = {
|
|
605
|
+
status: { type: "integer", const: status },
|
|
606
|
+
message: message ? { type: "string", examples: [message] } : { type: "string" },
|
|
607
|
+
error: error ? { type: "string", const: error } : { type: "string" }
|
|
608
|
+
};
|
|
609
|
+
const added = Object.keys(fields).filter((name) => !Object.hasOwn(own, name));
|
|
387
610
|
return {
|
|
388
611
|
type: "object",
|
|
389
|
-
required: ["status", "message", "error"],
|
|
390
|
-
properties: {
|
|
391
|
-
status: { type: "integer", const: status },
|
|
392
|
-
message: message ? { type: "string", examples: [message] } : { type: "string" },
|
|
393
|
-
error: error ? { type: "string", const: error } : { type: "string" }
|
|
394
|
-
}
|
|
612
|
+
required: ["status", "message", "error", ...added],
|
|
613
|
+
properties: { ...fields, ...own }
|
|
395
614
|
};
|
|
396
615
|
}
|
|
397
616
|
function validationFailed(status) {
|
|
@@ -433,14 +652,24 @@ import { onMount, route } from "@tetsujs/core";
|
|
|
433
652
|
// packages/openapi/src/page.ts
|
|
434
653
|
var defaults = {
|
|
435
654
|
scalar: {
|
|
436
|
-
script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference"
|
|
655
|
+
script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.1/dist/browser/standalone.js",
|
|
656
|
+
integrity: {
|
|
657
|
+
script: "sha384-U11tb2XnKvmwt8RlTvnwUnYgrN+ur4Xyh9htLhjajWNR/Oyl5AX5DEz00qRmlrmK"
|
|
658
|
+
}
|
|
437
659
|
},
|
|
438
660
|
"swagger-ui": {
|
|
439
|
-
script: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
|
|
440
|
-
style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css"
|
|
661
|
+
script: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui-bundle.js",
|
|
662
|
+
style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui.css",
|
|
663
|
+
integrity: {
|
|
664
|
+
script: "sha384-YDALVcy8kj8yltLBVi1vBiBAUqdxvus673gM8XKwiy6aDUJFXivF/KCufekjYbVf",
|
|
665
|
+
style: "sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW"
|
|
666
|
+
}
|
|
441
667
|
},
|
|
442
668
|
redoc: {
|
|
443
|
-
script: "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js"
|
|
669
|
+
script: "https://cdn.jsdelivr.net/npm/redoc@2.5.4/bundles/redoc.standalone.js",
|
|
670
|
+
integrity: {
|
|
671
|
+
script: "sha384-w447zOpYfw/1Tv/5AK9NfHTlQIqE3RVR6KY62jCyy9zNDgO64cMwGGP1Fj0zJVf5"
|
|
672
|
+
}
|
|
444
673
|
}
|
|
445
674
|
};
|
|
446
675
|
function docsPage(options) {
|
|
@@ -454,11 +683,11 @@ function docsPage(options) {
|
|
|
454
683
|
'<meta charset="utf-8" />',
|
|
455
684
|
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
456
685
|
`<title>${title}</title>`,
|
|
457
|
-
assets.style ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}" />` : "",
|
|
686
|
+
assets.style ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}"${verified(assets.integrity?.style)} />` : "",
|
|
458
687
|
"</head>",
|
|
459
688
|
"<body>",
|
|
460
689
|
body(options.ui, url),
|
|
461
|
-
`<script src="${escapeHtml(assets.script)}"></script>`,
|
|
690
|
+
`<script src="${escapeHtml(assets.script)}"${verified(assets.integrity?.script)}></script>`,
|
|
462
691
|
bootstrap(options.ui, url),
|
|
463
692
|
"</body>",
|
|
464
693
|
"</html>"
|
|
@@ -485,6 +714,9 @@ function bootstrap(ui, url) {
|
|
|
485
714
|
].join(`
|
|
486
715
|
`);
|
|
487
716
|
}
|
|
717
|
+
function verified(integrity) {
|
|
718
|
+
return integrity ? ` integrity="${escapeHtml(integrity)}" crossorigin="anonymous"` : "";
|
|
719
|
+
}
|
|
488
720
|
function escapeHtml(value) {
|
|
489
721
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
490
722
|
}
|
|
@@ -495,7 +727,8 @@ class DocsController {
|
|
|
495
727
|
json;
|
|
496
728
|
ui;
|
|
497
729
|
document;
|
|
498
|
-
|
|
730
|
+
page;
|
|
731
|
+
constructor(options, documentPath, uiPath) {
|
|
499
732
|
this.options = options;
|
|
500
733
|
const hidden = !options.documentSelf;
|
|
501
734
|
const json = route({
|
|
@@ -508,12 +741,12 @@ class DocsController {
|
|
|
508
741
|
method: "GET",
|
|
509
742
|
path: "/docs",
|
|
510
743
|
docs: { summary: "Rendered documentation", tags: ["docs"], hidden },
|
|
511
|
-
handler: () => new Response(page, {
|
|
744
|
+
handler: () => new Response(this.page, {
|
|
512
745
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
513
746
|
})
|
|
514
747
|
});
|
|
515
748
|
this.json = { ...json, path: documentPath };
|
|
516
|
-
this.ui = { ...ui, path: uiPath };
|
|
749
|
+
this.ui = options.ui === false ? undefined : { ...ui, path: uiPath };
|
|
517
750
|
}
|
|
518
751
|
[onMount](app) {
|
|
519
752
|
const generated = openapi(app, {
|
|
@@ -521,6 +754,17 @@ class DocsController {
|
|
|
521
754
|
...this.options.servers ? { servers: this.options.servers } : {}
|
|
522
755
|
});
|
|
523
756
|
this.document = generated.document;
|
|
757
|
+
const ui = this.options.ui ?? "scalar";
|
|
758
|
+
const json = this.json;
|
|
759
|
+
const served = app.entries.find((entry) => entry.def === json);
|
|
760
|
+
if (ui !== false && served) {
|
|
761
|
+
this.page = docsPage({
|
|
762
|
+
ui,
|
|
763
|
+
documentUrl: served.path,
|
|
764
|
+
title: this.options.title ?? this.options.info.title,
|
|
765
|
+
...this.options.assets ? { assets: this.options.assets } : {}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
524
768
|
const report = this.options.onWarning ?? warn;
|
|
525
769
|
for (const warning of generated.warnings) {
|
|
526
770
|
report(warning);
|
|
@@ -528,15 +772,7 @@ class DocsController {
|
|
|
528
772
|
}
|
|
529
773
|
}
|
|
530
774
|
function docs(options) {
|
|
531
|
-
|
|
532
|
-
const uiPath = options.uiPath ?? "/docs";
|
|
533
|
-
const page = docsPage({
|
|
534
|
-
ui: options.ui ?? "scalar",
|
|
535
|
-
documentUrl: documentPath,
|
|
536
|
-
title: options.title ?? options.info.title,
|
|
537
|
-
...options.assets ? { assets: options.assets } : {}
|
|
538
|
-
});
|
|
539
|
-
return new DocsController(options, documentPath, uiPath, page);
|
|
775
|
+
return new DocsController(options, options.path ?? "/openapi.json", options.uiPath ?? "/docs");
|
|
540
776
|
}
|
|
541
777
|
function warn(warning) {
|
|
542
778
|
console.warn(`[openapi] ${warning.route}: ${warning.message}`);
|
|
@@ -548,15 +784,17 @@ function openapi(app, options) {
|
|
|
548
784
|
const warnings = [];
|
|
549
785
|
const schemes = {};
|
|
550
786
|
const components = schemaComponents();
|
|
787
|
+
const envelopes2 = envelopes(components);
|
|
551
788
|
const ids = operationIds();
|
|
789
|
+
const described = app.entries.filter((entry) => !entry.ws && !entry.def.docs?.hidden);
|
|
790
|
+
for (const entry of described) {
|
|
791
|
+
declareEnvelopes(entry, envelopes2);
|
|
792
|
+
}
|
|
552
793
|
const occupied = new Map;
|
|
553
|
-
for (const entry of
|
|
554
|
-
if (entry.ws || entry.def.docs?.hidden) {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
794
|
+
for (const entry of described) {
|
|
557
795
|
const template = toTemplate(entry.path);
|
|
558
796
|
const route = `${entry.method} ${entry.path}`;
|
|
559
|
-
const operation = operationOf(entry, app.options, components, ids, (message) => {
|
|
797
|
+
const operation = operationOf(entry, app.options, components, envelopes2, ids, (message) => {
|
|
560
798
|
warnings.push({ route, message });
|
|
561
799
|
});
|
|
562
800
|
for (const requirement of contributionsOf(entry.hooks).security) {
|
|
@@ -613,5 +851,5 @@ export {
|
|
|
613
851
|
secured
|
|
614
852
|
};
|
|
615
853
|
|
|
616
|
-
//# debugId=
|
|
854
|
+
//# debugId=8D8E4C09AE6F825C64756E2164756E21
|
|
617
855
|
//# sourceMappingURL=index.js.map
|