schema-components 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/core/adapter.d.mts +115 -4
- package/dist/core/adapter.mjs +405 -75
- package/dist/core/constraints.d.mts +2 -2
- package/dist/core/constraints.mjs +0 -7
- package/dist/core/cssClasses.d.mts +52 -0
- package/dist/core/cssClasses.mjs +51 -0
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +5 -13
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +30 -2
- package/dist/core/formats.mjs +33 -1
- package/dist/core/idPath.d.mts +54 -0
- package/dist/core/idPath.mjs +66 -0
- package/dist/core/limits.d.mts +2 -0
- package/dist/core/limits.mjs +23 -0
- package/dist/core/merge.d.mts +10 -1
- package/dist/core/merge.mjs +49 -10
- package/dist/core/normalise.d.mts +40 -3
- package/dist/core/normalise.mjs +2 -2
- package/dist/core/openapi30.d.mts +15 -1
- package/dist/core/openapi30.mjs +2 -2
- package/dist/core/openapiConstants.d.mts +67 -0
- package/dist/core/openapiConstants.mjs +90 -0
- package/dist/core/ref.d.mts +2 -2
- package/dist/core/ref.mjs +85 -6
- package/dist/core/refChain.d.mts +70 -0
- package/dist/core/refChain.mjs +44 -0
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/renderer.mjs +0 -2
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +982 -2
- package/dist/core/types.d.mts +2 -2
- package/dist/core/types.mjs +1 -4
- package/dist/core/unionMatch.d.mts +36 -0
- package/dist/core/unionMatch.mjs +53 -0
- package/dist/core/version.d.mts +1 -1
- package/dist/core/version.mjs +29 -17
- package/dist/core/walkBuilders.d.mts +23 -4
- package/dist/core/walkBuilders.mjs +27 -7
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +123 -47
- package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
- package/dist/{errors-QEwOtQAA.d.mts → errors-g_MCTQel.d.mts} +10 -16
- package/dist/html/a11y.d.mts +9 -4
- package/dist/html/a11y.mjs +10 -12
- package/dist/html/renderToHtml.d.mts +10 -3
- package/dist/html/renderToHtml.mjs +13 -3
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderToHtmlStream.mjs +12 -1
- package/dist/html/renderers.d.mts +43 -8
- package/dist/html/renderers.mjs +136 -116
- package/dist/html/streamRenderers.d.mts +6 -6
- package/dist/html/streamRenderers.mjs +129 -89
- package/dist/limits-Cw5QZND8.d.mts +29 -0
- package/dist/{normalise-DaSrnr8g.mjs → normalise-DCYp06Sr.mjs} +770 -227
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/ApiSecurity.mjs +16 -2
- package/dist/openapi/components.d.mts +234 -23
- package/dist/openapi/components.mjs +183 -52
- package/dist/openapi/parser.d.mts +9 -8
- package/dist/openapi/parser.mjs +252 -70
- package/dist/openapi/resolve.d.mts +31 -15
- package/dist/openapi/resolve.mjs +260 -40
- package/dist/react/SchemaComponent.d.mts +126 -36
- package/dist/react/SchemaComponent.mjs +95 -57
- package/dist/react/SchemaView.d.mts +30 -10
- package/dist/react/SchemaView.mjs +2 -2
- package/dist/react/a11y.d.mts +21 -0
- package/dist/react/a11y.mjs +24 -0
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headless.mjs +1 -2
- package/dist/react/headlessRenderers.d.mts +9 -11
- package/dist/react/headlessRenderers.mjs +51 -102
- package/dist/{ref-si8ViYun.d.mts → ref-DjLEKa_E.d.mts} +38 -3
- package/dist/{renderer-DI6ZYf7a.d.mts → renderer-CXJ8y0qw.d.mts} +2 -2
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +2 -1
- package/dist/{types-BnxPEElk.d.mts → types-BTB73MB8.d.mts} +35 -14
- package/dist/{version-D-u7aMfy.d.mts → version-BFTVLsdb.d.mts} +7 -1
- package/package.json +1 -3
- package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
package/dist/openapi/parser.mjs
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
import { getProperty, isObject } from "../core/guards.mjs";
|
|
2
|
+
import "../core/limits.mjs";
|
|
3
|
+
import { emitDiagnostic } from "../core/diagnostics.mjs";
|
|
2
4
|
import { isPrototypePollutingKey } from "../core/uri.mjs";
|
|
5
|
+
import { detectOpenApiVersion } from "../core/version.mjs";
|
|
6
|
+
import { HTTP_METHODS } from "../core/openapiConstants.mjs";
|
|
7
|
+
import { resolveRefChain } from "../core/refChain.mjs";
|
|
3
8
|
//#region src/openapi/parser.ts
|
|
4
9
|
function getString(value, key) {
|
|
5
10
|
const result = isObject(value) ? value[key] : void 0;
|
|
6
11
|
return typeof result === "string" ? result : void 0;
|
|
7
12
|
}
|
|
8
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Narrow an OpenAPI parameter `in` value to the canonical four-value
|
|
15
|
+
* `ParameterLocation` union. Returns `undefined` for any other value
|
|
16
|
+
* (including the Swagger 2.0 `body`/`formData` locations, which the
|
|
17
|
+
* Swagger 2.0 → OpenAPI 3.x normaliser is expected to have already
|
|
18
|
+
* lifted out of the parameter array). When `diagnostics` is supplied,
|
|
19
|
+
* emits an `unknown-parameter-location` diagnostic so the caller can
|
|
20
|
+
* audit silent drops; without a sink the function still returns
|
|
21
|
+
* `undefined` rather than coercing the value, so the parameter is
|
|
22
|
+
* excluded from the operation's parameter list either way.
|
|
23
|
+
*/
|
|
24
|
+
function toParameterLocation(value, parameterName, pointer, diagnostics) {
|
|
9
25
|
if (value === "query" || value === "path" || value === "header" || value === "cookie") return value;
|
|
10
|
-
|
|
26
|
+
emitDiagnostic(diagnostics, {
|
|
27
|
+
code: "unknown-parameter-location",
|
|
28
|
+
message: parameterName !== void 0 ? `Parameter "${parameterName}" declares unknown \`in\` value ${JSON.stringify(value)}; expected one of query, path, header, cookie` : `Parameter declares unknown \`in\` value ${JSON.stringify(value)}; expected one of query, path, header, cookie`,
|
|
29
|
+
pointer,
|
|
30
|
+
detail: {
|
|
31
|
+
name: parameterName,
|
|
32
|
+
in: value
|
|
33
|
+
}
|
|
34
|
+
});
|
|
11
35
|
}
|
|
12
36
|
function parseOpenApiDocument(doc) {
|
|
13
37
|
const schemas = /* @__PURE__ */ new Map();
|
|
@@ -29,46 +53,79 @@ function getSchema(parsed, ref) {
|
|
|
29
53
|
return resolved;
|
|
30
54
|
}
|
|
31
55
|
}
|
|
32
|
-
const METHODS = [
|
|
33
|
-
"get",
|
|
34
|
-
"post",
|
|
35
|
-
"put",
|
|
36
|
-
"patch",
|
|
37
|
-
"delete",
|
|
38
|
-
"head",
|
|
39
|
-
"options",
|
|
40
|
-
"trace"
|
|
41
|
-
];
|
|
42
56
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
57
|
+
* Follow Path Item Object `$ref` chains (up to MAX_PATH_ITEM_REF_HOPS).
|
|
58
|
+
* Returns the resolved Path Item, or `undefined` when the chain cycles,
|
|
59
|
+
* exceeds the cap, or any intermediate ref fails to resolve.
|
|
60
|
+
*
|
|
61
|
+
* When `diagnostics` is supplied, cycle and depth-cap conditions emit
|
|
62
|
+
* `cyclic-path-item-ref` and `path-item-ref-too-deep` respectively. When
|
|
63
|
+
* `diagnostics` is omitted, the resolver still rejects cycles and
|
|
64
|
+
* over-deep chains silently — callers that wire their own resolver
|
|
65
|
+
* (notably `resolve.ts:resolvePathItemNode`) supply diagnostics to
|
|
66
|
+
* mirror this behaviour with full pointer information.
|
|
46
67
|
*/
|
|
47
|
-
function resolvePathItem(parsed, pathItem) {
|
|
68
|
+
function resolvePathItem(parsed, pathItem, diagnostics) {
|
|
48
69
|
if (!isObject(pathItem)) return void 0;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
return resolveRefChain(pathItem, {
|
|
71
|
+
lookup: (ref) => ref.startsWith("#/") ? resolveRefInDoc(parsed.doc, ref) : void 0,
|
|
72
|
+
maxHops: 8,
|
|
73
|
+
onCycle: (ref) => {
|
|
74
|
+
emitDiagnostic(diagnostics, {
|
|
75
|
+
code: "cyclic-path-item-ref",
|
|
76
|
+
message: `Cyclic Path Item Object $ref "${ref}"`,
|
|
77
|
+
pointer: ref,
|
|
78
|
+
detail: { ref }
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
onDepthExceeded: (ref) => {
|
|
82
|
+
emitDiagnostic(diagnostics, {
|
|
83
|
+
code: "path-item-ref-too-deep",
|
|
84
|
+
message: `Path Item Object $ref chain exceeded ${String(8)} hops`,
|
|
85
|
+
pointer: ref,
|
|
86
|
+
detail: {
|
|
87
|
+
maxHops: 8,
|
|
88
|
+
ref
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
52
93
|
}
|
|
53
94
|
function lookupPathItem(parsed, path) {
|
|
54
95
|
const resolved = resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "paths"), path));
|
|
55
96
|
if (resolved !== void 0) return resolved;
|
|
56
97
|
return resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "webhooks"), path));
|
|
57
98
|
}
|
|
58
|
-
function listOperations(parsed) {
|
|
99
|
+
function listOperations(parsed, diagnostics) {
|
|
59
100
|
const operations = [];
|
|
60
101
|
const paths = getProperty(parsed.doc, "paths");
|
|
61
102
|
if (!isObject(paths)) return operations;
|
|
103
|
+
const seenIds = /* @__PURE__ */ new Map();
|
|
62
104
|
for (const [path, rawPathItem] of Object.entries(paths)) {
|
|
63
|
-
const pathItem = resolvePathItem(parsed, rawPathItem);
|
|
105
|
+
const pathItem = resolvePathItem(parsed, rawPathItem, diagnostics);
|
|
64
106
|
if (pathItem === void 0) continue;
|
|
65
|
-
for (const method of
|
|
107
|
+
for (const method of HTTP_METHODS) {
|
|
66
108
|
const operation = getProperty(pathItem, method);
|
|
67
109
|
if (!isObject(operation)) continue;
|
|
110
|
+
const operationId = getString(operation, "operationId");
|
|
111
|
+
if (operationId !== void 0) {
|
|
112
|
+
const firstSeenAt = seenIds.get(operationId);
|
|
113
|
+
if (firstSeenAt !== void 0) emitDiagnostic(diagnostics, {
|
|
114
|
+
code: "duplicate-operation-id",
|
|
115
|
+
message: `operationId "${operationId}" is declared more than once (first at ${firstSeenAt}, again at ${method.toUpperCase()} ${path})`,
|
|
116
|
+
pointer: `/paths/${jsonPointerEscape(path)}/${method}/operationId`,
|
|
117
|
+
detail: {
|
|
118
|
+
operationId,
|
|
119
|
+
firstSeenAt,
|
|
120
|
+
duplicateAt: `${method.toUpperCase()} ${path}`
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
else seenIds.set(operationId, `${method.toUpperCase()} ${path}`);
|
|
124
|
+
}
|
|
68
125
|
operations.push({
|
|
69
126
|
path,
|
|
70
127
|
method,
|
|
71
|
-
operationId
|
|
128
|
+
operationId,
|
|
72
129
|
summary: getString(operation, "summary"),
|
|
73
130
|
description: getString(operation, "description"),
|
|
74
131
|
deprecated: getProperty(operation, "deprecated") === true,
|
|
@@ -78,31 +135,35 @@ function listOperations(parsed) {
|
|
|
78
135
|
}
|
|
79
136
|
return operations;
|
|
80
137
|
}
|
|
81
|
-
function getParameters(parsed, path, method) {
|
|
138
|
+
function getParameters(parsed, path, method, diagnostics) {
|
|
82
139
|
const pathItem = lookupPathItem(parsed, path);
|
|
83
140
|
if (pathItem === void 0) return [];
|
|
84
141
|
const operation = getProperty(pathItem, method);
|
|
85
142
|
if (!isObject(operation)) return [];
|
|
86
|
-
const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"));
|
|
87
|
-
const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"));
|
|
143
|
+
const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"), `/paths/${jsonPointerEscape(path)}/parameters`, diagnostics);
|
|
144
|
+
const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"), `/paths/${jsonPointerEscape(path)}/${method}/parameters`, diagnostics);
|
|
88
145
|
const map = /* @__PURE__ */ new Map();
|
|
89
146
|
for (const param of pathParams) map.set(`${param.name}:${param.location}`, param);
|
|
90
147
|
for (const param of opParams) map.set(`${param.name}:${param.location}`, param);
|
|
91
148
|
return [...map.values()];
|
|
92
149
|
}
|
|
93
|
-
function extractParameterList(doc, parameters) {
|
|
150
|
+
function extractParameterList(doc, parameters, pointerBase, diagnostics) {
|
|
94
151
|
if (!Array.isArray(parameters)) return [];
|
|
95
152
|
const result = [];
|
|
96
|
-
for (const param of parameters) {
|
|
153
|
+
for (const [index, param] of parameters.entries()) {
|
|
97
154
|
if (!isObject(param)) continue;
|
|
98
|
-
const
|
|
155
|
+
const entryPointer = `${pointerBase}/${String(index)}`;
|
|
156
|
+
const resolved = resolveParam(doc, param, diagnostics);
|
|
157
|
+
if (resolved === void 0) continue;
|
|
99
158
|
const name = getProperty(resolved, "name");
|
|
100
|
-
const
|
|
101
|
-
if (typeof name !== "string" || typeof
|
|
159
|
+
const rawLocation = getProperty(resolved, "in");
|
|
160
|
+
if (typeof name !== "string" || typeof rawLocation !== "string") continue;
|
|
161
|
+
const location = toParameterLocation(rawLocation, name, `${entryPointer}/in`, diagnostics);
|
|
162
|
+
if (location === void 0) continue;
|
|
102
163
|
const schema = getProperty(resolved, "schema");
|
|
103
164
|
result.push({
|
|
104
165
|
name,
|
|
105
|
-
location
|
|
166
|
+
location,
|
|
106
167
|
required: getProperty(resolved, "required") === true,
|
|
107
168
|
deprecated: getProperty(resolved, "deprecated") === true,
|
|
108
169
|
description: getString(resolved, "description"),
|
|
@@ -111,13 +172,69 @@ function extractParameterList(doc, parameters) {
|
|
|
111
172
|
}
|
|
112
173
|
return result;
|
|
113
174
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Resolve a Reference Object chain on a non-Path-Item OpenAPI node
|
|
177
|
+
* (Parameter, Header, Link, etc.). Single-hop resolution is insufficient
|
|
178
|
+
* because OAS 3.x permits chains of Reference Objects of arbitrary
|
|
179
|
+
* length — a Reference Object whose target is itself a Reference Object
|
|
180
|
+
* is legal. `resolveRefChain` centralises cycle and depth-cap protection.
|
|
181
|
+
*
|
|
182
|
+
* Cycles and over-deep chains reuse the existing `cyclic-path-item-ref`
|
|
183
|
+
* and `path-item-ref-too-deep` diagnostic codes with a
|
|
184
|
+
* `detail.kind: "<node-kind>"` discriminator (e.g. `"parameter"`,
|
|
185
|
+
* `"header"`, `"link"`). There is no dedicated code per node kind in the
|
|
186
|
+
* current `DiagnosticCode` union; consumers filter by `detail.kind` when
|
|
187
|
+
* they care to distinguish the source.
|
|
188
|
+
*
|
|
189
|
+
* TODO(round7-integration): consider adding dedicated `cyclic-*-ref` /
|
|
190
|
+
* `*-ref-too-deep` codes per node kind to `core/diagnostics.ts` so the
|
|
191
|
+
* kind discriminator on `detail` is not needed. The existing Swagger
|
|
192
|
+
* 2.0 path already uses a dedicated `swagger-cyclic-parameter-ref` for
|
|
193
|
+
* the equivalent failure mode.
|
|
194
|
+
*/
|
|
195
|
+
function resolveReferenceObjectChain(doc, node, kind, diagnostics) {
|
|
196
|
+
const kindLabel = kind === "parameter" ? "Parameter Object" : kind === "header" ? "Header Object" : "Link Object";
|
|
197
|
+
return resolveRefChain(node, {
|
|
198
|
+
lookup: (ref) => ref.startsWith("#/") ? resolveRefInDoc(doc, ref) : void 0,
|
|
199
|
+
onCycle: (ref) => {
|
|
200
|
+
emitDiagnostic(diagnostics, {
|
|
201
|
+
code: "cyclic-path-item-ref",
|
|
202
|
+
message: `Cyclic ${kindLabel} $ref "${ref}"`,
|
|
203
|
+
pointer: ref,
|
|
204
|
+
detail: {
|
|
205
|
+
ref,
|
|
206
|
+
kind
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
onDepthExceeded: (ref) => {
|
|
211
|
+
emitDiagnostic(diagnostics, {
|
|
212
|
+
code: "path-item-ref-too-deep",
|
|
213
|
+
message: `${kindLabel} $ref chain exceeded the hop cap starting from "${ref}"`,
|
|
214
|
+
pointer: ref,
|
|
215
|
+
detail: {
|
|
216
|
+
ref,
|
|
217
|
+
kind
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Resolve a Parameter Object `$ref` chain. See
|
|
225
|
+
* `resolveReferenceObjectChain` for the resolution contract.
|
|
226
|
+
*/
|
|
227
|
+
function resolveParam(doc, param, diagnostics) {
|
|
228
|
+
return resolveReferenceObjectChain(doc, param, "parameter", diagnostics);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Encode a path segment for embedding in a JSON Pointer (RFC 6901).
|
|
232
|
+
* `~` → `~0`, `/` → `~1`. Used to build pointer fragments for diagnostics
|
|
233
|
+
* — the diagnostics layer does not currently re-encode segments inserted
|
|
234
|
+
* via template strings.
|
|
235
|
+
*/
|
|
236
|
+
function jsonPointerEscape(segment) {
|
|
237
|
+
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
121
238
|
}
|
|
122
239
|
function getRequestBody(parsed, path, method) {
|
|
123
240
|
const requestBodyRaw = getProperty(getProperty(lookupPathItem(parsed, path), method), "requestBody");
|
|
@@ -140,7 +257,7 @@ function getRequestBody(parsed, path, method) {
|
|
|
140
257
|
schema
|
|
141
258
|
};
|
|
142
259
|
}
|
|
143
|
-
function getResponses(parsed, path, method) {
|
|
260
|
+
function getResponses(parsed, path, method, diagnostics) {
|
|
144
261
|
const responses = getProperty(getProperty(lookupPathItem(parsed, path), method), "responses");
|
|
145
262
|
if (!isObject(responses)) return [];
|
|
146
263
|
const result = [];
|
|
@@ -151,7 +268,7 @@ function getResponses(parsed, path, method) {
|
|
|
151
268
|
const content = getProperty(response, "content");
|
|
152
269
|
const contentTypes = isObject(content) ? Object.keys(content) : [];
|
|
153
270
|
const schema = isObject(content) ? extractSchemaFromContent(content) : void 0;
|
|
154
|
-
const headers = getResponseHeaders(response, parsed.doc);
|
|
271
|
+
const headers = getResponseHeaders(response, parsed.doc, diagnostics);
|
|
155
272
|
result.push({
|
|
156
273
|
statusCode,
|
|
157
274
|
description: getString(response, "description"),
|
|
@@ -169,15 +286,60 @@ function getResponses(parsed, path, method) {
|
|
|
169
286
|
* it has no `$ref`, or `undefined` when the `$ref` is malformed or
|
|
170
287
|
* cannot be resolved (so the caller skips the entry rather than reading
|
|
171
288
|
* stale fields from the bare `{ $ref }` envelope).
|
|
289
|
+
*
|
|
290
|
+
* OpenAPI 3.1 Reference Object — sibling merge. OAS 3.1 explicitly
|
|
291
|
+
* permits `summary` and `description` siblings of `$ref`; the wrapper's
|
|
292
|
+
* siblings override the corresponding fields on the referenced node
|
|
293
|
+
* (spec: "If the property is present on both the Reference Object and
|
|
294
|
+
* the referenced node, the value on the Reference Object overrides the
|
|
295
|
+
* value of the referenced node"). OAS 3.0 forbids siblings, so the merge
|
|
296
|
+
* is gated on the document version. The gating is best-effort — if no
|
|
297
|
+
* recognisable `openapi`/`swagger` field is present we err on the side
|
|
298
|
+
* of NOT merging siblings to avoid changing behaviour for ambiguous or
|
|
299
|
+
* partially-built documents.
|
|
172
300
|
*/
|
|
173
301
|
function resolveWrapperRef(doc, wrapper) {
|
|
174
302
|
const ref = getString(wrapper, "$ref");
|
|
175
303
|
if (ref === void 0) return wrapper;
|
|
176
|
-
|
|
304
|
+
const target = resolveRefInDoc(doc, ref);
|
|
305
|
+
if (target === void 0) return void 0;
|
|
306
|
+
if (!documentAllowsReferenceSiblings(doc)) return target;
|
|
307
|
+
return mergeReferenceSiblings(wrapper, target);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* OAS 3.1 admits `summary` and `description` siblings on a Reference
|
|
311
|
+
* Object; OAS 3.0 does not. Detect the document version once per call
|
|
312
|
+
* — `detectOpenApiVersion` reads the top-level `openapi`/`swagger` field
|
|
313
|
+
* and is cheap enough to call on every resolution without caching.
|
|
314
|
+
*/
|
|
315
|
+
function documentAllowsReferenceSiblings(doc) {
|
|
316
|
+
const version = detectOpenApiVersion(doc);
|
|
317
|
+
if (version === void 0) return false;
|
|
318
|
+
return version.major === 3 && version.minor >= 1;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Per OAS 3.1, only `summary` and `description` siblings on a Reference
|
|
322
|
+
* Object are permitted and they override the referenced node. Any other
|
|
323
|
+
* sibling is ignored (spec: "Any properties of a Reference Object other
|
|
324
|
+
* than those described above SHALL be ignored"). The returned object is
|
|
325
|
+
* a fresh shallow merge — the input wrapper and target are not mutated.
|
|
326
|
+
*/
|
|
327
|
+
const REFERENCE_OBJECT_SIBLING_KEYS = ["summary", "description"];
|
|
328
|
+
function mergeReferenceSiblings(wrapper, target) {
|
|
329
|
+
const merged = { ...target };
|
|
330
|
+
for (const key of REFERENCE_OBJECT_SIBLING_KEYS) {
|
|
331
|
+
const siblingValue = wrapper[key];
|
|
332
|
+
if (typeof siblingValue === "string") merged[key] = siblingValue;
|
|
333
|
+
}
|
|
334
|
+
return merged;
|
|
177
335
|
}
|
|
178
336
|
function extractSchemaFromContent(content) {
|
|
179
|
-
const
|
|
180
|
-
|
|
337
|
+
for (const [mediaType, mediaObj] of Object.entries(content)) {
|
|
338
|
+
if (mediaTypeBase(mediaType) !== "application/json") continue;
|
|
339
|
+
if (!isObject(mediaObj)) continue;
|
|
340
|
+
const schema = getProperty(mediaObj, "schema");
|
|
341
|
+
if (isObject(schema)) return schema;
|
|
342
|
+
}
|
|
181
343
|
for (const [mediaType, mediaObj] of Object.entries(content)) {
|
|
182
344
|
if (!isJsonSuffixMediaType(mediaType)) continue;
|
|
183
345
|
if (!isObject(mediaObj)) continue;
|
|
@@ -191,17 +353,40 @@ function extractSchemaFromContent(content) {
|
|
|
191
353
|
}
|
|
192
354
|
}
|
|
193
355
|
/**
|
|
356
|
+
* Return the lowercased media-type base — the type/subtype with any
|
|
357
|
+
* RFC 7231 parameters (`; charset=...`, `; profile=...`, etc.) stripped
|
|
358
|
+
* and surrounding whitespace trimmed. Returns the empty string when the
|
|
359
|
+
* input has no recognisable base (defensive against malformed entries).
|
|
360
|
+
*/
|
|
361
|
+
function mediaTypeBase(mediaType) {
|
|
362
|
+
return mediaType.toLowerCase().split(";", 1)[0]?.trim() ?? "";
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
194
365
|
* Detect RFC 6839 structured-syntax-suffix media types that encode JSON.
|
|
195
366
|
* Matches `application/<anything>+json`, optionally with parameters
|
|
196
367
|
* (`; charset=utf-8`). Excludes the literal `application/json`, which
|
|
197
368
|
* the caller checks separately to preserve preference order.
|
|
198
369
|
*/
|
|
199
370
|
function isJsonSuffixMediaType(mediaType) {
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
return baseType.startsWith("application/") && baseType.endsWith("+json");
|
|
371
|
+
const base = mediaTypeBase(mediaType);
|
|
372
|
+
if (base === "application/json") return false;
|
|
373
|
+
return base.startsWith("application/") && base.endsWith("+json");
|
|
204
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Resolve an in-document `$ref` against the supplied doc root.
|
|
377
|
+
*
|
|
378
|
+
* Limitation — cross-Schema-Object relative refs: refs that do NOT
|
|
379
|
+
* start with `#/` are not resolved here. The `normaliseOpenApiSchemas`
|
|
380
|
+
* pipeline (see `resolveRelativeRefs` in `core/normalise.ts`) rewrites
|
|
381
|
+
* relative refs WITHIN a Schema Object using that schema's `$id` base
|
|
382
|
+
* URI, but it does not currently model `$id` scopes that span Schema
|
|
383
|
+
* Object boundaries (e.g. a sibling component schema with its own
|
|
384
|
+
* `$id` that another schema's relative ref targets). Such refs survive
|
|
385
|
+
* normalisation unchanged and fall through this function returning
|
|
386
|
+
* `undefined`. `resolve.ts:detectUnsupportedCrossSchemaRefs` walks the
|
|
387
|
+
* normalised doc and emits `cross-schema-relative-ref-unsupported` per
|
|
388
|
+
* offending ref so consumers notice the silent failure.
|
|
389
|
+
*/
|
|
205
390
|
function resolveRefInDoc(doc, ref) {
|
|
206
391
|
if (!ref.startsWith("#/")) return void 0;
|
|
207
392
|
const parts = ref.slice(2).split("/");
|
|
@@ -248,14 +433,13 @@ function getSecuritySchemes(parsed) {
|
|
|
248
433
|
}
|
|
249
434
|
return result;
|
|
250
435
|
}
|
|
251
|
-
function getResponseHeaders(response, doc) {
|
|
436
|
+
function getResponseHeaders(response, doc, diagnostics) {
|
|
252
437
|
const result = /* @__PURE__ */ new Map();
|
|
253
438
|
const headers = getProperty(response, "headers");
|
|
254
439
|
if (!isObject(headers)) return result;
|
|
255
440
|
for (const [name, headerObj] of Object.entries(headers)) {
|
|
256
441
|
if (!isObject(headerObj)) continue;
|
|
257
|
-
const
|
|
258
|
-
const header = (ref !== void 0 && doc !== void 0 ? resolveRefInDoc(doc, ref) : void 0) ?? headerObj;
|
|
442
|
+
const header = doc !== void 0 ? resolveReferenceObjectChain(doc, headerObj, "header", diagnostics) ?? headerObj : headerObj;
|
|
259
443
|
const schemaProp = getProperty(header, "schema");
|
|
260
444
|
result.set(name, {
|
|
261
445
|
name,
|
|
@@ -267,14 +451,15 @@ function getResponseHeaders(response, doc) {
|
|
|
267
451
|
}
|
|
268
452
|
return result;
|
|
269
453
|
}
|
|
270
|
-
function listWebhooks(parsed) {
|
|
454
|
+
function listWebhooks(parsed, diagnostics) {
|
|
271
455
|
const result = [];
|
|
272
456
|
const webhooks = getProperty(parsed.doc, "webhooks");
|
|
273
457
|
if (!isObject(webhooks)) return result;
|
|
274
|
-
for (const [name,
|
|
275
|
-
|
|
458
|
+
for (const [name, rawHookItem] of Object.entries(webhooks)) {
|
|
459
|
+
const hookItem = resolvePathItem(parsed, rawHookItem, diagnostics);
|
|
460
|
+
if (hookItem === void 0) continue;
|
|
276
461
|
const operations = [];
|
|
277
|
-
for (const method of
|
|
462
|
+
for (const method of HTTP_METHODS) {
|
|
278
463
|
const operation = getProperty(hookItem, method);
|
|
279
464
|
if (!isObject(operation)) continue;
|
|
280
465
|
operations.push({
|
|
@@ -315,7 +500,7 @@ function getXmlInfo(schema) {
|
|
|
315
500
|
wrapped: getProperty(xml, "wrapped") === true
|
|
316
501
|
};
|
|
317
502
|
}
|
|
318
|
-
function listCallbacks(parsed, path, method) {
|
|
503
|
+
function listCallbacks(parsed, path, method, diagnostics) {
|
|
319
504
|
const operation = getProperty(lookupPathItem(parsed, path), method);
|
|
320
505
|
if (!isObject(operation)) return [];
|
|
321
506
|
const callbacks = getProperty(operation, "callbacks");
|
|
@@ -324,21 +509,20 @@ function listCallbacks(parsed, path, method) {
|
|
|
324
509
|
for (const [name, callbackItem] of Object.entries(callbacks)) {
|
|
325
510
|
if (!isObject(callbackItem)) continue;
|
|
326
511
|
const operations = [];
|
|
327
|
-
for (const [cbPath,
|
|
328
|
-
|
|
329
|
-
|
|
512
|
+
for (const [cbPath, rawCbPathItem] of Object.entries(callbackItem)) {
|
|
513
|
+
const cbPathItem = resolvePathItem(parsed, rawCbPathItem, diagnostics);
|
|
514
|
+
if (cbPathItem === void 0) continue;
|
|
515
|
+
for (const cbMethod of HTTP_METHODS) {
|
|
330
516
|
const cbOp = getProperty(cbPathItem, cbMethod);
|
|
331
517
|
if (!isObject(cbOp)) continue;
|
|
332
|
-
const ref = getString(cbOp, "$ref");
|
|
333
|
-
const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? cbOp : cbOp;
|
|
334
518
|
operations.push({
|
|
335
519
|
path: `${name}/${cbPath}`,
|
|
336
520
|
method: cbMethod,
|
|
337
|
-
operationId: getString(
|
|
338
|
-
summary: getString(
|
|
339
|
-
description: getString(
|
|
340
|
-
deprecated: getProperty(
|
|
341
|
-
operation:
|
|
521
|
+
operationId: getString(cbOp, "operationId"),
|
|
522
|
+
summary: getString(cbOp, "summary"),
|
|
523
|
+
description: getString(cbOp, "description"),
|
|
524
|
+
deprecated: getProperty(cbOp, "deprecated") === true,
|
|
525
|
+
operation: cbOp
|
|
342
526
|
});
|
|
343
527
|
}
|
|
344
528
|
}
|
|
@@ -349,7 +533,7 @@ function listCallbacks(parsed, path, method) {
|
|
|
349
533
|
}
|
|
350
534
|
return result;
|
|
351
535
|
}
|
|
352
|
-
function getLinks(parsed, path, method, statusCode) {
|
|
536
|
+
function getLinks(parsed, path, method, statusCode, diagnostics) {
|
|
353
537
|
const response = getProperty(getProperty(getProperty(lookupPathItem(parsed, path), method), "responses"), statusCode);
|
|
354
538
|
if (!isObject(response)) return [];
|
|
355
539
|
const links = getProperty(response, "links");
|
|
@@ -357,9 +541,7 @@ function getLinks(parsed, path, method, statusCode) {
|
|
|
357
541
|
const result = [];
|
|
358
542
|
for (const [name, linkObj] of Object.entries(links)) {
|
|
359
543
|
if (!isObject(linkObj)) continue;
|
|
360
|
-
const
|
|
361
|
-
const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? linkObj : linkObj;
|
|
362
|
-
const link = isObject(resolved) ? resolved : linkObj;
|
|
544
|
+
const link = resolveReferenceObjectChain(parsed.doc, linkObj, "link", diagnostics) ?? linkObj;
|
|
363
545
|
const params = getProperty(link, "parameters");
|
|
364
546
|
const paramMap = /* @__PURE__ */ new Map();
|
|
365
547
|
if (isObject(params)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as DiagnosticsOptions } from "../diagnostics-
|
|
1
|
+
import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
|
|
2
2
|
import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequestBody } from "./parser.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/openapi/resolve.d.ts
|
|
@@ -15,24 +15,40 @@ import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequest
|
|
|
15
15
|
* same form `<SchemaComponent>` does, keeping the OpenAPI components on
|
|
16
16
|
* the same pipeline as the top-level adapter.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* (`duplicate-body-parameter`, `dropped-swagger-feature`,
|
|
20
|
-
* `unknown-json-schema-dialect`, `divisible-by-conflict`,
|
|
21
|
-
* `relative-ref-resolved`, etc.) are forwarded to the sink. Passing
|
|
22
|
-
* diagnostics also bypasses the cache so each call observes the
|
|
23
|
-
* normalisation pipeline running against the supplied sink — caching
|
|
24
|
-
* would silently swallow every emission after the first.
|
|
18
|
+
* ### Caching and diagnostics
|
|
25
19
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
20
|
+
* Normalisation runs at most once per document identity. The full set
|
|
21
|
+
* of doc-level diagnostics emitted during that single run is captured
|
|
22
|
+
* into the cache alongside the parsed result. Each caller-supplied
|
|
23
|
+
* sink receives the captured diagnostics exactly once per cached
|
|
24
|
+
* entry, no matter how many times `getParsed` is called with that
|
|
25
|
+
* `(doc, sink)` pair.
|
|
26
|
+
*
|
|
27
|
+
* The previous implementation bypassed the cache whenever
|
|
28
|
+
* `diagnostics` was supplied and re-ran the entire normalisation
|
|
29
|
+
* pipeline against the new sink. That fired every doc-level
|
|
30
|
+
* diagnostic once per call, so a parent like `ApiWebhooks` that
|
|
31
|
+
* renders `ApiWebhook` per webhook entry caused N-fold emission of a
|
|
32
|
+
* single real cause. With the new strategy, cardinality stays at one
|
|
33
|
+
* per real cause regardless of how many child renders share the
|
|
34
|
+
* sink.
|
|
35
|
+
*
|
|
36
|
+
* Strict mode is treated as a per-call invariant — see
|
|
37
|
+
* {@link replayCapturedDiagnostics} for the rationale.
|
|
29
38
|
*/
|
|
30
39
|
declare function getParsed(doc: Record<string, unknown>, diagnostics?: DiagnosticsOptions): OpenApiDocument;
|
|
31
40
|
/**
|
|
32
|
-
* Coerce an unknown value to a record, returning
|
|
33
|
-
*
|
|
41
|
+
* Coerce an unknown value to a record, returning `undefined` when the
|
|
42
|
+
* value is not a plain object. Callers MUST handle the `undefined` case
|
|
43
|
+
* explicitly — typically by rendering a "doc not an object" diagnostic
|
|
44
|
+
* and short-circuiting, never by silently substituting `{}`.
|
|
45
|
+
*
|
|
46
|
+
* A previous implementation fell back to `{}` for non-objects, which
|
|
47
|
+
* masked configuration mistakes (passing a string, `null`, an array, or
|
|
48
|
+
* `undefined` as the OpenAPI document) as an empty document with no
|
|
49
|
+
* operations.
|
|
34
50
|
*/
|
|
35
|
-
declare function toDoc(value: unknown): Record<string, unknown
|
|
51
|
+
declare function toDoc(value: unknown): Record<string, unknown> | undefined;
|
|
36
52
|
/**
|
|
37
53
|
* Path-Item-level metadata. OpenAPI 3.1 added `summary` and `description`
|
|
38
54
|
* to Path Item Objects alongside the existing operation-level fields.
|
|
@@ -59,7 +75,7 @@ interface ResolvedOperation {
|
|
|
59
75
|
* normalisation pipeline (every re-run would emit each diagnostic
|
|
60
76
|
* again into the sink).
|
|
61
77
|
*/
|
|
62
|
-
declare function resolveOperationFromParsed(parsed: OpenApiDocument, path: string, method: string): ResolvedOperation;
|
|
78
|
+
declare function resolveOperationFromParsed(parsed: OpenApiDocument, path: string, method: string, diagnostics?: DiagnosticsOptions): ResolvedOperation;
|
|
63
79
|
/**
|
|
64
80
|
* Resolve an operation from an OpenAPI document by path and method.
|
|
65
81
|
* Throws if the operation is not found.
|