@tetsujs/openapi 0.5.3 → 0.6.1
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 +5 -401
- package/dist/src/document.d.ts +1 -0
- package/dist/src/document.d.ts.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +159 -26
- package/dist/src/index.js.map +7 -6
- package/dist/src/operation.d.ts.map +1 -1
- package/dist/src/refs.d.ts +36 -0
- package/dist/src/refs.d.ts.map +1 -0
- package/dist/src/testing.d.ts +3 -0
- package/dist/src/testing.d.ts.map +1 -1
- package/dist/src/testing.js +8 -4
- package/dist/src/testing.js.map +3 -3
- package/package.json +4 -4
- package/src/document.ts +1 -0
- package/src/index.ts +51 -11
- package/src/operation.ts +161 -12
- package/src/page.ts +13 -4
- package/src/refs.ts +136 -0
- package/src/testing.ts +20 -5
package/dist/src/index.js
CHANGED
|
@@ -373,8 +373,8 @@ function operationOf(entry, options, components, envelopes, ids, warn) {
|
|
|
373
373
|
};
|
|
374
374
|
}
|
|
375
375
|
function declareEnvelopes(entry, envelopes) {
|
|
376
|
-
for (const [status,
|
|
377
|
-
const described =
|
|
376
|
+
for (const [status, { body }] of declaredResponses(entry)) {
|
|
377
|
+
const described = body === null ? undefined : emitted(body, "", () => {});
|
|
378
378
|
const code = envelopes.format.code;
|
|
379
379
|
for (const branch of described ? branchesOf(described, code) : []) {
|
|
380
380
|
const found = code(branch);
|
|
@@ -390,9 +390,62 @@ function declaredResponses(entry) {
|
|
|
390
390
|
return [];
|
|
391
391
|
}
|
|
392
392
|
if ("~standard" in response) {
|
|
393
|
-
return [["200", response]];
|
|
393
|
+
return [["200", { body: response }]];
|
|
394
394
|
}
|
|
395
|
-
return Object.entries(response)
|
|
395
|
+
return Object.entries(response).map(([status, value]) => [
|
|
396
|
+
status,
|
|
397
|
+
asDeclared(value)
|
|
398
|
+
]);
|
|
399
|
+
}
|
|
400
|
+
function asDeclared(value) {
|
|
401
|
+
if (value === null || "~standard" in value) {
|
|
402
|
+
return { body: value };
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
body: value.body ?? null,
|
|
406
|
+
...value.headers === undefined ? {} : { headers: value.headers },
|
|
407
|
+
...value.cookies === undefined ? {} : { cookies: value.cookies }
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function declaredHeaders(declared, warn) {
|
|
411
|
+
const headers = {};
|
|
412
|
+
const [properties, required] = shapeOf2(declared.headers, "a response's headers", warn);
|
|
413
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
414
|
+
const { description, ...schema } = property;
|
|
415
|
+
headers[name] = {
|
|
416
|
+
...typeof description === "string" ? { description } : {},
|
|
417
|
+
...required.has(name) ? { required: true } : {},
|
|
418
|
+
schema
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const cookies = setCookie(declared.cookies, warn);
|
|
422
|
+
if (cookies) {
|
|
423
|
+
headers["set-cookie"] = cookies;
|
|
424
|
+
}
|
|
425
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
426
|
+
}
|
|
427
|
+
function setCookie(schema, warn) {
|
|
428
|
+
const [properties, required] = shapeOf2(schema, "a response's cookies", warn);
|
|
429
|
+
const names = Object.keys(properties);
|
|
430
|
+
if (names.length === 0) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const lines = names.map((name) => {
|
|
434
|
+
const description = properties[name]?.["description"];
|
|
435
|
+
return typeof description === "string" ? `- \`${name}\`: ${description}` : `- \`${name}\``;
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
description: ["Sets these cookies:", "", ...lines].join(`
|
|
439
|
+
`),
|
|
440
|
+
...required.size > 0 ? { required: true } : {},
|
|
441
|
+
schema: { type: "string" }
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function shapeOf2(schema, subject, warn) {
|
|
445
|
+
const described = schema ? emitted(schema, subject, warn, "input") : undefined;
|
|
446
|
+
const properties = described?.properties ?? {};
|
|
447
|
+
const required = new Set(Array.isArray(described?.required) ? described.required : []);
|
|
448
|
+
return [properties, required];
|
|
396
449
|
}
|
|
397
450
|
function branchesFor(schema, status, envelopes, warn) {
|
|
398
451
|
return branchesOf(schema, envelopes.format.code).map((branch) => {
|
|
@@ -463,7 +516,7 @@ function derivedId(entry) {
|
|
|
463
516
|
}
|
|
464
517
|
function requestBody(entry, warn) {
|
|
465
518
|
const declared = entry.def.schema?.body;
|
|
466
|
-
const bodyType = entry.def.bodyType ?? (declared ? "json" : undefined);
|
|
519
|
+
const bodyType = entry.def.bodyType ?? (declared || entry.def.rawBody ? "json" : undefined);
|
|
467
520
|
if (!bodyType) {
|
|
468
521
|
return;
|
|
469
522
|
}
|
|
@@ -508,15 +561,18 @@ function responses(entry, options, contributed, components, envelopes, warn) {
|
|
|
508
561
|
const add = (status, answer) => {
|
|
509
562
|
answers.set(status, [...answers.get(status) ?? [], answer]);
|
|
510
563
|
};
|
|
511
|
-
|
|
512
|
-
|
|
564
|
+
const declared = declaredResponses(entry);
|
|
565
|
+
for (const [status, { body, ...rest }] of declared) {
|
|
566
|
+
const described = body === null ? undefined : emitted(body, "a response", warn);
|
|
567
|
+
const headers = declaredHeaders({ body, ...rest }, warn);
|
|
513
568
|
add(status, {
|
|
514
569
|
placeholder: describeStatus(status),
|
|
515
570
|
said: described ? wordsOf(described, envelopes) : [],
|
|
516
|
-
schemas: described ? branchesFor(unionOnly(described), Number(status), envelopes, warn) : []
|
|
571
|
+
schemas: described ? branchesFor(unionOnly(described), Number(status), envelopes, warn) : [],
|
|
572
|
+
...headers === undefined ? {} : { headers }
|
|
517
573
|
});
|
|
518
574
|
}
|
|
519
|
-
if (
|
|
575
|
+
if (declared.length === 0) {
|
|
520
576
|
add("200", { placeholder: "Successful response", said: [], schemas: [] });
|
|
521
577
|
}
|
|
522
578
|
for (const [status, answer] of failures(entry, options, contributed, components, envelopes, warn)) {
|
|
@@ -541,7 +597,7 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
541
597
|
const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
|
|
542
598
|
return ref;
|
|
543
599
|
};
|
|
544
|
-
if (schema?.params || schema?.query || schema?.headers || schema?.body) {
|
|
600
|
+
if (schema?.params || schema?.query || schema?.headers || schema?.cookies || schema?.body) {
|
|
545
601
|
found.push([
|
|
546
602
|
options.validationStatus,
|
|
547
603
|
{
|
|
@@ -587,7 +643,7 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
587
643
|
}
|
|
588
644
|
]);
|
|
589
645
|
}
|
|
590
|
-
if (schema?.body || entry.def.bodyType) {
|
|
646
|
+
if (schema?.body || entry.def.bodyType || entry.def.rawBody) {
|
|
591
647
|
const unparsable = parseFailure(entry.def.bodyType);
|
|
592
648
|
if (unparsable) {
|
|
593
649
|
found.push([
|
|
@@ -745,6 +801,63 @@ function capitalize(value) {
|
|
|
745
801
|
function lowerFirst(value) {
|
|
746
802
|
return value.charAt(0).toLowerCase() + value.slice(1);
|
|
747
803
|
}
|
|
804
|
+
|
|
805
|
+
// packages/openapi/src/refs.ts
|
|
806
|
+
var data = new Set(["const", "default", "enum", "example", "examples"]);
|
|
807
|
+
function unresolvedRefs(node, document) {
|
|
808
|
+
const unresolved = new Set;
|
|
809
|
+
const visited = new Map;
|
|
810
|
+
const pending = [[node, document]];
|
|
811
|
+
for (let next = pending.pop();next; next = pending.pop()) {
|
|
812
|
+
const [value, base] = next;
|
|
813
|
+
if (typeof value !== "object" || value === null) {
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const bases = visited.get(value) ?? new Set;
|
|
817
|
+
if (bases.has(base)) {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
visited.set(value, bases.add(base));
|
|
821
|
+
const schema = value;
|
|
822
|
+
const resource = !Array.isArray(value) && typeof schema["$id"] === "string" ? schema : base;
|
|
823
|
+
const ref = Array.isArray(value) ? undefined : schema["$ref"];
|
|
824
|
+
if (typeof ref === "string" && ref.startsWith("#") && !resolves(ref.slice(1), resource, document)) {
|
|
825
|
+
unresolved.add(ref);
|
|
826
|
+
}
|
|
827
|
+
const children = Object.entries(schema).filter(([keyword]) => Array.isArray(value) || !data.has(keyword));
|
|
828
|
+
for (let index = children.length - 1;index >= 0; index -= 1) {
|
|
829
|
+
pending.push([children[index]?.[1], resource]);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return [...unresolved];
|
|
833
|
+
}
|
|
834
|
+
function resolves(fragment, base, document) {
|
|
835
|
+
if (fragment === "") {
|
|
836
|
+
return base !== document;
|
|
837
|
+
}
|
|
838
|
+
if (!fragment.startsWith("/")) {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
let target = base;
|
|
842
|
+
for (const token of fragment.slice(1).split("/")) {
|
|
843
|
+
if (typeof target !== "object" || target === null) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
const key = decoded(token);
|
|
847
|
+
if (key === undefined || !Object.hasOwn(target, key)) {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
target = target[key];
|
|
851
|
+
}
|
|
852
|
+
return typeof target === "object" && target !== null || typeof target === "boolean";
|
|
853
|
+
}
|
|
854
|
+
function decoded(token) {
|
|
855
|
+
try {
|
|
856
|
+
return decodeURIComponent(token).replaceAll("~1", "/").replaceAll("~0", "~");
|
|
857
|
+
} catch {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
748
861
|
// packages/openapi/src/controller.ts
|
|
749
862
|
import { onMount, route } from "@tetsujs/core";
|
|
750
863
|
|
|
@@ -787,7 +900,7 @@ function docsPage(options) {
|
|
|
787
900
|
"<body>",
|
|
788
901
|
body(options.ui, url),
|
|
789
902
|
`<script src="${escapeHtml(assets.script)}"${verified(assets.integrity?.script)}></script>`,
|
|
790
|
-
bootstrap(options.ui
|
|
903
|
+
bootstrap(options.ui),
|
|
791
904
|
"</body>",
|
|
792
905
|
"</html>"
|
|
793
906
|
].filter(Boolean).join(`
|
|
@@ -800,15 +913,16 @@ function body(ui, url) {
|
|
|
800
913
|
if (ui === "redoc") {
|
|
801
914
|
return `<redoc spec-url="${url}"></redoc>`;
|
|
802
915
|
}
|
|
803
|
-
return
|
|
916
|
+
return `<div id="swagger-ui" data-url="${url}"></div>`;
|
|
804
917
|
}
|
|
805
|
-
function bootstrap(ui
|
|
918
|
+
function bootstrap(ui) {
|
|
806
919
|
if (ui !== "swagger-ui") {
|
|
807
920
|
return "";
|
|
808
921
|
}
|
|
809
922
|
return [
|
|
810
923
|
"<script>",
|
|
811
|
-
|
|
924
|
+
'const root = document.getElementById("swagger-ui");',
|
|
925
|
+
'SwaggerUIBundle({ url: root.dataset.url, dom_id: "#swagger-ui" });',
|
|
812
926
|
"</script>"
|
|
813
927
|
].join(`
|
|
814
928
|
`);
|
|
@@ -938,17 +1052,36 @@ function openapi(app, options) {
|
|
|
938
1052
|
};
|
|
939
1053
|
}
|
|
940
1054
|
const tags = options.tags ? tagsOf(options.tags, used, warnings) : undefined;
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
...componentsOf(schemes, components.schemas)
|
|
949
|
-
},
|
|
950
|
-
warnings
|
|
1055
|
+
const document = {
|
|
1056
|
+
openapi: "3.1.0",
|
|
1057
|
+
info: options.info,
|
|
1058
|
+
...options.servers ? { servers: options.servers } : {},
|
|
1059
|
+
...tags ? { tags } : {},
|
|
1060
|
+
paths,
|
|
1061
|
+
...componentsOf(schemes, components.schemas)
|
|
951
1062
|
};
|
|
1063
|
+
refsOf(document, occupied, warnings);
|
|
1064
|
+
return { document, warnings };
|
|
1065
|
+
}
|
|
1066
|
+
function refsOf(document, routes, warnings) {
|
|
1067
|
+
const unresolved = (ref) => `refers to "${ref}", which is no schema in the document: references are embedded as they are, and resolve against the document's root`;
|
|
1068
|
+
for (const [template, item] of Object.entries(document.paths)) {
|
|
1069
|
+
for (const [method, operation] of Object.entries(item)) {
|
|
1070
|
+
const route = routes.get(`${method} ${template}`) ?? "";
|
|
1071
|
+
for (const ref of unresolvedRefs(operation, document)) {
|
|
1072
|
+
warnings.push({ route, message: `a schema ${unresolved(ref)}` });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const schemas = document.components?.schemas ?? {};
|
|
1077
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1078
|
+
for (const ref of unresolvedRefs(schema, document)) {
|
|
1079
|
+
warnings.push({
|
|
1080
|
+
route: "",
|
|
1081
|
+
message: `components/schemas/${name} ${unresolved(ref)}`
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
952
1085
|
}
|
|
953
1086
|
function tagsOf(described, used, warnings) {
|
|
954
1087
|
const tags = Object.entries(described).map(([name, description]) => ({ name, description }));
|
|
@@ -988,5 +1121,5 @@ export {
|
|
|
988
1121
|
secured
|
|
989
1122
|
};
|
|
990
1123
|
|
|
991
|
-
//# debugId=
|
|
1124
|
+
//# debugId=A9BC9A29B6C6C1DA64756E2164756E21
|
|
992
1125
|
//# sourceMappingURL=index.js.map
|