@webpieces/docs-site 0.0.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 +83 -0
- package/package.json +28 -0
- package/src/DocsSiteError.d.ts +24 -0
- package/src/DocsSiteError.js +33 -0
- package/src/DocsSiteError.js.map +1 -0
- package/src/assets/SiteScript.d.ts +11 -0
- package/src/assets/SiteScript.js +88 -0
- package/src/assets/SiteScript.js.map +1 -0
- package/src/assets/SiteStyles.d.ts +13 -0
- package/src/assets/SiteStyles.js +106 -0
- package/src/assets/SiteStyles.js.map +1 -0
- package/src/cli/DocsSiteCli.d.ts +37 -0
- package/src/cli/DocsSiteCli.js +116 -0
- package/src/cli/DocsSiteCli.js.map +1 -0
- package/src/cli/WpDocsSiteMain.d.ts +21 -0
- package/src/cli/WpDocsSiteMain.js +60 -0
- package/src/cli/WpDocsSiteMain.js.map +1 -0
- package/src/cli/wp-docs-site.d.ts +2 -0
- package/src/cli/wp-docs-site.js +22 -0
- package/src/cli/wp-docs-site.js.map +1 -0
- package/src/emit/SiteWriter.d.ts +13 -0
- package/src/emit/SiteWriter.js +34 -0
- package/src/emit/SiteWriter.js.map +1 -0
- package/src/index.d.ts +41 -0
- package/src/index.js +105 -0
- package/src/index.js.map +1 -0
- package/src/manifest/DocsManifest.d.ts +28 -0
- package/src/manifest/DocsManifest.js +44 -0
- package/src/manifest/DocsManifest.js.map +1 -0
- package/src/manifest/ProseLoader.d.ts +23 -0
- package/src/manifest/ProseLoader.js +81 -0
- package/src/manifest/ProseLoader.js.map +1 -0
- package/src/markdown/Markdown.d.ts +47 -0
- package/src/markdown/Markdown.js +178 -0
- package/src/markdown/Markdown.js.map +1 -0
- package/src/render/CardRenderer.d.ts +28 -0
- package/src/render/CardRenderer.js +70 -0
- package/src/render/CardRenderer.js.map +1 -0
- package/src/render/FieldTreeRenderer.d.ts +34 -0
- package/src/render/FieldTreeRenderer.js +91 -0
- package/src/render/FieldTreeRenderer.js.map +1 -0
- package/src/render/Html.d.ts +14 -0
- package/src/render/Html.js +28 -0
- package/src/render/Html.js.map +1 -0
- package/src/render/Layout.d.ts +21 -0
- package/src/render/Layout.js +74 -0
- package/src/render/Layout.js.map +1 -0
- package/src/render/NavModel.d.ts +47 -0
- package/src/render/NavModel.js +86 -0
- package/src/render/NavModel.js.map +1 -0
- package/src/render/OperationPageRenderer.d.ts +44 -0
- package/src/render/OperationPageRenderer.js +102 -0
- package/src/render/OperationPageRenderer.js.map +1 -0
- package/src/render/SitePage.d.ts +30 -0
- package/src/render/SitePage.js +46 -0
- package/src/render/SitePage.js.map +1 -0
- package/src/render/SiteRenderer.d.ts +34 -0
- package/src/render/SiteRenderer.js +121 -0
- package/src/render/SiteRenderer.js.map +1 -0
- package/src/render/SiteUrls.d.ts +24 -0
- package/src/render/SiteUrls.js +37 -0
- package/src/render/SiteUrls.js.map +1 -0
- package/src/samples/CodeSamples.d.ts +49 -0
- package/src/samples/CodeSamples.js +166 -0
- package/src/samples/CodeSamples.js.map +1 -0
- package/src/serve/DevServer.d.ts +35 -0
- package/src/serve/DevServer.js +109 -0
- package/src/serve/DevServer.js.map +1 -0
- package/src/spec/ApiSpec.d.ts +110 -0
- package/src/spec/ApiSpec.js +165 -0
- package/src/spec/ApiSpec.js.map +1 -0
- package/src/spec/ExampleBuilder.d.ts +32 -0
- package/src/spec/ExampleBuilder.js +119 -0
- package/src/spec/ExampleBuilder.js.map +1 -0
- package/src/spec/FieldTree.d.ts +75 -0
- package/src/spec/FieldTree.js +155 -0
- package/src/spec/FieldTree.js.map +1 -0
- package/src/spec/JsonNode.d.ts +53 -0
- package/src/spec/JsonNode.js +93 -0
- package/src/spec/JsonNode.js.map +1 -0
- package/src/spec/SchemaLens.d.ts +57 -0
- package/src/spec/SchemaLens.js +162 -0
- package/src/spec/SchemaLens.js.map +1 -0
- package/src/spec/SchemaShape.d.ts +22 -0
- package/src/spec/SchemaShape.js +61 -0
- package/src/spec/SchemaShape.js.map +1 -0
- package/src/spec/Slug.d.ts +16 -0
- package/src/spec/Slug.js +43 -0
- package/src/spec/Slug.js.map +1 -0
- package/src/spec/SpecLoader.d.ts +26 -0
- package/src/spec/SpecLoader.js +64 -0
- package/src/spec/SpecLoader.js.map +1 -0
- package/src/spec/SpecReader.d.ts +46 -0
- package/src/spec/SpecReader.js +174 -0
- package/src/spec/SpecReader.js.map +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonEntry = exports.JsonNode = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A READER over one JSON value, and the reason this package can render a document it did not
|
|
6
|
+
* generate.
|
|
7
|
+
*
|
|
8
|
+
* The alternative — importing `@webpieces/openapi-generator`'s document classes, which are
|
|
9
|
+
* structurally identical — would make "generated by us" a precondition for "renderable by us"
|
|
10
|
+
* (issue #985). So the document is read STRUCTURALLY: every accessor asks "is there a string at this
|
|
11
|
+
* key", never "is this an instance of our class", and a key that is absent or the wrong shape comes
|
|
12
|
+
* back `undefined` instead of throwing. A conforming document written by any other tool reads the
|
|
13
|
+
* same way.
|
|
14
|
+
*
|
|
15
|
+
* It is deliberately total rather than validating. A docs site that refuses to render because one
|
|
16
|
+
* optional `description` was a number is worse than one that renders the rest: the reader loses a
|
|
17
|
+
* whole reference over a field they were not looking at.
|
|
18
|
+
*/
|
|
19
|
+
class JsonNode {
|
|
20
|
+
raw;
|
|
21
|
+
// webpieces-disable no-any-unknown -- the document is FOREIGN input; narrowing it is this class's whole job
|
|
22
|
+
constructor(raw) {
|
|
23
|
+
this.raw = raw;
|
|
24
|
+
}
|
|
25
|
+
/** True when this node is a JSON object (and not an array, and not null). */
|
|
26
|
+
isObject() {
|
|
27
|
+
return typeof this.raw === 'object' && this.raw !== null && !Array.isArray(this.raw);
|
|
28
|
+
}
|
|
29
|
+
/** The keys of this object, in document order. Empty for anything that is not an object. */
|
|
30
|
+
keys() {
|
|
31
|
+
if (!this.isObject()) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return Object.keys(this.raw);
|
|
35
|
+
}
|
|
36
|
+
/** The child at `key`, always a node — an absent key reads as a node over `undefined`. */
|
|
37
|
+
at(key) {
|
|
38
|
+
if (!this.isObject()) {
|
|
39
|
+
return new JsonNode(undefined);
|
|
40
|
+
}
|
|
41
|
+
return new JsonNode(this.raw[key]);
|
|
42
|
+
}
|
|
43
|
+
/** The string at `key`, or `undefined` when it is absent or is not a string. */
|
|
44
|
+
text(key) {
|
|
45
|
+
const value = this.at(key).raw;
|
|
46
|
+
return typeof value === 'string' ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
/** This node itself as a string, or `undefined`. */
|
|
49
|
+
asText() {
|
|
50
|
+
return typeof this.raw === 'string' ? this.raw : undefined;
|
|
51
|
+
}
|
|
52
|
+
/** The boolean at `key`, or `undefined`. A missing flag is not `false`; the caller decides. */
|
|
53
|
+
flag(key) {
|
|
54
|
+
const value = this.at(key).raw;
|
|
55
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
56
|
+
}
|
|
57
|
+
/** The number at `key`, or `undefined`. */
|
|
58
|
+
number(key) {
|
|
59
|
+
const value = this.at(key).raw;
|
|
60
|
+
return typeof value === 'number' ? value : undefined;
|
|
61
|
+
}
|
|
62
|
+
/** The array at `key` as nodes. Empty when absent or not an array. */
|
|
63
|
+
list(key) {
|
|
64
|
+
return this.at(key).asList();
|
|
65
|
+
}
|
|
66
|
+
/** This node itself as an array of nodes. Empty when it is not an array. */
|
|
67
|
+
asList() {
|
|
68
|
+
if (!Array.isArray(this.raw)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return this.raw.map((entry) => new JsonNode(entry));
|
|
72
|
+
}
|
|
73
|
+
/** Every own entry of this object as `[key, node]`, in document order. */
|
|
74
|
+
entries() {
|
|
75
|
+
return this.keys().map((key) => new JsonEntry(key, this.at(key)));
|
|
76
|
+
}
|
|
77
|
+
/** True when this node holds nothing at all — an absent key, or an explicit `null`. */
|
|
78
|
+
isAbsent() {
|
|
79
|
+
return this.raw === undefined || this.raw === null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.JsonNode = JsonNode;
|
|
83
|
+
/** One `[key, value]` pair of a JSON object, as a class because it is data. */
|
|
84
|
+
class JsonEntry {
|
|
85
|
+
key;
|
|
86
|
+
value;
|
|
87
|
+
constructor(key, value) {
|
|
88
|
+
this.key = key;
|
|
89
|
+
this.value = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.JsonEntry = JsonEntry;
|
|
93
|
+
//# sourceMappingURL=JsonNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JsonNode.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/spec/JsonNode.ts"],"names":[],"mappings":"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","sourcesContent":["/** Everything an OpenAPI document can hold. It is JSON, so this is the whole value space. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | JsonMap;\n\n/** The object case of {@link JsonValue}, named so the recursive alias can refer to it. */\nexport type JsonMap = { [key: string]: JsonValue };\n\n/**\n * A READER over one JSON value, and the reason this package can render a document it did not\n * generate.\n *\n * The alternative — importing `@webpieces/openapi-generator`'s document classes, which are\n * structurally identical — would make \"generated by us\" a precondition for \"renderable by us\"\n * (issue #985). So the document is read STRUCTURALLY: every accessor asks \"is there a string at this\n * key\", never \"is this an instance of our class\", and a key that is absent or the wrong shape comes\n * back `undefined` instead of throwing. A conforming document written by any other tool reads the\n * same way.\n *\n * It is deliberately total rather than validating. A docs site that refuses to render because one\n * optional `description` was a number is worse than one that renders the rest: the reader loses a\n * whole reference over a field they were not looking at.\n */\nexport class JsonNode {\n // webpieces-disable no-any-unknown -- the document is FOREIGN input; narrowing it is this class's whole job\n constructor(readonly raw: unknown) {}\n\n /** True when this node is a JSON object (and not an array, and not null). */\n isObject(): boolean {\n return typeof this.raw === 'object' && this.raw !== null && !Array.isArray(this.raw);\n }\n\n /** The keys of this object, in document order. Empty for anything that is not an object. */\n keys(): readonly string[] {\n if (!this.isObject()) {\n return [];\n }\n return Object.keys(this.raw as JsonMap);\n }\n\n /** The child at `key`, always a node — an absent key reads as a node over `undefined`. */\n at(key: string): JsonNode {\n if (!this.isObject()) {\n return new JsonNode(undefined);\n }\n return new JsonNode((this.raw as JsonMap)[key]);\n }\n\n /** The string at `key`, or `undefined` when it is absent or is not a string. */\n text(key: string): string | undefined {\n const value = this.at(key).raw;\n return typeof value === 'string' ? value : undefined;\n }\n\n /** This node itself as a string, or `undefined`. */\n asText(): string | undefined {\n return typeof this.raw === 'string' ? this.raw : undefined;\n }\n\n /** The boolean at `key`, or `undefined`. A missing flag is not `false`; the caller decides. */\n flag(key: string): boolean | undefined {\n const value = this.at(key).raw;\n return typeof value === 'boolean' ? value : undefined;\n }\n\n /** The number at `key`, or `undefined`. */\n number(key: string): number | undefined {\n const value = this.at(key).raw;\n return typeof value === 'number' ? value : undefined;\n }\n\n /** The array at `key` as nodes. Empty when absent or not an array. */\n list(key: string): readonly JsonNode[] {\n return this.at(key).asList();\n }\n\n /** This node itself as an array of nodes. Empty when it is not an array. */\n asList(): readonly JsonNode[] {\n if (!Array.isArray(this.raw)) {\n return [];\n }\n return (this.raw as JsonValue[]).map((entry: JsonValue): JsonNode => new JsonNode(entry));\n }\n\n /** Every own entry of this object as `[key, node]`, in document order. */\n entries(): readonly JsonEntry[] {\n return this.keys().map((key: string): JsonEntry => new JsonEntry(key, this.at(key)));\n }\n\n /** True when this node holds nothing at all — an absent key, or an explicit `null`. */\n isAbsent(): boolean {\n return this.raw === undefined || this.raw === null;\n }\n}\n\n/** One `[key, value]` pair of a JSON object, as a class because it is data. */\nexport class JsonEntry {\n constructor(\n readonly key: string,\n readonly value: JsonNode,\n ) {}\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ApiSpec, NamedSchema } from './ApiSpec';
|
|
2
|
+
import { JsonNode } from './JsonNode';
|
|
3
|
+
import { SchemaShape } from './SchemaShape';
|
|
4
|
+
/** What a `$ref` (in any of its three spellings) points at, plus whether the site said it may be null. */
|
|
5
|
+
export declare class RefTarget {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly nullable: boolean;
|
|
8
|
+
constructor(name: string, nullable: boolean);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Everything "what does this schema node SAY" — reference reading and the type label.
|
|
12
|
+
*
|
|
13
|
+
* ## A reference arrives in three shapes, and reading only `$ref` drops two
|
|
14
|
+
*
|
|
15
|
+
* | shape | written by |
|
|
16
|
+
* |---|---|
|
|
17
|
+
* | `{ "$ref": "…", "description": "…" }` | OpenAPI 3.1, where prose beside a `$ref` is legal |
|
|
18
|
+
* | `{ "anyOf": [{ "$ref": "…" }, { "type": "null" }] }` | OpenAPI 3.1's nullable reference |
|
|
19
|
+
* | `{ "allOf": [{ "$ref": "…" }], "description": "…" }` | OpenAPI 3.0, whose `$ref` siblings are ignored |
|
|
20
|
+
*
|
|
21
|
+
* All three render, because this package renders any conforming document and the third one is what
|
|
22
|
+
* every 3.0 generator in the world emits. Reading only the first would silently drop a field's whole
|
|
23
|
+
* type on a 3.0 document — the field would render with no type at all, which a reader reads as "we
|
|
24
|
+
* forgot to document it".
|
|
25
|
+
*/
|
|
26
|
+
export declare class SchemaLens {
|
|
27
|
+
private readonly spec;
|
|
28
|
+
private readonly shape;
|
|
29
|
+
constructor(spec: ApiSpec);
|
|
30
|
+
/** What a node says about itself, with no catalog needed — see {@link SchemaShape}. */
|
|
31
|
+
shapeOf(): SchemaShape;
|
|
32
|
+
/** The reference this node makes, in whichever of the three shapes it was written. */
|
|
33
|
+
refTargetOf(node: JsonNode): RefTarget | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The schema a node ultimately describes: the reference target when it is one, else the node.
|
|
36
|
+
* An UNDEFINED `$ref` resolves to the node itself, so a dangling pointer renders inline as
|
|
37
|
+
* whatever it says rather than becoming a dead link.
|
|
38
|
+
*/
|
|
39
|
+
resolve(node: JsonNode): JsonNode;
|
|
40
|
+
/** The named schema a node refers to, when the document actually defines it. */
|
|
41
|
+
named(node: JsonNode): NamedSchema | undefined;
|
|
42
|
+
/** The prose for a field: what the USE SITE says, falling back to what the TYPE says. */
|
|
43
|
+
describe(node: JsonNode): string;
|
|
44
|
+
/**
|
|
45
|
+
* The type label a reader sees. `format` renders inside angle brackets — `string<date-time>` —
|
|
46
|
+
* so the format is visibly a refinement of the type rather than a second type.
|
|
47
|
+
*/
|
|
48
|
+
label(node: JsonNode): string;
|
|
49
|
+
private labelOfInline;
|
|
50
|
+
/** The `oneOf` branches of a node, resolved through a `$ref` if it is one. Empty when it is not a union. */
|
|
51
|
+
branchesOf(node: JsonNode): readonly JsonNode[];
|
|
52
|
+
/** The complete `Possible values:` list, from the node or from the type it refers to. */
|
|
53
|
+
enumValuesOf(node: JsonNode): readonly string[];
|
|
54
|
+
private stringsIn;
|
|
55
|
+
private singleRefIn;
|
|
56
|
+
private nameOfPointer;
|
|
57
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaLens = exports.RefTarget = void 0;
|
|
4
|
+
const SchemaShape_1 = require("./SchemaShape");
|
|
5
|
+
/** What a `$ref` (in any of its three spellings) points at, plus whether the site said it may be null. */
|
|
6
|
+
class RefTarget {
|
|
7
|
+
name;
|
|
8
|
+
nullable;
|
|
9
|
+
constructor(name, nullable) {
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.nullable = nullable;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.RefTarget = RefTarget;
|
|
15
|
+
const SCHEMA_POINTER = '#/components/schemas/';
|
|
16
|
+
/**
|
|
17
|
+
* Everything "what does this schema node SAY" — reference reading and the type label.
|
|
18
|
+
*
|
|
19
|
+
* ## A reference arrives in three shapes, and reading only `$ref` drops two
|
|
20
|
+
*
|
|
21
|
+
* | shape | written by |
|
|
22
|
+
* |---|---|
|
|
23
|
+
* | `{ "$ref": "…", "description": "…" }` | OpenAPI 3.1, where prose beside a `$ref` is legal |
|
|
24
|
+
* | `{ "anyOf": [{ "$ref": "…" }, { "type": "null" }] }` | OpenAPI 3.1's nullable reference |
|
|
25
|
+
* | `{ "allOf": [{ "$ref": "…" }], "description": "…" }` | OpenAPI 3.0, whose `$ref` siblings are ignored |
|
|
26
|
+
*
|
|
27
|
+
* All three render, because this package renders any conforming document and the third one is what
|
|
28
|
+
* every 3.0 generator in the world emits. Reading only the first would silently drop a field's whole
|
|
29
|
+
* type on a 3.0 document — the field would render with no type at all, which a reader reads as "we
|
|
30
|
+
* forgot to document it".
|
|
31
|
+
*/
|
|
32
|
+
class SchemaLens {
|
|
33
|
+
spec;
|
|
34
|
+
shape = new SchemaShape_1.SchemaShape();
|
|
35
|
+
constructor(spec) {
|
|
36
|
+
this.spec = spec;
|
|
37
|
+
}
|
|
38
|
+
/** What a node says about itself, with no catalog needed — see {@link SchemaShape}. */
|
|
39
|
+
shapeOf() {
|
|
40
|
+
return this.shape;
|
|
41
|
+
}
|
|
42
|
+
/** The reference this node makes, in whichever of the three shapes it was written. */
|
|
43
|
+
refTargetOf(node) {
|
|
44
|
+
const direct = this.nameOfPointer(node.text('$ref'));
|
|
45
|
+
if (direct !== undefined) {
|
|
46
|
+
return new RefTarget(direct, this.shape.isNullable(node));
|
|
47
|
+
}
|
|
48
|
+
const wrapped = this.singleRefIn(node.list('allOf'));
|
|
49
|
+
if (wrapped !== undefined) {
|
|
50
|
+
return new RefTarget(wrapped, this.shape.isNullable(node));
|
|
51
|
+
}
|
|
52
|
+
const nullable = this.singleRefIn(node.list('anyOf'));
|
|
53
|
+
if (nullable !== undefined) {
|
|
54
|
+
return new RefTarget(nullable, true);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The schema a node ultimately describes: the reference target when it is one, else the node.
|
|
60
|
+
* An UNDEFINED `$ref` resolves to the node itself, so a dangling pointer renders inline as
|
|
61
|
+
* whatever it says rather than becoming a dead link.
|
|
62
|
+
*/
|
|
63
|
+
resolve(node) {
|
|
64
|
+
const target = this.refTargetOf(node);
|
|
65
|
+
if (target === undefined) {
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
const named = this.spec.schemaNamed(target.name);
|
|
69
|
+
return named === undefined ? node : named.node;
|
|
70
|
+
}
|
|
71
|
+
/** The named schema a node refers to, when the document actually defines it. */
|
|
72
|
+
named(node) {
|
|
73
|
+
const target = this.refTargetOf(node);
|
|
74
|
+
if (target === undefined) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return this.spec.schemaNamed(target.name);
|
|
78
|
+
}
|
|
79
|
+
/** The prose for a field: what the USE SITE says, falling back to what the TYPE says. */
|
|
80
|
+
describe(node) {
|
|
81
|
+
const own = node.text('description');
|
|
82
|
+
if (own !== undefined && own !== '') {
|
|
83
|
+
return own;
|
|
84
|
+
}
|
|
85
|
+
const named = this.named(node);
|
|
86
|
+
return named === undefined ? '' : (named.node.text('description') ?? '');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The type label a reader sees. `format` renders inside angle brackets — `string<date-time>` —
|
|
90
|
+
* so the format is visibly a refinement of the type rather than a second type.
|
|
91
|
+
*/
|
|
92
|
+
label(node) {
|
|
93
|
+
const named = this.named(node);
|
|
94
|
+
if (named !== undefined) {
|
|
95
|
+
return named.name;
|
|
96
|
+
}
|
|
97
|
+
const target = this.refTargetOf(node);
|
|
98
|
+
if (target !== undefined) {
|
|
99
|
+
return target.name;
|
|
100
|
+
}
|
|
101
|
+
return this.labelOfInline(node);
|
|
102
|
+
}
|
|
103
|
+
labelOfInline(node) {
|
|
104
|
+
const declared = this.shape.declaredType(node);
|
|
105
|
+
if (declared === 'array') {
|
|
106
|
+
return `array<${this.label(node.at('items'))}>`;
|
|
107
|
+
}
|
|
108
|
+
if (declared === 'object' && node.at('additionalProperties').isObject()) {
|
|
109
|
+
return `map<string, ${this.label(node.at('additionalProperties'))}>`;
|
|
110
|
+
}
|
|
111
|
+
const branches = this.branchesOf(node);
|
|
112
|
+
if (branches.length > 0) {
|
|
113
|
+
return branches.map((branch) => this.label(branch)).join(' | ');
|
|
114
|
+
}
|
|
115
|
+
const format = node.text('format');
|
|
116
|
+
if (declared !== undefined && format !== undefined) {
|
|
117
|
+
return `${declared}<${format}>`;
|
|
118
|
+
}
|
|
119
|
+
return declared ?? 'object';
|
|
120
|
+
}
|
|
121
|
+
/** The `oneOf` branches of a node, resolved through a `$ref` if it is one. Empty when it is not a union. */
|
|
122
|
+
branchesOf(node) {
|
|
123
|
+
const resolved = this.resolve(node);
|
|
124
|
+
const branches = resolved.list('oneOf');
|
|
125
|
+
return branches.length > 0 ? branches : [];
|
|
126
|
+
}
|
|
127
|
+
/** The complete `Possible values:` list, from the node or from the type it refers to. */
|
|
128
|
+
enumValuesOf(node) {
|
|
129
|
+
const own = this.stringsIn(node.list('enum'));
|
|
130
|
+
if (own.length > 0) {
|
|
131
|
+
return own;
|
|
132
|
+
}
|
|
133
|
+
return this.stringsIn(this.resolve(node).list('enum'));
|
|
134
|
+
}
|
|
135
|
+
stringsIn(nodes) {
|
|
136
|
+
const values = [];
|
|
137
|
+
for (const entry of nodes) {
|
|
138
|
+
const raw = entry.raw;
|
|
139
|
+
if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {
|
|
140
|
+
values.push(String(raw));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return values;
|
|
144
|
+
}
|
|
145
|
+
singleRefIn(branches) {
|
|
146
|
+
for (const branch of branches) {
|
|
147
|
+
const name = this.nameOfPointer(branch.text('$ref'));
|
|
148
|
+
if (name !== undefined) {
|
|
149
|
+
return name;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
nameOfPointer(pointer) {
|
|
155
|
+
if (pointer === undefined || !pointer.startsWith(SCHEMA_POINTER)) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
return pointer.slice(SCHEMA_POINTER.length);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.SchemaLens = SchemaLens;
|
|
162
|
+
//# sourceMappingURL=SchemaLens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaLens.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/spec/SchemaLens.ts"],"names":[],"mappings":";;;AAEA,+CAA4C;AAE5C,0GAA0G;AAC1G,MAAa,SAAS;IAEL;IACA;IAFb,YACa,IAAY,EACZ,QAAiB;QADjB,SAAI,GAAJ,IAAI,CAAQ;QACZ,aAAQ,GAAR,QAAQ,CAAS;IAC3B,CAAC;CACP;AALD,8BAKC;AAED,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,MAAa,UAAU;IAGU;IAFZ,KAAK,GAAG,IAAI,yBAAW,EAAE,CAAC;IAE3C,YAA6B,IAAa;QAAb,SAAI,GAAJ,IAAI,CAAS;IAAG,CAAC;IAE9C,uFAAuF;IACvF,OAAO;QACH,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,sFAAsF;IACtF,WAAW,CAAC,IAAc;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACrD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,IAAc;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IACnD,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,IAAc;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,yFAAyF;IACzF,QAAQ,CAAC,IAAc;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YAClC,OAAO,GAAG,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAc;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,KAAK,CAAC,IAAI,CAAC;QACtB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC,IAAI,CAAC;QACvB,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAEO,aAAa,CAAC,IAAc;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC;QACpD,CAAC;QACD,IAAI,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YACtE,OAAO,eAAe,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,GAAG,CAAC;QACzE,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAgB,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,QAAQ,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACjD,OAAO,GAAG,QAAQ,IAAI,MAAM,GAAG,CAAC;QACpC,CAAC;QACD,OAAO,QAAQ,IAAI,QAAQ,CAAC;IAChC,CAAC;IAED,4GAA4G;IAC5G,UAAU,CAAC,IAAc;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,yFAAyF;IACzF,YAAY,CAAC,IAAc;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9C,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjB,OAAO,GAAG,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,CAAC;IAEO,SAAS,CAAC,KAA0B;QACxC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;YACtB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,SAAS,EAAE,CAAC;gBACjF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAEO,WAAW,CAAC,QAA6B;QAC7C,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YACrD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACrB,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAEO,aAAa,CAAC,OAA2B;QAC7C,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/D,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;CACJ;AA1ID,gCA0IC","sourcesContent":["import { ApiSpec, NamedSchema } from './ApiSpec';\nimport { JsonNode } from './JsonNode';\nimport { SchemaShape } from './SchemaShape';\n\n/** What a `$ref` (in any of its three spellings) points at, plus whether the site said it may be null. */\nexport class RefTarget {\n constructor(\n readonly name: string,\n readonly nullable: boolean,\n ) {}\n}\n\nconst SCHEMA_POINTER = '#/components/schemas/';\n\n/**\n * Everything \"what does this schema node SAY\" — reference reading and the type label.\n *\n * ## A reference arrives in three shapes, and reading only `$ref` drops two\n *\n * | shape | written by |\n * |---|---|\n * | `{ \"$ref\": \"…\", \"description\": \"…\" }` | OpenAPI 3.1, where prose beside a `$ref` is legal |\n * | `{ \"anyOf\": [{ \"$ref\": \"…\" }, { \"type\": \"null\" }] }` | OpenAPI 3.1's nullable reference |\n * | `{ \"allOf\": [{ \"$ref\": \"…\" }], \"description\": \"…\" }` | OpenAPI 3.0, whose `$ref` siblings are ignored |\n *\n * All three render, because this package renders any conforming document and the third one is what\n * every 3.0 generator in the world emits. Reading only the first would silently drop a field's whole\n * type on a 3.0 document — the field would render with no type at all, which a reader reads as \"we\n * forgot to document it\".\n */\nexport class SchemaLens {\n private readonly shape = new SchemaShape();\n\n constructor(private readonly spec: ApiSpec) {}\n\n /** What a node says about itself, with no catalog needed — see {@link SchemaShape}. */\n shapeOf(): SchemaShape {\n return this.shape;\n }\n\n /** The reference this node makes, in whichever of the three shapes it was written. */\n refTargetOf(node: JsonNode): RefTarget | undefined {\n const direct = this.nameOfPointer(node.text('$ref'));\n if (direct !== undefined) {\n return new RefTarget(direct, this.shape.isNullable(node));\n }\n const wrapped = this.singleRefIn(node.list('allOf'));\n if (wrapped !== undefined) {\n return new RefTarget(wrapped, this.shape.isNullable(node));\n }\n const nullable = this.singleRefIn(node.list('anyOf'));\n if (nullable !== undefined) {\n return new RefTarget(nullable, true);\n }\n return undefined;\n }\n\n /**\n * The schema a node ultimately describes: the reference target when it is one, else the node.\n * An UNDEFINED `$ref` resolves to the node itself, so a dangling pointer renders inline as\n * whatever it says rather than becoming a dead link.\n */\n resolve(node: JsonNode): JsonNode {\n const target = this.refTargetOf(node);\n if (target === undefined) {\n return node;\n }\n const named = this.spec.schemaNamed(target.name);\n return named === undefined ? node : named.node;\n }\n\n /** The named schema a node refers to, when the document actually defines it. */\n named(node: JsonNode): NamedSchema | undefined {\n const target = this.refTargetOf(node);\n if (target === undefined) {\n return undefined;\n }\n return this.spec.schemaNamed(target.name);\n }\n\n /** The prose for a field: what the USE SITE says, falling back to what the TYPE says. */\n describe(node: JsonNode): string {\n const own = node.text('description');\n if (own !== undefined && own !== '') {\n return own;\n }\n const named = this.named(node);\n return named === undefined ? '' : (named.node.text('description') ?? '');\n }\n\n /**\n * The type label a reader sees. `format` renders inside angle brackets — `string<date-time>` —\n * so the format is visibly a refinement of the type rather than a second type.\n */\n label(node: JsonNode): string {\n const named = this.named(node);\n if (named !== undefined) {\n return named.name;\n }\n const target = this.refTargetOf(node);\n if (target !== undefined) {\n return target.name;\n }\n return this.labelOfInline(node);\n }\n\n private labelOfInline(node: JsonNode): string {\n const declared = this.shape.declaredType(node);\n if (declared === 'array') {\n return `array<${this.label(node.at('items'))}>`;\n }\n if (declared === 'object' && node.at('additionalProperties').isObject()) {\n return `map<string, ${this.label(node.at('additionalProperties'))}>`;\n }\n const branches = this.branchesOf(node);\n if (branches.length > 0) {\n return branches.map((branch: JsonNode): string => this.label(branch)).join(' | ');\n }\n const format = node.text('format');\n if (declared !== undefined && format !== undefined) {\n return `${declared}<${format}>`;\n }\n return declared ?? 'object';\n }\n\n /** The `oneOf` branches of a node, resolved through a `$ref` if it is one. Empty when it is not a union. */\n branchesOf(node: JsonNode): readonly JsonNode[] {\n const resolved = this.resolve(node);\n const branches = resolved.list('oneOf');\n return branches.length > 0 ? branches : [];\n }\n\n /** The complete `Possible values:` list, from the node or from the type it refers to. */\n enumValuesOf(node: JsonNode): readonly string[] {\n const own = this.stringsIn(node.list('enum'));\n if (own.length > 0) {\n return own;\n }\n return this.stringsIn(this.resolve(node).list('enum'));\n }\n\n private stringsIn(nodes: readonly JsonNode[]): readonly string[] {\n const values: string[] = [];\n for (const entry of nodes) {\n const raw = entry.raw;\n if (typeof raw === 'string' || typeof raw === 'number' || typeof raw === 'boolean') {\n values.push(String(raw));\n }\n }\n return values;\n }\n\n private singleRefIn(branches: readonly JsonNode[]): string | undefined {\n for (const branch of branches) {\n const name = this.nameOfPointer(branch.text('$ref'));\n if (name !== undefined) {\n return name;\n }\n }\n return undefined;\n }\n\n private nameOfPointer(pointer: string | undefined): string | undefined {\n if (pointer === undefined || !pointer.startsWith(SCHEMA_POINTER)) {\n return undefined;\n }\n return pointer.slice(SCHEMA_POINTER.length);\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { JsonNode } from './JsonNode';
|
|
2
|
+
/**
|
|
3
|
+
* What a schema node says about ITSELF, with no catalog of named schemas needed to answer.
|
|
4
|
+
*
|
|
5
|
+
* It is separate from `SchemaLens` because `SpecReader` has to answer "does this named schema
|
|
6
|
+
* deserve a page" while it is still BUILDING the list of named schemas — there is no `ApiSpec` to
|
|
7
|
+
* hand a lens yet. Folding these three questions into the lens would mean constructing a lens over a
|
|
8
|
+
* half-built spec, which is the kind of ordering dependency that works until somebody reorders two
|
|
9
|
+
* lines.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SchemaShape {
|
|
12
|
+
/** True when a NAMED schema deserves its own page: objects and object unions, never scalars. */
|
|
13
|
+
hasOwnPage(node: JsonNode): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The declared `type`, with 3.1's `type: [T, "null"]` reduced to `T`. A nullable field's type
|
|
16
|
+
* column shows the NON-null type: "it is a string, and it may be absent" is one fact about the
|
|
17
|
+
* field, not two types.
|
|
18
|
+
*/
|
|
19
|
+
declaredType(node: JsonNode): string | undefined;
|
|
20
|
+
/** True when the node states, in either dialect, that null is allowed. */
|
|
21
|
+
isNullable(node: JsonNode): boolean;
|
|
22
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaShape = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* What a schema node says about ITSELF, with no catalog of named schemas needed to answer.
|
|
6
|
+
*
|
|
7
|
+
* It is separate from `SchemaLens` because `SpecReader` has to answer "does this named schema
|
|
8
|
+
* deserve a page" while it is still BUILDING the list of named schemas — there is no `ApiSpec` to
|
|
9
|
+
* hand a lens yet. Folding these three questions into the lens would mean constructing a lens over a
|
|
10
|
+
* half-built spec, which is the kind of ordering dependency that works until somebody reorders two
|
|
11
|
+
* lines.
|
|
12
|
+
*/
|
|
13
|
+
class SchemaShape {
|
|
14
|
+
/** True when a NAMED schema deserves its own page: objects and object unions, never scalars. */
|
|
15
|
+
hasOwnPage(node) {
|
|
16
|
+
if (node.at('properties').isObject()) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (node.at('oneOf').asList().length > 0 || node.at('allOf').asList().length > 0) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return this.declaredType(node) === 'object';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The declared `type`, with 3.1's `type: [T, "null"]` reduced to `T`. A nullable field's type
|
|
26
|
+
* column shows the NON-null type: "it is a string, and it may be absent" is one fact about the
|
|
27
|
+
* field, not two types.
|
|
28
|
+
*/
|
|
29
|
+
declaredType(node) {
|
|
30
|
+
const direct = node.text('type');
|
|
31
|
+
if (direct !== undefined) {
|
|
32
|
+
return direct;
|
|
33
|
+
}
|
|
34
|
+
for (const entry of node.list('type')) {
|
|
35
|
+
const name = entry.asText();
|
|
36
|
+
if (name !== undefined && name !== 'null') {
|
|
37
|
+
return name;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/** True when the node states, in either dialect, that null is allowed. */
|
|
43
|
+
isNullable(node) {
|
|
44
|
+
if (node.flag('nullable') === true) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
for (const entry of node.list('type')) {
|
|
48
|
+
if (entry.asText() === 'null') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const branch of node.list('anyOf')) {
|
|
53
|
+
if (branch.text('type') === 'null') {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.SchemaShape = SchemaShape;
|
|
61
|
+
//# sourceMappingURL=SchemaShape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaShape.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/spec/SchemaShape.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;GAQG;AACH,MAAa,WAAW;IACpB,gGAAgG;IAChG,UAAU,CAAC,IAAc;QACrB,IAAI,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/E,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,IAAc;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAClB,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC5B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxC,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,0EAA0E;IAC1E,UAAU,CAAC,IAAc;QACrB,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;gBAC5B,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC;gBACjC,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ;AAhDD,kCAgDC","sourcesContent":["import { JsonNode } from './JsonNode';\n\n/**\n * What a schema node says about ITSELF, with no catalog of named schemas needed to answer.\n *\n * It is separate from `SchemaLens` because `SpecReader` has to answer \"does this named schema\n * deserve a page\" while it is still BUILDING the list of named schemas — there is no `ApiSpec` to\n * hand a lens yet. Folding these three questions into the lens would mean constructing a lens over a\n * half-built spec, which is the kind of ordering dependency that works until somebody reorders two\n * lines.\n */\nexport class SchemaShape {\n /** True when a NAMED schema deserves its own page: objects and object unions, never scalars. */\n hasOwnPage(node: JsonNode): boolean {\n if (node.at('properties').isObject()) {\n return true;\n }\n if (node.at('oneOf').asList().length > 0 || node.at('allOf').asList().length > 0) {\n return true;\n }\n return this.declaredType(node) === 'object';\n }\n\n /**\n * The declared `type`, with 3.1's `type: [T, \"null\"]` reduced to `T`. A nullable field's type\n * column shows the NON-null type: \"it is a string, and it may be absent\" is one fact about the\n * field, not two types.\n */\n declaredType(node: JsonNode): string | undefined {\n const direct = node.text('type');\n if (direct !== undefined) {\n return direct;\n }\n for (const entry of node.list('type')) {\n const name = entry.asText();\n if (name !== undefined && name !== 'null') {\n return name;\n }\n }\n return undefined;\n }\n\n /** True when the node states, in either dialect, that null is allowed. */\n isNullable(node: JsonNode): boolean {\n if (node.flag('nullable') === true) {\n return true;\n }\n for (const entry of node.list('type')) {\n if (entry.asText() === 'null') {\n return true;\n }\n }\n for (const branch of node.list('anyOf')) {\n if (branch.text('type') === 'null') {\n return true;\n }\n }\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL slugs. One pre-rendered HTML file per URL means the slug IS the filename, so it has to be
|
|
3
|
+
* stable, lower-case and free of anything a filesystem or a static host treats specially.
|
|
4
|
+
*
|
|
5
|
+
* Uniqueness is enforced rather than hoped for: two operations that kebab to the same string would
|
|
6
|
+
* otherwise silently overwrite each other's page, and the missing one looks exactly like an
|
|
7
|
+
* operation that was never documented. Uniqueness is per INSTANCE, so operations and schemas —
|
|
8
|
+
* which live in different directories — do not collide with each other for no reason.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Slug {
|
|
11
|
+
private readonly taken;
|
|
12
|
+
/** `fetchOrders` -> `fetch-orders`, `order.state-changed` -> `order-state-changed`. */
|
|
13
|
+
kebab(name: string): string;
|
|
14
|
+
/** The kebab of `name`, suffixed `-2`, `-3`, … if this instance has already handed it out. */
|
|
15
|
+
unique(name: string): string;
|
|
16
|
+
}
|
package/src/spec/Slug.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Slug = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* URL slugs. One pre-rendered HTML file per URL means the slug IS the filename, so it has to be
|
|
6
|
+
* stable, lower-case and free of anything a filesystem or a static host treats specially.
|
|
7
|
+
*
|
|
8
|
+
* Uniqueness is enforced rather than hoped for: two operations that kebab to the same string would
|
|
9
|
+
* otherwise silently overwrite each other's page, and the missing one looks exactly like an
|
|
10
|
+
* operation that was never documented. Uniqueness is per INSTANCE, so operations and schemas —
|
|
11
|
+
* which live in different directories — do not collide with each other for no reason.
|
|
12
|
+
*/
|
|
13
|
+
class Slug {
|
|
14
|
+
taken = new Set();
|
|
15
|
+
/** `fetchOrders` -> `fetch-orders`, `order.state-changed` -> `order-state-changed`. */
|
|
16
|
+
kebab(name) {
|
|
17
|
+
const spaced = name
|
|
18
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
19
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2');
|
|
20
|
+
const cleaned = spaced
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
23
|
+
.replace(/^-+|-+$/g, '');
|
|
24
|
+
return cleaned === '' ? 'page' : cleaned;
|
|
25
|
+
}
|
|
26
|
+
/** The kebab of `name`, suffixed `-2`, `-3`, … if this instance has already handed it out. */
|
|
27
|
+
unique(name) {
|
|
28
|
+
const base = this.kebab(name);
|
|
29
|
+
if (!this.taken.has(base)) {
|
|
30
|
+
this.taken.add(base);
|
|
31
|
+
return base;
|
|
32
|
+
}
|
|
33
|
+
let counter = 2;
|
|
34
|
+
while (this.taken.has(`${base}-${counter}`)) {
|
|
35
|
+
counter++;
|
|
36
|
+
}
|
|
37
|
+
const chosen = `${base}-${counter}`;
|
|
38
|
+
this.taken.add(chosen);
|
|
39
|
+
return chosen;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.Slug = Slug;
|
|
43
|
+
//# sourceMappingURL=Slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Slug.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/spec/Slug.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,MAAa,IAAI;IACI,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAE3C,uFAAuF;IACvF,KAAK,CAAC,IAAY;QACd,MAAM,MAAM,GAAG,IAAI;aACd,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;aACtC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,MAAM;aACjB,WAAW,EAAE;aACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;aAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC7B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7C,CAAC;IAED,8FAA8F;IAC9F,MAAM,CAAC,IAAY;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACrB,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,EAAE,CAAC;YAC1C,OAAO,EAAE,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AA9BD,oBA8BC","sourcesContent":["/**\n * URL slugs. One pre-rendered HTML file per URL means the slug IS the filename, so it has to be\n * stable, lower-case and free of anything a filesystem or a static host treats specially.\n *\n * Uniqueness is enforced rather than hoped for: two operations that kebab to the same string would\n * otherwise silently overwrite each other's page, and the missing one looks exactly like an\n * operation that was never documented. Uniqueness is per INSTANCE, so operations and schemas —\n * which live in different directories — do not collide with each other for no reason.\n */\nexport class Slug {\n private readonly taken = new Set<string>();\n\n /** `fetchOrders` -> `fetch-orders`, `order.state-changed` -> `order-state-changed`. */\n kebab(name: string): string {\n const spaced = name\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2');\n const cleaned = spaced\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return cleaned === '' ? 'page' : cleaned;\n }\n\n /** The kebab of `name`, suffixed `-2`, `-3`, … if this instance has already handed it out. */\n unique(name: string): string {\n const base = this.kebab(name);\n if (!this.taken.has(base)) {\n this.taken.add(base);\n return base;\n }\n let counter = 2;\n while (this.taken.has(`${base}-${counter}`)) {\n counter++;\n }\n const chosen = `${base}-${counter}`;\n this.taken.add(chosen);\n return chosen;\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { JsonNode } from './JsonNode';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the OpenAPI document off disk.
|
|
4
|
+
*
|
|
5
|
+
* It accepts 3.0 and 3.1 alike, and beyond the version it validates NOTHING: this package renders
|
|
6
|
+
* any CONFORMING document, and a renderer that re-validates is a second opinion about the spec that
|
|
7
|
+
* some other tool's document will eventually fail for no reason a reader can act on.
|
|
8
|
+
*
|
|
9
|
+
* ## Why SWAGGER 2.0 is refused rather than attempted
|
|
10
|
+
*
|
|
11
|
+
* 2.0 is a different document: schemas live in `definitions`, not `components.schemas`, and there is
|
|
12
|
+
* no `webhooks` block. Reading one with this reader finds none of those keys and succeeds — writing
|
|
13
|
+
* a site with every operation's fields missing and no schema pages, which is indistinguishable from
|
|
14
|
+
* a contract nobody documented. So the version is checked for `3.`, and a 2.0 document is refused
|
|
15
|
+
* with the converter named. A wrong site is worse than no site, because only one of the two sends
|
|
16
|
+
* somebody looking for the bug.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SpecLoader {
|
|
19
|
+
load(file: string): JsonNode;
|
|
20
|
+
/**
|
|
21
|
+
* A Swagger 2.0 document is named in the message, because "declares no openapi version" would
|
|
22
|
+
* send somebody looking for a missing key in a file that is simply a different format.
|
|
23
|
+
*/
|
|
24
|
+
private wrongDocumentMessage;
|
|
25
|
+
private parse;
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpecLoader = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("node:fs"));
|
|
6
|
+
const DocsSiteError_1 = require("../DocsSiteError");
|
|
7
|
+
const JsonNode_1 = require("./JsonNode");
|
|
8
|
+
/**
|
|
9
|
+
* Reads the OpenAPI document off disk.
|
|
10
|
+
*
|
|
11
|
+
* It accepts 3.0 and 3.1 alike, and beyond the version it validates NOTHING: this package renders
|
|
12
|
+
* any CONFORMING document, and a renderer that re-validates is a second opinion about the spec that
|
|
13
|
+
* some other tool's document will eventually fail for no reason a reader can act on.
|
|
14
|
+
*
|
|
15
|
+
* ## Why SWAGGER 2.0 is refused rather than attempted
|
|
16
|
+
*
|
|
17
|
+
* 2.0 is a different document: schemas live in `definitions`, not `components.schemas`, and there is
|
|
18
|
+
* no `webhooks` block. Reading one with this reader finds none of those keys and succeeds — writing
|
|
19
|
+
* a site with every operation's fields missing and no schema pages, which is indistinguishable from
|
|
20
|
+
* a contract nobody documented. So the version is checked for `3.`, and a 2.0 document is refused
|
|
21
|
+
* with the converter named. A wrong site is worse than no site, because only one of the two sends
|
|
22
|
+
* somebody looking for the bug.
|
|
23
|
+
*/
|
|
24
|
+
class SpecLoader {
|
|
25
|
+
load(file) {
|
|
26
|
+
if (!fs.existsSync(file)) {
|
|
27
|
+
throw new DocsSiteError_1.DocsSiteError('no OpenAPI document there', file, 'Point --spec at a generated OpenAPI 3.0 or 3.1 JSON document.');
|
|
28
|
+
}
|
|
29
|
+
const root = new JsonNode_1.JsonNode(this.parse(file, fs.readFileSync(file, 'utf8')));
|
|
30
|
+
const version = root.text('openapi');
|
|
31
|
+
if (version === undefined) {
|
|
32
|
+
throw new DocsSiteError_1.DocsSiteError(this.wrongDocumentMessage(root), file, 'Point --spec at the OpenAPI document itself — for a webpieces app, one of the files wp-openapi wrote.');
|
|
33
|
+
}
|
|
34
|
+
if (!version.startsWith('3.')) {
|
|
35
|
+
throw new DocsSiteError_1.DocsSiteError(`this renders OpenAPI 3.0 and 3.1, and that document declares "${version}"`, file, 'Convert the document to 3.0 or 3.1 first — 3.x keeps its schemas under components.schemas, which is where this reads them.');
|
|
36
|
+
}
|
|
37
|
+
return root;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A Swagger 2.0 document is named in the message, because "declares no openapi version" would
|
|
41
|
+
* send somebody looking for a missing key in a file that is simply a different format.
|
|
42
|
+
*/
|
|
43
|
+
wrongDocumentMessage(root) {
|
|
44
|
+
const swagger = root.text('swagger');
|
|
45
|
+
if (swagger !== undefined) {
|
|
46
|
+
return `that is a Swagger ${swagger} document, and this renders OpenAPI 3.0 and 3.1`;
|
|
47
|
+
}
|
|
48
|
+
return 'that JSON file declares no "openapi" version, so it is not an OpenAPI document';
|
|
49
|
+
}
|
|
50
|
+
// webpieces-disable no-any-unknown -- JSON.parse returns a foreign value; JsonNode is the narrowing
|
|
51
|
+
parse(file, source) {
|
|
52
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- re-thrown below as a stated failure
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(source);
|
|
55
|
+
// webpieces-disable no-any-unknown -- the catch binding, which TypeScript types for us
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
//const error = toError(err);
|
|
59
|
+
throw new DocsSiteError_1.DocsSiteError(`the OpenAPI document is not valid JSON: ${err instanceof Error ? err.message : String(err)}`, file, 'Regenerate the document, or fix the JSON syntax, and re-run.');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.SpecLoader = SpecLoader;
|
|
64
|
+
//# sourceMappingURL=SpecLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpecLoader.js","sourceRoot":"","sources":["../../../../../../packages/docs/docs-site/src/spec/SpecLoader.ts"],"names":[],"mappings":";;;;AAAA,oDAA8B;AAC9B,oDAAiD;AACjD,yCAAsC;AAEtC;;;;;;;;;;;;;;;GAeG;AACH,MAAa,UAAU;IACnB,IAAI,CAAC,IAAY;QACb,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,6BAAa,CACnB,2BAA2B,EAC3B,IAAI,EACJ,+DAA+D,CAClE,CAAC;QACN,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,6BAAa,CACnB,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAC/B,IAAI,EACJ,uGAAuG,CAC1G,CAAC;QACN,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,6BAAa,CACnB,iEAAiE,OAAO,GAAG,EAC3E,IAAI,EACJ,4HAA4H,CAC/H,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACK,oBAAoB,CAAC,IAAc;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,qBAAqB,OAAO,iDAAiD,CAAC;QACzF,CAAC;QACD,OAAO,gFAAgF,CAAC;IAC5F,CAAC;IAED,oGAAoG;IAC5F,KAAK,CAAC,IAAY,EAAE,MAAc;QACtC,qGAAqG;QACrG,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC1B,uFAAuF;QAC3F,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,6BAA6B;YAC7B,MAAM,IAAI,6BAAa,CACnB,2CAA2C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAC7F,IAAI,EACJ,8DAA8D,CACjE,CAAC;QACN,CAAC;IACL,CAAC;CACJ;AAvDD,gCAuDC","sourcesContent":["import * as fs from 'node:fs';\nimport { DocsSiteError } from '../DocsSiteError';\nimport { JsonNode } from './JsonNode';\n\n/**\n * Reads the OpenAPI document off disk.\n *\n * It accepts 3.0 and 3.1 alike, and beyond the version it validates NOTHING: this package renders\n * any CONFORMING document, and a renderer that re-validates is a second opinion about the spec that\n * some other tool's document will eventually fail for no reason a reader can act on.\n *\n * ## Why SWAGGER 2.0 is refused rather than attempted\n *\n * 2.0 is a different document: schemas live in `definitions`, not `components.schemas`, and there is\n * no `webhooks` block. Reading one with this reader finds none of those keys and succeeds — writing\n * a site with every operation's fields missing and no schema pages, which is indistinguishable from\n * a contract nobody documented. So the version is checked for `3.`, and a 2.0 document is refused\n * with the converter named. A wrong site is worse than no site, because only one of the two sends\n * somebody looking for the bug.\n */\nexport class SpecLoader {\n load(file: string): JsonNode {\n if (!fs.existsSync(file)) {\n throw new DocsSiteError(\n 'no OpenAPI document there',\n file,\n 'Point --spec at a generated OpenAPI 3.0 or 3.1 JSON document.',\n );\n }\n const root = new JsonNode(this.parse(file, fs.readFileSync(file, 'utf8')));\n const version = root.text('openapi');\n if (version === undefined) {\n throw new DocsSiteError(\n this.wrongDocumentMessage(root),\n file,\n 'Point --spec at the OpenAPI document itself — for a webpieces app, one of the files wp-openapi wrote.',\n );\n }\n if (!version.startsWith('3.')) {\n throw new DocsSiteError(\n `this renders OpenAPI 3.0 and 3.1, and that document declares \"${version}\"`,\n file,\n 'Convert the document to 3.0 or 3.1 first — 3.x keeps its schemas under components.schemas, which is where this reads them.',\n );\n }\n return root;\n }\n\n /**\n * A Swagger 2.0 document is named in the message, because \"declares no openapi version\" would\n * send somebody looking for a missing key in a file that is simply a different format.\n */\n private wrongDocumentMessage(root: JsonNode): string {\n const swagger = root.text('swagger');\n if (swagger !== undefined) {\n return `that is a Swagger ${swagger} document, and this renders OpenAPI 3.0 and 3.1`;\n }\n return 'that JSON file declares no \"openapi\" version, so it is not an OpenAPI document';\n }\n\n // webpieces-disable no-any-unknown -- JSON.parse returns a foreign value; JsonNode is the narrowing\n private parse(file: string, source: string): unknown {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- re-thrown below as a stated failure\n try {\n return JSON.parse(source);\n // webpieces-disable no-any-unknown -- the catch binding, which TypeScript types for us\n } catch (err: unknown) {\n //const error = toError(err);\n throw new DocsSiteError(\n `the OpenAPI document is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n file,\n 'Regenerate the document, or fix the JSON syntax, and re-run.',\n );\n }\n }\n}\n"]}
|