@scalar/openapi-to-markdown 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/get-markdown-examples.d.ts +5 -1
- package/dist/get-markdown-examples.d.ts.map +1 -1
- package/dist/get-markdown-examples.js +15 -4
- package/dist/load-document.d.ts.map +1 -1
- package/dist/load-document.js +120 -42
- package/dist/object-kinds.d.ts +14 -0
- package/dist/object-kinds.d.ts.map +1 -0
- package/dist/object-kinds.js +70 -0
- package/dist/render-examples.d.ts +1 -1
- package/dist/render-examples.d.ts.map +1 -1
- package/dist/render-examples.js +28 -11
- package/dist/render-operation-details.d.ts +2 -2
- package/dist/render-operation-details.d.ts.map +1 -1
- package/dist/render-operation-details.js +5 -5
- package/dist/render-operation.d.ts.map +1 -1
- package/dist/render-operation.js +6 -6
- package/dist/restore-boolean-schemas.d.ts +8 -1
- package/dist/restore-boolean-schemas.d.ts.map +1 -1
- package/dist/restore-boolean-schemas.js +42 -38
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @scalar/openapi-to-markdown
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#10192](https://github.com/scalar/scalar/pull/10192): Mock-server XML response bytes now use the shared schema-aware serializer instead of `json2xml`, including attributes, namespaces, and root naming. Existing XML response snapshots may need updating. Supplied serialized XML remains unchanged.
|
|
8
|
+
|
|
9
|
+
Generate XML examples from schema metadata, preserving attributes, namespaces, array wrappers, repeated elements, and OpenAPI 3.2 text and CDATA nodes. Use the same XML serialization for request bodies, code snippets, response examples, mock responses, and Markdown documentation. Preserve serialized media examples and escape schema string examples as element text.
|
|
10
|
+
|
|
11
|
+
Explain XML generation failures in response example panels, including the serialized-example escape hatch for large payloads. Expose XML generation failures in mock response headers with `X-Scalar-XML-Error`, containing the first error diagnostic code. Report diagnostics to other consumers through a callback or the developer console, and format element-only descendants within mixed content without changing text values.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#10356](https://github.com/scalar/scalar/pull/10356): Load densely cross-linked API descriptions with far less memory. References are now linked after coercion, so TypeBox no longer copies the reference graph each time it checks a union or array. Stripe's API description loads in about 2 seconds with a peak of about 190 MB, down from about 17 seconds and 1.4 GB, so it now loads and renders with a 768 MB heap limit.
|
|
16
|
+
|
|
17
|
+
Coercion now checks each reference target in its own position, not through every reference that points to it. Before, a target that failed a strict schema check (for example, a schema with `oneOf` references) could turn the reference into an empty schema and drop sibling `x-` extensions. Those references and extensions are now kept, so affected response schemas render in full.
|
|
18
|
+
|
|
19
|
+
References to targets that coercion drops, such as a root-level `definitions` block, now link to a copy cast as the schema, parameter, response or other object the reference stands in for, and their anchors resolve against the resource that contains them. A `$ref: '#'` inside a schema with an `$id` now links to that schema.
|
|
20
|
+
|
|
21
|
+
Coerce reference targets stored in extension data and schema siblings on fallback references before rendering, avoiding crashes from malformed fields while preserving recursive links.
|
|
22
|
+
|
|
23
|
+
- [#10192](https://github.com/scalar/scalar/pull/10192): Apply edited XML bodies instead of their original serialized or data examples, render schema-free XML examples in Markdown, and share reference decoding and response provenance selection.
|
|
24
|
+
|
|
3
25
|
## 1.2.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
|
@@ -17,6 +17,10 @@ type MarkdownExample = {
|
|
|
17
17
|
serializedValue: string;
|
|
18
18
|
} | {
|
|
19
19
|
omitted: true;
|
|
20
|
+
} | {
|
|
21
|
+
dataValue: unknown;
|
|
22
|
+
} | {
|
|
23
|
+
error: string;
|
|
20
24
|
});
|
|
21
25
|
/**
|
|
22
26
|
* Estimate how many values an example generated from this schema contains, stopping just past the limit.
|
|
@@ -24,6 +28,6 @@ type MarkdownExample = {
|
|
|
24
28
|
*/
|
|
25
29
|
export declare const countGeneratedExampleValues: (root: unknown, limit?: number) => number;
|
|
26
30
|
/** Preserve supplied values; generate a fallback only when examples are not supplied. */
|
|
27
|
-
export declare const getMarkdownExamples: (source: ExampleSource, mediaType: string, mode?: "read" | "write", openapiVersion?: string) => MarkdownExample[];
|
|
31
|
+
export declare const getMarkdownExamples: (source: ExampleSource, mediaType: string, mode?: "read" | "write", openapiVersion?: string, schemaOpenapiVersion?: string) => MarkdownExample[];
|
|
28
32
|
export {};
|
|
29
33
|
//# sourceMappingURL=get-markdown-examples.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"AAMA,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,gFAAgF;AAChF,KAAK,eAAe,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,CACA;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CACpB,CAAA;AAYD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,OAAO,EAAE,cAAoC,KAAG,MA2CjG,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,aAAa,EACrB,WAAW,MAAM,EACjB,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,EAExB,6BAAqC,KACpC,eAAe,EAoCjB,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
|
|
1
2
|
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
3
|
import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
3
|
-
import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
|
|
4
|
+
import { getExampleFromSchema, getXmlBodyExample } from '@scalar/workspace-store/request-example';
|
|
4
5
|
/** Mirrors the depth at which `getExampleFromSchema` stops following nested schemas. */
|
|
5
6
|
const EXAMPLE_DEPTH = 10;
|
|
6
7
|
/**
|
|
@@ -68,7 +69,9 @@ export const countGeneratedExampleValues = (root, limit = MAX_GENERATED_EXAMPLE_
|
|
|
68
69
|
return count(root, 0);
|
|
69
70
|
};
|
|
70
71
|
/** Preserve supplied values; generate a fallback only when examples are not supplied. */
|
|
71
|
-
export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3.2.0'
|
|
72
|
+
export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3.2.0',
|
|
73
|
+
// Schema metadata can be upgraded while example fields still follow the original version.
|
|
74
|
+
schemaOpenapiVersion = openapiVersion) => {
|
|
72
75
|
if (source.example !== undefined)
|
|
73
76
|
return [{ value: source.example }];
|
|
74
77
|
if (source.examples && Object.keys(source.examples).length) {
|
|
@@ -88,7 +91,7 @@ export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3
|
|
|
88
91
|
if (typeof example.externalValue === 'string')
|
|
89
92
|
return [{ ...metadata, externalValue: example.externalValue }];
|
|
90
93
|
if (/^3\.2\./.test(openapiVersion) && example.dataValue !== undefined)
|
|
91
|
-
return [{ ...metadata,
|
|
94
|
+
return [{ ...metadata, dataValue: example.dataValue }];
|
|
92
95
|
return [];
|
|
93
96
|
});
|
|
94
97
|
}
|
|
@@ -97,8 +100,16 @@ export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3
|
|
|
97
100
|
return [];
|
|
98
101
|
if (countGeneratedExampleValues(source.schema) > MAX_GENERATED_EXAMPLE_VALUES)
|
|
99
102
|
return [{ omitted: true }];
|
|
103
|
+
if (isXmlMediaType(mediaType)) {
|
|
104
|
+
const result = getXmlBodyExample(source.schema, undefined, {
|
|
105
|
+
mode,
|
|
106
|
+
openapiVersion: schemaOpenapiVersion,
|
|
107
|
+
});
|
|
108
|
+
return [
|
|
109
|
+
result.xml === undefined ? { error: 'Unable to generate an XML example.' } : { serializedValue: result.xml },
|
|
110
|
+
];
|
|
111
|
+
}
|
|
100
112
|
const value = getExampleFromSchema(getResolvedRef(source.schema, mergeSiblingReferences), {
|
|
101
|
-
xml: mediaType.includes('xml'),
|
|
102
113
|
mode,
|
|
103
114
|
});
|
|
104
115
|
return value === undefined ? [] : [{ value }];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-document.d.ts","sourceRoot":"","sources":["../src/load-document.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"load-document.d.ts","sourceRoot":"","sources":["../src/load-document.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,8DAA8D,CAAA;AA6LrE,8EAA8E;AAC9E,eAAO,MAAM,YAAY,GACvB,OAAO,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,KACxD,OAAO,CAAC,eAAe,CAgGzB,CAAA"}
|
package/dist/load-document.js
CHANGED
|
@@ -9,21 +9,99 @@ import { getRaw } from '@scalar/json-magic/magic-proxy';
|
|
|
9
9
|
import { upgrade } from '@scalar/openapi-upgrader';
|
|
10
10
|
import { deepClone } from '@scalar/workspace-store/helpers/deep-clone';
|
|
11
11
|
import { coerceValue } from '@scalar/workspace-store/schemas/typebox-coerce';
|
|
12
|
-
import { OpenAPIDocumentSchema, } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
13
|
-
import {
|
|
12
|
+
import { OpenAPIDocumentSchema, SchemaObjectSchema, } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
13
|
+
import { getChildKind, referenceTargetSchemas } from './object-kinds.js';
|
|
14
|
+
import { getChildPosition, restoreBooleanSchemas } from './restore-boolean-schemas.js';
|
|
14
15
|
/**
|
|
15
16
|
* Link references in a private, bundled document without proxies or expanded copies.
|
|
16
17
|
* JSON Magic owns `$id` and anchor indexing, which keeps local-reference behavior
|
|
17
18
|
* aligned with the external-reference bundler without retaining a second resolver.
|
|
18
19
|
* Non-enumerable links preserve serialization and share recursive schema targets.
|
|
19
|
-
*
|
|
20
|
+
* With `detectOnly`, nothing is linked and the pass only looks for external references.
|
|
20
21
|
* Returns whether external references remain and require bundling.
|
|
21
22
|
*/
|
|
22
|
-
const attachRefValues = (document,
|
|
23
|
-
//
|
|
23
|
+
const attachRefValues = (document, schemas = getSchemas(document), { detectOnly = false, fallback } = {}) => {
|
|
24
|
+
// Fallback targets are linked outside the walked tree and can be reached again from it,
|
|
25
|
+
// so one set keeps every object to a single visit.
|
|
24
26
|
const seen = new WeakSet();
|
|
27
|
+
// Large descriptions repeat the same reference strings thousands of times.
|
|
28
|
+
const targets = new Map();
|
|
29
|
+
// Share one cast copy between every reference of the same kind to the same dropped target.
|
|
30
|
+
const castFallbacks = new WeakMap();
|
|
25
31
|
let hasExternalReferences = false;
|
|
26
|
-
const
|
|
32
|
+
const lookUp = (path, ref) => {
|
|
33
|
+
// An empty path is a resource root, unless the fragment is the pointer to the empty key.
|
|
34
|
+
const segments = path === '' && !ref.includes('#/') ? [] : parseJsonPointerSegments(`/${path}`);
|
|
35
|
+
const linked = getValueByPath(document, segments);
|
|
36
|
+
let position = 'document';
|
|
37
|
+
let kind = 'document';
|
|
38
|
+
let parent = document;
|
|
39
|
+
for (const key of segments) {
|
|
40
|
+
kind = kind && getChildKind(kind, key);
|
|
41
|
+
position = Array.isArray(parent) ? position : position && getChildPosition(position, key);
|
|
42
|
+
// Arbitrary extension and example data has not passed an OpenAPI object schema.
|
|
43
|
+
if (position === 'document' && kind === undefined)
|
|
44
|
+
position = undefined;
|
|
45
|
+
parent = getValueByPath(parent, [key]).value;
|
|
46
|
+
}
|
|
47
|
+
const schema = getTargetSchema(position, kind);
|
|
48
|
+
if ((linked.value !== undefined && schema !== undefined) || fallback === undefined) {
|
|
49
|
+
return { ...linked, linked: true, schema };
|
|
50
|
+
}
|
|
51
|
+
// The context is the base of the resource that contains the fallback target.
|
|
52
|
+
return { ...getValueByPath(fallback.source, segments), linked: false };
|
|
53
|
+
};
|
|
54
|
+
const resolve = (ref, base) => {
|
|
55
|
+
const key = `${base}\u0000${ref}`;
|
|
56
|
+
if (targets.has(key)) {
|
|
57
|
+
return targets.get(key);
|
|
58
|
+
}
|
|
59
|
+
// JSON Magic has no entry for an empty fragment, which names the root of the current resource.
|
|
60
|
+
const path = ref === '#' ? (schemas.get(base) ?? '') : convertToLocalRef(ref, base, schemas);
|
|
61
|
+
const resolved = path === undefined ? undefined : lookUp(path, ref);
|
|
62
|
+
targets.set(key, resolved);
|
|
63
|
+
return resolved;
|
|
64
|
+
};
|
|
65
|
+
// Cast dropped targets like the objects they stand in for, so the renderer never sees uncoerced shapes.
|
|
66
|
+
const castFallback = (value, schema, position) => {
|
|
67
|
+
// OpenAPI 3.1 keeps boolean schemas, and a nested reference is linked once it is walked.
|
|
68
|
+
if (value === undefined || (fallback?.booleanSchemas && position === 'schema' && typeof value === 'boolean')) {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
if (!isObject(value)) {
|
|
72
|
+
return coerceValue(schema, value);
|
|
73
|
+
}
|
|
74
|
+
const casts = castFallbacks.get(value) ?? new Map();
|
|
75
|
+
castFallbacks.set(value, casts);
|
|
76
|
+
if (!casts.has(schema)) {
|
|
77
|
+
// Cast schema siblings without losing the reference. Other Reference Objects only
|
|
78
|
+
// support summary and description; casting them as their targets would add defaults.
|
|
79
|
+
// Path Item Objects declare their own $ref field, so their siblings are cast normally.
|
|
80
|
+
const reference = typeof value.$ref === 'string' ? value.$ref : undefined;
|
|
81
|
+
const cast = reference === undefined || schema === referenceTargetSchemas.pathItem
|
|
82
|
+
? coerceValue(schema, value)
|
|
83
|
+
: position === 'schema'
|
|
84
|
+
? { ...coerceValue(SchemaObjectSchema, value), $ref: reference }
|
|
85
|
+
: {
|
|
86
|
+
$ref: reference,
|
|
87
|
+
...(typeof value.summary === 'string' ? { summary: value.summary } : {}),
|
|
88
|
+
...(typeof value.description === 'string' ? { description: value.description } : {}),
|
|
89
|
+
};
|
|
90
|
+
if (fallback?.booleanSchemas) {
|
|
91
|
+
restoreBooleanSchemas(value, cast, position);
|
|
92
|
+
}
|
|
93
|
+
casts.set(schema, cast);
|
|
94
|
+
}
|
|
95
|
+
return casts.get(schema);
|
|
96
|
+
};
|
|
97
|
+
// Schemas are recognized by their position, and every other object by the field that holds it.
|
|
98
|
+
const getTargetSchema = (position, kind) => {
|
|
99
|
+
if (position === 'schema') {
|
|
100
|
+
return SchemaObjectSchema;
|
|
101
|
+
}
|
|
102
|
+
return typeof kind === 'string' ? referenceTargetSchemas[kind] : undefined;
|
|
103
|
+
};
|
|
104
|
+
const visit = (node, context, position, kind) => {
|
|
27
105
|
if (node === null || typeof node !== 'object' || seen.has(node)) {
|
|
28
106
|
return;
|
|
29
107
|
}
|
|
@@ -32,45 +110,40 @@ const attachRefValues = (document, enumerable = false, schemas = getSchemas(docu
|
|
|
32
110
|
const base = getId(node) ?? context;
|
|
33
111
|
if (isObject(node) && typeof node.$ref === 'string') {
|
|
34
112
|
const ref = node.$ref;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (path === undefined && ref.split('#')[0]) {
|
|
40
|
-
hasExternalReferences = true;
|
|
41
|
-
}
|
|
42
|
-
if (enumerable) {
|
|
43
|
-
const followed = new WeakSet();
|
|
44
|
-
let resolved = target;
|
|
45
|
-
// TypeBox needs the final target, so collapse a pre-existing reference chain safely.
|
|
46
|
-
while (isObject(resolved) && '$ref-value' in resolved && !followed.has(resolved)) {
|
|
47
|
-
followed.add(resolved);
|
|
48
|
-
resolved = resolved['$ref-value'];
|
|
113
|
+
if (detectOnly) {
|
|
114
|
+
// JSON Magic only needs to bundle references outside this document's resource index.
|
|
115
|
+
if (ref.split('#')[0] && convertToLocalRef(ref, base, schemas) === undefined) {
|
|
116
|
+
hasExternalReferences = true;
|
|
49
117
|
}
|
|
50
|
-
|
|
118
|
+
}
|
|
119
|
+
else if (getTargetSchema(position, kind)) {
|
|
120
|
+
const resolved = resolve(ref, base);
|
|
121
|
+
const expectedSchema = getTargetSchema(position, kind);
|
|
122
|
+
const schema = resolved?.schema === expectedSchema ? undefined : expectedSchema;
|
|
123
|
+
// Point to the target instead of copying it into every reference.
|
|
124
|
+
const target = schema ? castFallback(resolved?.value, schema, position ?? 'document') : resolved?.value;
|
|
125
|
+
if (target !== undefined) {
|
|
51
126
|
Object.defineProperty(node, '$ref-value', {
|
|
52
|
-
value:
|
|
53
|
-
enumerable,
|
|
127
|
+
value: target,
|
|
128
|
+
enumerable: false,
|
|
54
129
|
configurable: true,
|
|
55
130
|
writable: true,
|
|
56
131
|
});
|
|
57
132
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
enumerable,
|
|
63
|
-
configurable: true,
|
|
64
|
-
writable: true,
|
|
65
|
-
});
|
|
133
|
+
// Fallback targets live outside the walked tree, so link their own references too.
|
|
134
|
+
if (resolved && (!resolved.linked || schema)) {
|
|
135
|
+
visit(target, resolved.context, position, kind);
|
|
136
|
+
}
|
|
66
137
|
}
|
|
67
138
|
}
|
|
68
139
|
// Preserve the nearest resource base while walking into child schemas.
|
|
69
|
-
for (const child of Object.
|
|
70
|
-
|
|
140
|
+
for (const [key, child] of Object.entries(node)) {
|
|
141
|
+
const childKind = kind && getChildKind(kind, key);
|
|
142
|
+
const childPosition = Array.isArray(node) ? position : position && getChildPosition(position, key);
|
|
143
|
+
visit(child, base, childPosition === 'document' && childKind === undefined ? undefined : childPosition, childKind);
|
|
71
144
|
}
|
|
72
145
|
};
|
|
73
|
-
visit(document);
|
|
146
|
+
visit(document, getId(document) ?? '', 'document', 'document');
|
|
74
147
|
return hasExternalReferences;
|
|
75
148
|
};
|
|
76
149
|
/** Coerce one plain document and keep references linked to shared targets. */
|
|
@@ -108,7 +181,7 @@ export const loadDocument = async (input) => {
|
|
|
108
181
|
const declaredOpenapiVersion = typeof raw.openapi === 'string' ? raw.openapi : '2.0';
|
|
109
182
|
const { document: upgraded } = upgrade(raw, '3.2', { onIncompatible: 'collect' });
|
|
110
183
|
const upgradedSchemas = getSchemas(upgraded);
|
|
111
|
-
const hasExternalReferences = attachRefValues(upgraded,
|
|
184
|
+
const hasExternalReferences = attachRefValues(upgraded, upgradedSchemas, { detectOnly: true });
|
|
112
185
|
let document = upgraded;
|
|
113
186
|
let schemas = upgradedSchemas;
|
|
114
187
|
// Resolve external targets only when the initial local-reference pass finds them.
|
|
@@ -130,17 +203,21 @@ export const loadDocument = async (input) => {
|
|
|
130
203
|
throw new Error(errors.join('\n'));
|
|
131
204
|
}
|
|
132
205
|
schemas = getSchemas(document);
|
|
133
|
-
attachRefValues(document, false, schemas);
|
|
134
206
|
}
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
207
|
+
// Cast the document before linking references. TypeBox clones every value that already
|
|
208
|
+
// matches a union or array schema, and its clone follows all own properties, including
|
|
209
|
+
// non-enumerable `$ref-value` links. On densely linked descriptions, each clone would copy
|
|
210
|
+
// most of the reference graph again, which costs gigabytes of transient memory.
|
|
211
|
+
// Reference branches accept a missing `$ref-value`, and every target is cast in its own
|
|
212
|
+
// position, so one target that fails a strict check can no longer replace the reference.
|
|
138
213
|
const coerced = coerceValue(OpenAPIDocumentSchema, document);
|
|
139
214
|
// Rendering must use the declared version for features added after OpenAPI 3.1.
|
|
140
215
|
coerced['x-original-oas-version'] = declaredOpenapiVersion;
|
|
141
216
|
// Boolean schemas were introduced in OpenAPI 3.1; older descriptions retain their existing coercion.
|
|
142
|
-
|
|
217
|
+
const booleanSchemas = /^3\.[12]\./.test(declaredOpenapiVersion);
|
|
218
|
+
if (booleanSchemas) {
|
|
143
219
|
restoreBooleanSchemas(document, coerced);
|
|
220
|
+
}
|
|
144
221
|
// Keep extension resources that local and bundled references can target.
|
|
145
222
|
for (const [key, value] of Object.entries(document)) {
|
|
146
223
|
if (key.startsWith('x-') && !(key in coerced)) {
|
|
@@ -152,7 +229,8 @@ export const loadDocument = async (input) => {
|
|
|
152
229
|
});
|
|
153
230
|
}
|
|
154
231
|
}
|
|
155
|
-
//
|
|
156
|
-
|
|
232
|
+
// Link references to coerced targets. Targets that coercion dropped fall back to source values,
|
|
233
|
+
// which are cast according to the object kind expected at the reference.
|
|
234
|
+
attachRefValues(coerced, schemas, { fallback: { source: document, booleanSchemas } });
|
|
157
235
|
return coerced;
|
|
158
236
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { coerceValue } from '@scalar/workspace-store/schemas/typebox-coerce';
|
|
2
|
+
/** The OpenAPI objects outside of schemas that the linking pass tells apart. */
|
|
3
|
+
export type ObjectKind = 'document' | 'components' | 'pathItem' | 'operation' | 'callback' | 'response' | 'requestBody' | 'mediaType' | 'encoding' | 'parameter' | 'header' | 'example' | 'link' | 'securityScheme';
|
|
4
|
+
/** A value is either one object of a kind, or a map or array whose values all have that kind. */
|
|
5
|
+
export type KindPosition = ObjectKind | {
|
|
6
|
+
values: ObjectKind;
|
|
7
|
+
};
|
|
8
|
+
type TargetSchema = Parameters<typeof coerceValue>[0];
|
|
9
|
+
/** Returns the kind of the value stored under `key`, or `undefined` outside the known structure. */
|
|
10
|
+
export declare const getChildKind: (position: KindPosition, key: string) => KindPosition | undefined;
|
|
11
|
+
/** Strict schemas for the objects that a Reference Object can stand in for. */
|
|
12
|
+
export declare const referenceTargetSchemas: Partial<Record<ObjectKind, TargetSchema>>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=object-kinds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-kinds.d.ts","sourceRoot":"","sources":["../src/object-kinds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gDAAgD,CAAA;AAejF,gFAAgF;AAChF,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,YAAY,GACZ,UAAU,GACV,WAAW,GACX,UAAU,GACV,UAAU,GACV,aAAa,GACb,WAAW,GACX,UAAU,GACV,WAAW,GACX,QAAQ,GACR,SAAS,GACT,MAAM,GACN,gBAAgB,CAAA;AAEpB,iGAAiG;AACjG,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAA;AAE9D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAmDrD,oGAAoG;AACpG,eAAO,MAAM,YAAY,GAAI,UAAU,YAAY,EAAE,KAAK,MAAM,KAAG,YAAY,GAAG,SAMjF,CAAA;AAED,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAY5E,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CallbackObjectSchema, ExampleObjectSchema, HeaderObjectSchema, LinkObjectSchema, MediaTypeObjectSchema, OperationObjectSchema, ParameterObjectSchema, PathItemObjectSchema, RequestBodyObjectSchema, ResponseObjectSchema, SecuritySchemeObjectSchema, } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
2
|
+
const methods = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace', 'connect', 'query'];
|
|
3
|
+
/**
|
|
4
|
+
* Mirrors the OpenAPI 3.2 structure of the strict workspace schemas, so a reference knows which
|
|
5
|
+
* object it stands in for. A `*` key applies to every field, such as the expressions of a callback.
|
|
6
|
+
*/
|
|
7
|
+
const childKinds = {
|
|
8
|
+
document: {
|
|
9
|
+
paths: { values: 'pathItem' },
|
|
10
|
+
webhooks: { values: 'pathItem' },
|
|
11
|
+
components: 'components',
|
|
12
|
+
},
|
|
13
|
+
components: {
|
|
14
|
+
responses: { values: 'response' },
|
|
15
|
+
parameters: { values: 'parameter' },
|
|
16
|
+
examples: { values: 'example' },
|
|
17
|
+
requestBodies: { values: 'requestBody' },
|
|
18
|
+
headers: { values: 'header' },
|
|
19
|
+
securitySchemes: { values: 'securityScheme' },
|
|
20
|
+
links: { values: 'link' },
|
|
21
|
+
callbacks: { values: 'callback' },
|
|
22
|
+
pathItems: { values: 'pathItem' },
|
|
23
|
+
mediaTypes: { values: 'mediaType' },
|
|
24
|
+
},
|
|
25
|
+
pathItem: {
|
|
26
|
+
...Object.fromEntries(methods.map((method) => [method, 'operation'])),
|
|
27
|
+
additionalOperations: { values: 'operation' },
|
|
28
|
+
parameters: { values: 'parameter' },
|
|
29
|
+
},
|
|
30
|
+
operation: {
|
|
31
|
+
parameters: { values: 'parameter' },
|
|
32
|
+
requestBody: 'requestBody',
|
|
33
|
+
responses: { values: 'response' },
|
|
34
|
+
callbacks: { values: 'callback' },
|
|
35
|
+
},
|
|
36
|
+
callback: { '*': 'pathItem' },
|
|
37
|
+
response: { headers: { values: 'header' }, content: { values: 'mediaType' }, links: { values: 'link' } },
|
|
38
|
+
requestBody: { content: { values: 'mediaType' } },
|
|
39
|
+
mediaType: {
|
|
40
|
+
examples: { values: 'example' },
|
|
41
|
+
encoding: { values: 'encoding' },
|
|
42
|
+
prefixEncoding: { values: 'encoding' },
|
|
43
|
+
itemEncoding: 'encoding',
|
|
44
|
+
},
|
|
45
|
+
encoding: { headers: { values: 'header' } },
|
|
46
|
+
parameter: { examples: { values: 'example' }, content: { values: 'mediaType' } },
|
|
47
|
+
header: { examples: { values: 'example' }, content: { values: 'mediaType' } },
|
|
48
|
+
};
|
|
49
|
+
/** Returns the kind of the value stored under `key`, or `undefined` outside the known structure. */
|
|
50
|
+
export const getChildKind = (position, key) => {
|
|
51
|
+
if (typeof position !== 'string') {
|
|
52
|
+
return position.values;
|
|
53
|
+
}
|
|
54
|
+
const children = childKinds[position];
|
|
55
|
+
return children?.[key] ?? children?.['*'];
|
|
56
|
+
};
|
|
57
|
+
/** Strict schemas for the objects that a Reference Object can stand in for. */
|
|
58
|
+
export const referenceTargetSchemas = {
|
|
59
|
+
pathItem: PathItemObjectSchema,
|
|
60
|
+
operation: OperationObjectSchema,
|
|
61
|
+
callback: CallbackObjectSchema,
|
|
62
|
+
response: ResponseObjectSchema,
|
|
63
|
+
requestBody: RequestBodyObjectSchema,
|
|
64
|
+
mediaType: MediaTypeObjectSchema,
|
|
65
|
+
parameter: ParameterObjectSchema,
|
|
66
|
+
header: HeaderObjectSchema,
|
|
67
|
+
example: ExampleObjectSchema,
|
|
68
|
+
link: LinkObjectSchema,
|
|
69
|
+
securityScheme: SecuritySchemeObjectSchema,
|
|
70
|
+
};
|
|
@@ -2,5 +2,5 @@ import type { RootContent } from 'mdast';
|
|
|
2
2
|
import { type ExampleSource } from './get-markdown-examples.js';
|
|
3
3
|
import type { DescriptionParser } from './parse-description.js';
|
|
4
4
|
/** Render supplied examples before considering a schema-generated fallback. */
|
|
5
|
-
export declare const renderExamples: (source: ExampleSource, description: DescriptionParser, mediaType?: string, mode?: "read" | "write", openapiVersion?: string) => Promise<RootContent[]>;
|
|
5
|
+
export declare const renderExamples: (source: ExampleSource, description: DescriptionParser, mediaType?: string, mode?: "read" | "write", openapiVersion?: string, schemaOpenapiVersion?: string) => Promise<RootContent[]>;
|
|
6
6
|
//# sourceMappingURL=render-examples.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-examples.d.ts","sourceRoot":"","sources":["../src/render-examples.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render-examples.d.ts","sourceRoot":"","sources":["../src/render-examples.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAExC,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,yBAAyB,CAAA;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,GACzB,QAAQ,aAAa,EACrB,aAAa,iBAAiB,EAC9B,kBAA8B,EAC9B,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,EAExB,6BAAqC,KACpC,OAAO,CAAC,WAAW,EAAE,CAmDvB,CAAA"}
|
package/dist/render-examples.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
|
|
2
|
+
import { getXmlBodyExample } from '@scalar/workspace-store/request-example';
|
|
2
3
|
import { getMarkdownExamples } from './get-markdown-examples.js';
|
|
3
4
|
import { link, paragraph, strong, text } from './markdown-nodes.js';
|
|
4
5
|
/** Render supplied examples before considering a schema-generated fallback. */
|
|
5
|
-
export const renderExamples = async (source, description, mediaType = 'application/json', mode, openapiVersion = '3.2.0'
|
|
6
|
+
export const renderExamples = async (source, description, mediaType = 'application/json', mode, openapiVersion = '3.2.0',
|
|
7
|
+
// Schema metadata can be upgraded while example fields still follow the original version.
|
|
8
|
+
schemaOpenapiVersion = openapiVersion) => {
|
|
6
9
|
const nodes = [];
|
|
7
|
-
for (const example of getMarkdownExamples(source, mediaType, mode, openapiVersion)) {
|
|
10
|
+
for (const example of getMarkdownExamples(source, mediaType, mode, openapiVersion, schemaOpenapiVersion)) {
|
|
8
11
|
nodes.push(paragraph(strong(text(example.name ? `Example: ${example.name}` : 'Example:'))));
|
|
9
12
|
if ('omitted' in example) {
|
|
10
13
|
nodes.push(paragraph(text('[Generated example omitted because it is too large]')));
|
|
@@ -13,6 +16,10 @@ export const renderExamples = async (source, description, mediaType = 'applicati
|
|
|
13
16
|
if (example.summary)
|
|
14
17
|
nodes.push(paragraph(text(example.summary)));
|
|
15
18
|
nodes.push(...(await description(example.description)));
|
|
19
|
+
if ('error' in example) {
|
|
20
|
+
nodes.push(paragraph(text(example.error)));
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
16
23
|
if ('externalValue' in example) {
|
|
17
24
|
nodes.push(paragraph(strong(text('External value:')), text(' '), link(example.externalValue, example.externalValue)));
|
|
18
25
|
continue;
|
|
@@ -20,21 +27,31 @@ export const renderExamples = async (source, description, mediaType = 'applicati
|
|
|
20
27
|
if ('serializedValue' in example) {
|
|
21
28
|
nodes.push({
|
|
22
29
|
type: 'code',
|
|
23
|
-
lang: mediaType
|
|
30
|
+
lang: isXmlMediaType(mediaType) ? 'xml' : mediaType.includes('json') ? 'json' : 'text',
|
|
24
31
|
value: example.serializedValue,
|
|
25
32
|
});
|
|
26
33
|
continue;
|
|
27
34
|
}
|
|
28
|
-
const xml = mediaType
|
|
35
|
+
const xml = isXmlMediaType(mediaType);
|
|
36
|
+
const value = 'dataValue' in example ? example.dataValue : example.value;
|
|
37
|
+
if (xml && !source.schema && 'value' in example && (value === null || typeof value !== 'object')) {
|
|
38
|
+
nodes.push({ type: 'code', lang: 'xml', value: String(value) });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const result = xml
|
|
42
|
+
? getXmlBodyExample(source.schema, example, {
|
|
43
|
+
mode,
|
|
44
|
+
openapiVersion: schemaOpenapiVersion,
|
|
45
|
+
})
|
|
46
|
+
: undefined;
|
|
47
|
+
if (xml && result?.xml === undefined) {
|
|
48
|
+
nodes.push(paragraph(text('Unable to generate an XML example.')));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
29
51
|
nodes.push({
|
|
30
52
|
type: 'code',
|
|
31
53
|
lang: xml ? 'xml' : 'json',
|
|
32
|
-
|
|
33
|
-
value: xml
|
|
34
|
-
? example.value !== null && typeof example.value === 'object'
|
|
35
|
-
? json2xml(example.value)
|
|
36
|
-
: String(example.value)
|
|
37
|
-
: (JSON.stringify(example.value, null, 2) ?? ''),
|
|
54
|
+
value: result?.xml ?? JSON.stringify(value, null, 2) ?? '',
|
|
38
55
|
});
|
|
39
56
|
}
|
|
40
57
|
return nodes;
|
|
@@ -3,9 +3,9 @@ import type { RootContent } from 'mdast';
|
|
|
3
3
|
import type { DescriptionParser } from './parse-description.js';
|
|
4
4
|
import type { SchemaRenderer } from './render-schema.js';
|
|
5
5
|
/** Render response or multipart headers, whose names come from their containing map. */
|
|
6
|
-
export declare const renderHeaders: (headers: ResponseObject["headers"], description: DescriptionParser, schemas: SchemaRenderer, openapiVersion: string) => Promise<RootContent[]>;
|
|
6
|
+
export declare const renderHeaders: (headers: ResponseObject["headers"], description: DescriptionParser, schemas: SchemaRenderer, openapiVersion: string, schemaOpenapiVersion?: string) => Promise<RootContent[]>;
|
|
7
7
|
/** Preserve explicit encoding settings, including false flags and part headers. */
|
|
8
|
-
export declare const renderEncoding: (encoding: Record<string, EncodingObject> | undefined, mediaType: string, description: DescriptionParser, schemas: SchemaRenderer, openapiVersion: string) => Promise<RootContent[]>;
|
|
8
|
+
export declare const renderEncoding: (encoding: Record<string, EncodingObject> | undefined, mediaType: string, description: DescriptionParser, schemas: SchemaRenderer, openapiVersion: string, schemaOpenapiVersion?: string) => Promise<RootContent[]>;
|
|
9
9
|
/** Render response link expressions as literal values, without evaluating them. */
|
|
10
10
|
export declare const renderResponseLinks: (links: ResponseObject["links"], description: DescriptionParser) => Promise<RootContent[]>;
|
|
11
11
|
//# sourceMappingURL=render-operation-details.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-operation-details.d.ts","sourceRoot":"","sources":["../src/render-operation-details.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAA;AAClH,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,wFAAwF;AACxF,eAAO,MAAM,aAAa,GACxB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,aAAa,iBAAiB,EAC9B,SAAS,cAAc,EACvB,gBAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"render-operation-details.d.ts","sourceRoot":"","sources":["../src/render-operation-details.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAA;AAClH,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,wFAAwF;AACxF,eAAO,MAAM,aAAa,GACxB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,aAAa,iBAAiB,EAC9B,SAAS,cAAc,EACvB,gBAAgB,MAAM,EACtB,6BAAqC,KACpC,OAAO,CAAC,WAAW,EAAE,CA0CvB,CAAA;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,SAAS,EACpD,WAAW,MAAM,EACjB,aAAa,iBAAiB,EAC9B,SAAS,cAAc,EACvB,gBAAgB,MAAM,EACtB,6BAAqC,KACpC,OAAO,CAAC,WAAW,EAAE,CA6BvB,CAAA;AAID,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,cAAc,CAAC,OAAO,CAAC,EAC9B,aAAa,iBAAiB,KAC7B,OAAO,CAAC,WAAW,EAAE,CA2BvB,CAAA"}
|
|
@@ -2,7 +2,7 @@ import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/
|
|
|
2
2
|
import { inlineCode, item, list, paragraph, strong, text } from './markdown-nodes.js';
|
|
3
3
|
import { renderExamples } from './render-examples.js';
|
|
4
4
|
/** Render response or multipart headers, whose names come from their containing map. */
|
|
5
|
-
export const renderHeaders = async (headers, description, schemas, openapiVersion) => {
|
|
5
|
+
export const renderHeaders = async (headers, description, schemas, openapiVersion, schemaOpenapiVersion = openapiVersion) => {
|
|
6
6
|
const entries = [];
|
|
7
7
|
for (const [name, reference] of Object.entries(headers ?? {})) {
|
|
8
8
|
// OpenAPI reserves Content-Type for the media type map / encoding field.
|
|
@@ -18,20 +18,20 @@ export const renderHeaders = async (headers, description, schemas, openapiVersio
|
|
|
18
18
|
if ('schema' in header && header.schema !== undefined)
|
|
19
19
|
blocks.push(...schemas.render(header.schema));
|
|
20
20
|
if ('example' in header || 'examples' in header) {
|
|
21
|
-
blocks.push(...(await renderExamples({ example: header.example, examples: header.examples }, description, 'application/json', undefined, openapiVersion)));
|
|
21
|
+
blocks.push(...(await renderExamples({ example: header.example, examples: header.examples }, description, 'application/json', undefined, openapiVersion, schemaOpenapiVersion)));
|
|
22
22
|
}
|
|
23
23
|
for (const [mediaType, content] of Object.entries('content' in header ? (header.content ?? {}) : {})) {
|
|
24
24
|
blocks.push(paragraph(strong(text('Content-Type:')), text(` ${mediaType}`)));
|
|
25
25
|
if (content.schema !== undefined)
|
|
26
26
|
blocks.push(...schemas.render(content.schema));
|
|
27
|
-
blocks.push(...(await renderExamples(content, description, mediaType, undefined, openapiVersion)));
|
|
27
|
+
blocks.push(...(await renderExamples(content, description, mediaType, undefined, openapiVersion, schemaOpenapiVersion)));
|
|
28
28
|
}
|
|
29
29
|
entries.push(item(...blocks));
|
|
30
30
|
}
|
|
31
31
|
return entries.length ? [paragraph(strong(text('Headers:'))), list(entries)] : [];
|
|
32
32
|
};
|
|
33
33
|
/** Preserve explicit encoding settings, including false flags and part headers. */
|
|
34
|
-
export const renderEncoding = async (encoding, mediaType, description, schemas, openapiVersion) => {
|
|
34
|
+
export const renderEncoding = async (encoding, mediaType, description, schemas, openapiVersion, schemaOpenapiVersion = openapiVersion) => {
|
|
35
35
|
const multipart = mediaType.startsWith('multipart/');
|
|
36
36
|
if (!multipart && mediaType !== 'application/x-www-form-urlencoded')
|
|
37
37
|
return [];
|
|
@@ -51,7 +51,7 @@ export const renderEncoding = async (encoding, mediaType, description, schemas,
|
|
|
51
51
|
if (fields.length)
|
|
52
52
|
blocks.push(list(fields));
|
|
53
53
|
if (multipart)
|
|
54
|
-
blocks.push(...(await renderHeaders(entry.headers, description, schemas, openapiVersion)));
|
|
54
|
+
blocks.push(...(await renderHeaders(entry.headers, description, schemas, openapiVersion, schemaOpenapiVersion)));
|
|
55
55
|
entries.push(item(...blocks));
|
|
56
56
|
}
|
|
57
57
|
return entries.length ? [paragraph(strong(text('Encoding:'))), list(entries)] : [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-operation.d.ts","sourceRoot":"","sources":["../src/render-operation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAEf,cAAc,EAGf,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAG5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,oEAAoE;AACpE,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,iBAAiB,CAAA;IAC9B,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,GAC1B,UAAU,eAAe,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,UAAU,cAAc,EACxB,WAAW,eAAe,EAC1B,SAAS,OAAO,EAChB,0BAA0B,aAAa,KACtC,OAAO,CAAC,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"render-operation.d.ts","sourceRoot":"","sources":["../src/render-operation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAEf,cAAc,EAGf,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAG5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,oEAAoE;AACpE,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,iBAAiB,CAAA;IAC9B,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,GAC1B,UAAU,eAAe,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,UAAU,cAAc,EACxB,WAAW,eAAe,EAC1B,SAAS,OAAO,EAChB,0BAA0B,aAAa,KACtC,OAAO,CAAC,WAAW,EAAE,CA4GvB,CAAA"}
|
package/dist/render-operation.js
CHANGED
|
@@ -59,12 +59,12 @@ export const renderOperation = async (document, path, method, pathItem, operatio
|
|
|
59
59
|
if ('schema' in parameter && parameter.schema !== undefined)
|
|
60
60
|
nodes.push(...schemas.render(parameter.schema));
|
|
61
61
|
if ('example' in parameter || 'examples' in parameter)
|
|
62
|
-
nodes.push(...(await renderExamples({ example: parameter.example, examples: parameter.examples }, description, 'application/json', 'write', openapiVersion)));
|
|
62
|
+
nodes.push(...(await renderExamples({ example: parameter.example, examples: parameter.examples }, description, 'application/json', 'write', openapiVersion, document.openapi)));
|
|
63
63
|
for (const [mediaType, content] of Object.entries('content' in parameter ? (parameter.content ?? {}) : {})) {
|
|
64
64
|
nodes.push(heading(6, text(`Content-Type: ${mediaType}`)));
|
|
65
65
|
if (content.schema !== undefined)
|
|
66
66
|
nodes.push(...schemas.render(content.schema));
|
|
67
|
-
nodes.push(...(await renderExamples(content, description, mediaType, 'write', openapiVersion)));
|
|
67
|
+
nodes.push(...(await renderExamples(content, description, mediaType, 'write', openapiVersion, document.openapi)));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
const body = getResolvedRef(operation.requestBody, mergeSiblingReferences);
|
|
@@ -76,8 +76,8 @@ export const renderOperation = async (document, path, method, pathItem, operatio
|
|
|
76
76
|
nodes.push(heading(5, text(`Content-Type: ${mediaType}`)));
|
|
77
77
|
if (content.schema !== undefined)
|
|
78
78
|
nodes.push(...schemas.render(content.schema));
|
|
79
|
-
nodes.push(...(await renderExamples(content, description, mediaType, 'write', openapiVersion)));
|
|
80
|
-
nodes.push(...(await renderEncoding(content.encoding, mediaType, description, schemas, openapiVersion)));
|
|
79
|
+
nodes.push(...(await renderExamples(content, description, mediaType, 'write', openapiVersion, document.openapi)));
|
|
80
|
+
nodes.push(...(await renderEncoding(content.encoding, mediaType, description, schemas, openapiVersion, document.openapi)));
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
const responses = Object.entries(operation.responses ?? {}).flatMap(([status, reference]) => {
|
|
@@ -88,12 +88,12 @@ export const renderOperation = async (document, path, method, pathItem, operatio
|
|
|
88
88
|
nodes.push(heading(4, text('Responses')));
|
|
89
89
|
for (const { status, response } of responses) {
|
|
90
90
|
nodes.push(heading(5, text(`Status: ${status}${response.description ? ` ${response.description}` : ''}`)));
|
|
91
|
-
nodes.push(...(await renderHeaders(response.headers, description, schemas, openapiVersion)), ...(await renderResponseLinks(response.links, description)));
|
|
91
|
+
nodes.push(...(await renderHeaders(response.headers, description, schemas, openapiVersion, document.openapi)), ...(await renderResponseLinks(response.links, description)));
|
|
92
92
|
for (const [mediaType, content] of Object.entries(response.content ?? {})) {
|
|
93
93
|
nodes.push(heading(6, text(`Content-Type: ${mediaType}`)));
|
|
94
94
|
if (content.schema !== undefined)
|
|
95
95
|
nodes.push(...schemas.render(content.schema));
|
|
96
|
-
nodes.push(...(await renderExamples(content, description, mediaType, 'read', openapiVersion)));
|
|
96
|
+
nodes.push(...(await renderExamples(content, description, mediaType, 'read', openapiVersion, document.openapi)));
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
return nodes;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
/** Where a value sits in an OpenAPI description: a schema, a map of schemas, or anything else. */
|
|
2
|
+
export type SchemaPosition = 'document' | 'schema' | 'map';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the position of the value stored under `key`, or `undefined` when that value
|
|
5
|
+
* is not part of the schema tree, such as example payloads and schema metadata.
|
|
6
|
+
*/
|
|
7
|
+
export declare const getChildPosition: (position: SchemaPosition, key: string) => SchemaPosition | undefined;
|
|
1
8
|
/**
|
|
2
9
|
* The workspace schema currently casts boolean JSON Schemas into empty objects.
|
|
3
10
|
* Restore them only in schema positions, never in example payloads or metadata.
|
|
4
11
|
* TODO: Remove this bridge when the shared schema accepts boolean JSON Schemas.
|
|
5
12
|
*/
|
|
6
|
-
export declare const restoreBooleanSchemas: (source: unknown, target: unknown) => void;
|
|
13
|
+
export declare const restoreBooleanSchemas: (source: unknown, target: unknown, position?: SchemaPosition) => void;
|
|
7
14
|
//# sourceMappingURL=restore-boolean-schemas.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restore-boolean-schemas.d.ts","sourceRoot":"","sources":["../src/restore-boolean-schemas.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"restore-boolean-schemas.d.ts","sourceRoot":"","sources":["../src/restore-boolean-schemas.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAA;AAmB1D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,cAAc,EAAE,KAAK,MAAM,KAAG,cAAc,GAAG,SAWzF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,OAAO,EACf,QAAQ,OAAO,EACf,WAAU,cAA2B,KACpC,IA2BF,CAAA"}
|
|
@@ -1,46 +1,50 @@
|
|
|
1
1
|
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
|
+
const schemaMaps = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']);
|
|
3
|
+
const schemaArrays = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
|
|
4
|
+
const schemaFields = new Set([
|
|
5
|
+
'items',
|
|
6
|
+
'not',
|
|
7
|
+
'additionalProperties',
|
|
8
|
+
'additionalItems',
|
|
9
|
+
'contains',
|
|
10
|
+
'contentSchema',
|
|
11
|
+
'propertyNames',
|
|
12
|
+
'if',
|
|
13
|
+
'then',
|
|
14
|
+
'else',
|
|
15
|
+
'unevaluatedProperties',
|
|
16
|
+
'unevaluatedItems',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Returns the position of the value stored under `key`, or `undefined` when that value
|
|
20
|
+
* is not part of the schema tree, such as example payloads and schema metadata.
|
|
21
|
+
*/
|
|
22
|
+
export const getChildPosition = (position, key) => {
|
|
23
|
+
if (position === 'map')
|
|
24
|
+
return 'schema';
|
|
25
|
+
if (position === 'schema') {
|
|
26
|
+
if (schemaMaps.has(key))
|
|
27
|
+
return 'map';
|
|
28
|
+
if (schemaArrays.has(key) || schemaFields.has(key))
|
|
29
|
+
return 'schema';
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (key === 'schema' || key === 'itemSchema')
|
|
33
|
+
return 'schema';
|
|
34
|
+
if (key === 'schemas')
|
|
35
|
+
return 'map';
|
|
36
|
+
if (['example', 'examples', 'default', 'const', 'enum'].includes(key))
|
|
37
|
+
return undefined;
|
|
38
|
+
return 'document';
|
|
39
|
+
};
|
|
2
40
|
/**
|
|
3
41
|
* The workspace schema currently casts boolean JSON Schemas into empty objects.
|
|
4
42
|
* Restore them only in schema positions, never in example payloads or metadata.
|
|
5
43
|
* TODO: Remove this bridge when the shared schema accepts boolean JSON Schemas.
|
|
6
44
|
*/
|
|
7
|
-
export const restoreBooleanSchemas = (source, target) => {
|
|
45
|
+
export const restoreBooleanSchemas = (source, target, position = 'document') => {
|
|
8
46
|
const seen = new WeakSet();
|
|
9
|
-
const
|
|
10
|
-
const schemaArrays = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
|
|
11
|
-
const schemaFields = new Set([
|
|
12
|
-
'items',
|
|
13
|
-
'not',
|
|
14
|
-
'additionalProperties',
|
|
15
|
-
'additionalItems',
|
|
16
|
-
'contains',
|
|
17
|
-
'propertyNames',
|
|
18
|
-
'if',
|
|
19
|
-
'then',
|
|
20
|
-
'else',
|
|
21
|
-
'unevaluatedProperties',
|
|
22
|
-
'unevaluatedItems',
|
|
23
|
-
'$ref-value',
|
|
24
|
-
]);
|
|
25
|
-
const getChildContext = (context, key) => {
|
|
26
|
-
if (context === 'map')
|
|
27
|
-
return 'schema';
|
|
28
|
-
if (context === 'schema') {
|
|
29
|
-
if (schemaMaps.has(key))
|
|
30
|
-
return 'map';
|
|
31
|
-
if (schemaArrays.has(key) || schemaFields.has(key))
|
|
32
|
-
return 'schema';
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
if (key === 'schema' || key === 'itemSchema')
|
|
36
|
-
return 'schema';
|
|
37
|
-
if (key === 'schemas')
|
|
38
|
-
return 'map';
|
|
39
|
-
if (['example', 'examples', 'default', 'const', 'enum'].includes(key))
|
|
40
|
-
return undefined;
|
|
41
|
-
return 'document';
|
|
42
|
-
};
|
|
43
|
-
const visit = (original, coerced, context = 'document') => {
|
|
47
|
+
const visit = (original, coerced, context) => {
|
|
44
48
|
if (context === 'schema' && typeof original === 'boolean')
|
|
45
49
|
return original;
|
|
46
50
|
if (!original || typeof original !== 'object' || !coerced || typeof coerced !== 'object' || seen.has(coerced))
|
|
@@ -61,7 +65,7 @@ export const restoreBooleanSchemas = (source, target) => {
|
|
|
61
65
|
const property = Object.getOwnPropertyDescriptor(coerced, key);
|
|
62
66
|
if (!property || !('value' in property))
|
|
63
67
|
continue;
|
|
64
|
-
const childContext =
|
|
68
|
+
const childContext = getChildPosition(context, key);
|
|
65
69
|
if (childContext === undefined)
|
|
66
70
|
continue;
|
|
67
71
|
Object.defineProperty(coerced, key, { ...property, value: visit(value, property.value, childContext) });
|
|
@@ -69,5 +73,5 @@ export const restoreBooleanSchemas = (source, target) => {
|
|
|
69
73
|
}
|
|
70
74
|
return coerced;
|
|
71
75
|
};
|
|
72
|
-
visit(source, target);
|
|
76
|
+
visit(source, target, position);
|
|
73
77
|
};
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"llm",
|
|
17
17
|
"swagger"
|
|
18
18
|
],
|
|
19
|
-
"version": "1.
|
|
19
|
+
"version": "1.3.0",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
@@ -41,10 +41,10 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@scalar/code-highlight": "0.4.7",
|
|
44
|
-
"@scalar/helpers": "0.
|
|
45
|
-
"@scalar/json-magic": "0.15.
|
|
46
|
-
"@scalar/openapi-upgrader": "0.
|
|
47
|
-
"@scalar/workspace-store": "0.
|
|
44
|
+
"@scalar/helpers": "0.15.0",
|
|
45
|
+
"@scalar/json-magic": "0.15.2",
|
|
46
|
+
"@scalar/openapi-upgrader": "0.4.0",
|
|
47
|
+
"@scalar/workspace-store": "0.67.0",
|
|
48
48
|
"rehype-parse": "^9.0.1",
|
|
49
49
|
"rehype-remark": "^10.0.1",
|
|
50
50
|
"rehype-sanitize": "^6.0.0",
|