@shipworthy/openapi-parser 0.0.1 → 1.0.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 +10 -7
- package/dist/errors.d.ts +42 -14
- package/dist/errors.js +3 -3
- package/dist/hoist.js +48 -43
- package/dist/http.js +6 -9
- package/dist/load.js +18 -18
- package/dist/loader.js +7 -7
- package/dist/parsers.js +7 -18
- package/dist/pipeline.js +78 -89
- package/dist/pointer.d.ts +3 -0
- package/dist/pointer.js +26 -22
- package/dist/run.js +18 -18
- package/dist/schemas/3.1/callback.js +2 -2
- package/dist/schemas/3.1/example.js +2 -2
- package/dist/schemas/3.1/header.js +3 -3
- package/dist/schemas/3.1/media-type.js +2 -2
- package/dist/schemas/3.1/object.js +2 -5
- package/dist/schemas/3.1/parameter.js +3 -3
- package/dist/schemas/3.1/responses.js +2 -2
- package/dist/upgraders/3.0-to-3.1.js +34 -31
- package/dist/url.d.ts +1 -1
- package/dist/url.js +42 -45
- package/dist/yaml.js +50 -46
- package/package.json +7 -9
package/dist/parsers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Predicate } from "effect";
|
|
1
|
+
import { Effect, Predicate, Schema, String as Str } from "effect";
|
|
2
2
|
import { ParseError, ParserFailure } from "./errors.js";
|
|
3
3
|
import { promisePlugin } from "./plugins.js";
|
|
4
4
|
import { extensionOf } from "./url.js";
|
|
@@ -7,19 +7,18 @@ function decodeData(data) {
|
|
|
7
7
|
if (data === undefined)
|
|
8
8
|
return "";
|
|
9
9
|
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
10
|
-
return
|
|
10
|
+
return Str.replace(/^\uFEFF/, "")(text);
|
|
11
11
|
}
|
|
12
|
+
const decodeJsonText = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
12
13
|
function parseJsonText(text) {
|
|
13
|
-
|
|
14
|
-
return null;
|
|
15
|
-
return JSON.parse(text);
|
|
14
|
+
return Str.isEmpty(Str.trim(text)) ? Effect.succeed(null) : decodeJsonText(text);
|
|
16
15
|
}
|
|
17
16
|
export const parseText = Effect.fnUntraced(function* (source) {
|
|
18
17
|
const text = decodeData(source);
|
|
19
|
-
const first =
|
|
18
|
+
const first = Str.trimStart(text)[0];
|
|
20
19
|
const yaml = () => Effect.try({ try: () => parseYamlDocument(text), catch: parseFailure });
|
|
21
20
|
if (first === "{" || first === "[") {
|
|
22
|
-
return yield*
|
|
21
|
+
return yield* parseJsonText(text).pipe(Effect.catch(yaml));
|
|
23
22
|
}
|
|
24
23
|
return yield* yaml();
|
|
25
24
|
});
|
|
@@ -45,20 +44,10 @@ function jsonParser() {
|
|
|
45
44
|
order: 100,
|
|
46
45
|
can: (file) => extensionOf(file.url) === ".json" || extensionOf(file.url) === "",
|
|
47
46
|
run(file) {
|
|
48
|
-
return Effect.
|
|
49
|
-
try: () => parseJson(file),
|
|
50
|
-
catch: (cause) => new ParserFailure({
|
|
51
|
-
message: cause instanceof Error ? cause.message : "Parser Error",
|
|
52
|
-
source: file.url,
|
|
53
|
-
cause,
|
|
54
|
-
}),
|
|
55
|
-
});
|
|
47
|
+
return parseJsonText(decodeData(file.data)).pipe(Effect.mapError((cause) => new ParserFailure({ message: cause.message, source: file.url, cause })));
|
|
56
48
|
},
|
|
57
49
|
};
|
|
58
50
|
}
|
|
59
|
-
function parseJson(file) {
|
|
60
|
-
return parseJsonText(decodeData(file.data));
|
|
61
|
-
}
|
|
62
51
|
function yamlParser() {
|
|
63
52
|
return {
|
|
64
53
|
order: 200,
|
package/dist/pipeline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JsonPointer, JsonSchema, Predicate } from "effect";
|
|
1
|
+
import { Array as Arr, Formatter, JsonPointer, JsonSchema, Predicate, Record as Rec, String as Str, } from "effect";
|
|
2
2
|
import { getAt, joinPointer } from "./pointer.js";
|
|
3
3
|
import { componentSchemas, schemaSlots } from "./upgraders/3.0-to-3.1.js";
|
|
4
4
|
import { bundleFailure, circularAliasFailure, invalidPointerFailure, missingPointerFailure, } from "./errors.js";
|
|
@@ -37,7 +37,7 @@ export function runSchemaPipeline(document, mode, hooks) {
|
|
|
37
37
|
}
|
|
38
38
|
const others = schemaSlots(document, false);
|
|
39
39
|
const slotSchemas = [];
|
|
40
|
-
const booleanSlots =
|
|
40
|
+
const booleanSlots = Arr.map(others, (slot) => {
|
|
41
41
|
const value = slot.parent[slot.key];
|
|
42
42
|
return typeof value === "boolean" ? value : undefined;
|
|
43
43
|
});
|
|
@@ -48,10 +48,10 @@ export function runSchemaPipeline(document, mode, hooks) {
|
|
|
48
48
|
slotSchemas.push(normalized.schema);
|
|
49
49
|
}
|
|
50
50
|
const recursive = recursiveNames(definitions);
|
|
51
|
-
const rootDefs = new Set(
|
|
51
|
+
const rootDefs = new Set(Rec.keys(asRecord(document.$defs) ?? {}));
|
|
52
52
|
const aliases = new Map();
|
|
53
53
|
const resolving = new Set();
|
|
54
|
-
for (const name of
|
|
54
|
+
for (const name of Rec.keys(definitions)) {
|
|
55
55
|
definitions[name] = resolveValue(definitions[name], definitions, rootDefs, recursive, aliases, resolving, mode, hooks, `#/$defs/${name}`);
|
|
56
56
|
}
|
|
57
57
|
for (let index = 0; index < slotSchemas.length; index++) {
|
|
@@ -61,9 +61,9 @@ export function runSchemaPipeline(document, mode, hooks) {
|
|
|
61
61
|
slot.parent[slot.key] = slotSchemas[index];
|
|
62
62
|
}
|
|
63
63
|
const parked = [];
|
|
64
|
-
for (const [name, schema] of
|
|
64
|
+
for (const [name, schema] of Rec.toEntries(definitions))
|
|
65
65
|
park(schema, "definition", name, [], parked);
|
|
66
|
-
|
|
66
|
+
Arr.forEach(slotSchemas, (schema, index) => park(schema, "schema", String(index), [], parked));
|
|
67
67
|
const schemas = (slotSchemas.length > 0 ? slotSchemas : [{}]);
|
|
68
68
|
const converted = JsonSchema.toMultiDocumentOpenApi3_1({
|
|
69
69
|
dialect: "draft-2020-12",
|
|
@@ -79,7 +79,7 @@ export function runSchemaPipeline(document, mode, hooks) {
|
|
|
79
79
|
if (value !== undefined)
|
|
80
80
|
slot.parent[slot.key] = value;
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
Arr.forEach(others, (slot, index) => {
|
|
83
83
|
slot.parent[slot.key] = booleanSlots[index] ?? converted.schemas[index];
|
|
84
84
|
});
|
|
85
85
|
rewriteLeftoverDefinitionRefs(document);
|
|
@@ -94,26 +94,26 @@ function normalizeSchema(value) {
|
|
|
94
94
|
const record = asRecord(value);
|
|
95
95
|
if (!record)
|
|
96
96
|
throw bundleFailure("A schema position must contain an object or a boolean");
|
|
97
|
-
const normalized = JsonSchema.fromSchemaOpenApi3_1(
|
|
97
|
+
const normalized = JsonSchema.fromSchemaOpenApi3_1(record);
|
|
98
98
|
// A schema $id is its own resource, so its $defs join the shared pool.
|
|
99
99
|
// Nested $defs on a schema without $id stay on that schema.
|
|
100
100
|
if (typeof record.$id === "string")
|
|
101
101
|
return normalized;
|
|
102
102
|
const extracted = asRecord(normalized.definitions);
|
|
103
|
-
if (!extracted ||
|
|
103
|
+
if (!extracted || Rec.isEmptyRecord(extracted))
|
|
104
104
|
return normalized;
|
|
105
105
|
const schema = asRecord(normalized.schema) ?? {};
|
|
106
106
|
schema.$defs = { ...asRecord(schema.$defs), ...extracted };
|
|
107
107
|
return { dialect: normalized.dialect, schema, definitions: {} };
|
|
108
108
|
}
|
|
109
109
|
function mergeDefinitions(pool, extra, owner, order) {
|
|
110
|
-
for (const [name, schema] of
|
|
111
|
-
if (!
|
|
110
|
+
for (const [name, schema] of Rec.toEntries(extra)) {
|
|
111
|
+
if (!Rec.has(pool, name)) {
|
|
112
112
|
pool[name] = schema;
|
|
113
113
|
rememberDefinition(order, name);
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
|
-
if (
|
|
116
|
+
if (Formatter.formatJson(pool[name]) === Formatter.formatJson(schema))
|
|
117
117
|
continue;
|
|
118
118
|
const unique = uniqueName(pool, name);
|
|
119
119
|
pool[unique] = schema;
|
|
@@ -132,18 +132,18 @@ function orderDefinitions(definitions, order) {
|
|
|
132
132
|
return definitions;
|
|
133
133
|
const output = {};
|
|
134
134
|
for (const name of order) {
|
|
135
|
-
if (
|
|
135
|
+
if (Rec.has(source, name))
|
|
136
136
|
output[name] = source[name];
|
|
137
137
|
}
|
|
138
|
-
for (const name of
|
|
139
|
-
if (!
|
|
138
|
+
for (const name of Rec.keys(source)) {
|
|
139
|
+
if (!Rec.has(output, name))
|
|
140
140
|
output[name] = source[name];
|
|
141
141
|
}
|
|
142
142
|
return output;
|
|
143
143
|
}
|
|
144
144
|
function uniqueName(pool, name) {
|
|
145
145
|
let suffix = 2;
|
|
146
|
-
while (
|
|
146
|
+
while (Rec.has(pool, `${name}_${suffix}`))
|
|
147
147
|
suffix += 1;
|
|
148
148
|
return `${name}_${suffix}`;
|
|
149
149
|
}
|
|
@@ -153,37 +153,12 @@ function rewriteDefName(schema, from, to) {
|
|
|
153
153
|
return;
|
|
154
154
|
if (record.$ref === `#/$defs/${from}`)
|
|
155
155
|
record.$ref = `#/$defs/${to}`;
|
|
156
|
-
for (const value of
|
|
156
|
+
for (const value of Rec.values(record))
|
|
157
157
|
rewriteDefName(value, from, to);
|
|
158
158
|
}
|
|
159
|
-
function liftSiblings(value) {
|
|
160
|
-
if (Array.isArray(value))
|
|
161
|
-
return value.map((item) => liftSiblings(item));
|
|
162
|
-
const record = asRecord(value);
|
|
163
|
-
if (!record)
|
|
164
|
-
return value;
|
|
165
|
-
const lifted = {};
|
|
166
|
-
for (const [key, child] of Object.entries(record))
|
|
167
|
-
lifted[key] = liftSiblings(child);
|
|
168
|
-
if (typeof lifted.$ref !== "string")
|
|
169
|
-
return lifted;
|
|
170
|
-
const siblings = { ...lifted };
|
|
171
|
-
delete siblings.$ref;
|
|
172
|
-
// readOnly/writeOnly stay beside $ref so generators can wrap the reference itself.
|
|
173
|
-
const flags = {};
|
|
174
|
-
for (const key of ["readOnly", "writeOnly"]) {
|
|
175
|
-
if (siblings[key] === true) {
|
|
176
|
-
flags[key] = true;
|
|
177
|
-
delete siblings[key];
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (Object.keys(siblings).length === 0)
|
|
181
|
-
return { $ref: lifted.$ref, ...flags };
|
|
182
|
-
return { allOf: [{ $ref: lifted.$ref }, siblings], ...flags };
|
|
183
|
-
}
|
|
184
159
|
function recursiveNames(definitions) {
|
|
185
|
-
const names =
|
|
186
|
-
const dependencies = new Map(
|
|
160
|
+
const names = Rec.keys(definitions);
|
|
161
|
+
const dependencies = new Map(Arr.map(names, (name) => [name, collectDefRefs(definitions[name])]));
|
|
187
162
|
const recursive = new Set();
|
|
188
163
|
const state = new Map();
|
|
189
164
|
const stack = [];
|
|
@@ -192,7 +167,7 @@ function recursiveNames(definitions) {
|
|
|
192
167
|
if (current === 1) {
|
|
193
168
|
const start = stack.indexOf(name);
|
|
194
169
|
const cycle = stack.slice(start);
|
|
195
|
-
if (
|
|
170
|
+
if (Arr.every(cycle, (item) => isPureRef(definitions[item]))) {
|
|
196
171
|
throw circularAliasFailure(name);
|
|
197
172
|
}
|
|
198
173
|
for (const item of cycle)
|
|
@@ -215,27 +190,28 @@ function recursiveNames(definitions) {
|
|
|
215
190
|
return recursive;
|
|
216
191
|
}
|
|
217
192
|
function collectDefRefs(value, found = new Set(), seen = new Set()) {
|
|
218
|
-
if (
|
|
193
|
+
if (!Predicate.isObjectOrArray(value) || seen.has(value))
|
|
219
194
|
return found;
|
|
220
195
|
seen.add(value);
|
|
221
|
-
if (
|
|
196
|
+
if (Arr.isArray(value)) {
|
|
222
197
|
for (const item of value)
|
|
223
198
|
collectDefRefs(item, found, seen);
|
|
224
199
|
return found;
|
|
225
200
|
}
|
|
226
|
-
const
|
|
201
|
+
const record = value;
|
|
202
|
+
const ref = record.$ref;
|
|
227
203
|
if (typeof ref === "string") {
|
|
228
204
|
const key = referenceKey(ref);
|
|
229
205
|
if (key)
|
|
230
206
|
found.add(key);
|
|
231
207
|
}
|
|
232
|
-
for (const child of
|
|
208
|
+
for (const child of Rec.values(record))
|
|
233
209
|
collectDefRefs(child, found, seen);
|
|
234
210
|
return found;
|
|
235
211
|
}
|
|
236
212
|
const rootDefRef = (name) => `#/root-defs/${JsonPointer.escapeToken(name)}`;
|
|
237
213
|
function resolveValue(value, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, pointer) {
|
|
238
|
-
if (
|
|
214
|
+
if (Arr.isArray(value)) {
|
|
239
215
|
for (let index = 0; index < value.length; index++) {
|
|
240
216
|
value[index] = resolveValue(value[index], definitions, rootDefs, recursive, aliases, resolving, mode, hooks, joinPointer(pointer, String(index)));
|
|
241
217
|
}
|
|
@@ -246,32 +222,44 @@ function resolveValue(value, definitions, rootDefs, recursive, aliases, resolvin
|
|
|
246
222
|
return value;
|
|
247
223
|
if (typeof record.$ref === "string") {
|
|
248
224
|
const key = referenceKey(record.$ref);
|
|
249
|
-
if (!key)
|
|
250
|
-
return record;
|
|
251
|
-
if (!Object.hasOwn(definitions, key)) {
|
|
225
|
+
if (key && !Rec.has(definitions, key)) {
|
|
252
226
|
if (rootDefs.has(key)) {
|
|
253
227
|
record.$ref = rootDefRef(key);
|
|
254
|
-
return record;
|
|
255
228
|
}
|
|
256
|
-
|
|
229
|
+
else {
|
|
230
|
+
throw missingPointerFailure(key, record.$ref, record.$ref);
|
|
231
|
+
}
|
|
257
232
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (recursive.has(final))
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
233
|
+
else if (key) {
|
|
234
|
+
const final = finalName(key, definitions, aliases);
|
|
235
|
+
const ref = joinPointer("#/$defs", final);
|
|
236
|
+
if (recursive.has(final) || mode === "bundle") {
|
|
237
|
+
if (recursive.has(final))
|
|
238
|
+
hooks?.onCircular?.(pointer);
|
|
239
|
+
record.$ref = ref;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
if (resolving.has(final))
|
|
243
|
+
throw circularAliasFailure(final);
|
|
244
|
+
resolving.add(final);
|
|
245
|
+
const resolved = resolveValue(structuredClone(definitions[final]), definitions, rootDefs, recursive, aliases, resolving, mode, hooks, ref);
|
|
246
|
+
resolving.delete(final);
|
|
247
|
+
const siblings = { ...record };
|
|
248
|
+
delete siblings.$ref;
|
|
249
|
+
const result = Rec.isEmptyRecord(siblings)
|
|
250
|
+
? resolved
|
|
251
|
+
: {
|
|
252
|
+
allOf: [
|
|
253
|
+
resolved,
|
|
254
|
+
resolveValue(siblings, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, pointer),
|
|
255
|
+
],
|
|
256
|
+
};
|
|
257
|
+
hooks?.onDereference?.(pointer, result, record, "$ref");
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
265
260
|
}
|
|
266
|
-
if (resolving.has(final))
|
|
267
|
-
throw circularAliasFailure(final);
|
|
268
|
-
resolving.add(final);
|
|
269
|
-
const resolved = resolveValue(structuredClone(definitions[final]), definitions, rootDefs, recursive, aliases, resolving, mode, hooks, ref);
|
|
270
|
-
resolving.delete(final);
|
|
271
|
-
hooks?.onDereference?.(pointer, resolved, record, "$ref");
|
|
272
|
-
return resolved;
|
|
273
261
|
}
|
|
274
|
-
for (const [key, child] of
|
|
262
|
+
for (const [key, child] of Rec.toEntries(record)) {
|
|
275
263
|
record[key] = resolveValue(child, definitions, rootDefs, recursive, aliases, resolving, mode, hooks, joinPointer(pointer, key));
|
|
276
264
|
}
|
|
277
265
|
return record;
|
|
@@ -287,7 +275,7 @@ function finalName(name, definitions, aliases) {
|
|
|
287
275
|
const schema = definitions[name];
|
|
288
276
|
if (isPureRef(schema)) {
|
|
289
277
|
const key = referenceKey(schema.$ref);
|
|
290
|
-
if (key &&
|
|
278
|
+
if (key && Rec.has(definitions, key)) {
|
|
291
279
|
const resolved = finalName(key, definitions, aliases);
|
|
292
280
|
aliases.set(name, resolved);
|
|
293
281
|
return resolved;
|
|
@@ -298,10 +286,10 @@ function finalName(name, definitions, aliases) {
|
|
|
298
286
|
}
|
|
299
287
|
function isPureRef(value) {
|
|
300
288
|
const record = asRecord(value);
|
|
301
|
-
return Boolean(record && typeof record.$ref === "string" &&
|
|
289
|
+
return Boolean(record && typeof record.$ref === "string" && Rec.size(record) === 1);
|
|
302
290
|
}
|
|
303
291
|
function referenceKey(ref) {
|
|
304
|
-
if (!
|
|
292
|
+
if (!Str.startsWith("#")(ref))
|
|
305
293
|
return undefined;
|
|
306
294
|
const tokens = JsonPointer.parseUriFragment(ref);
|
|
307
295
|
if (tokens === undefined)
|
|
@@ -313,7 +301,7 @@ function park(value, scope, id, pointer, saved) {
|
|
|
313
301
|
if (!record)
|
|
314
302
|
return;
|
|
315
303
|
for (const key of ANNOTATIONS) {
|
|
316
|
-
if (!
|
|
304
|
+
if (!Rec.has(record, key))
|
|
317
305
|
continue;
|
|
318
306
|
saved.push({ scope, id, pointer: [...pointer], key, value: record[key] });
|
|
319
307
|
delete record[key];
|
|
@@ -321,15 +309,16 @@ function park(value, scope, id, pointer, saved) {
|
|
|
321
309
|
for (const key of NESTED_SINGLE)
|
|
322
310
|
park(record[key], scope, id, [...pointer, key], saved);
|
|
323
311
|
for (const key of NESTED_ARRAY) {
|
|
324
|
-
|
|
312
|
+
const items = record[key];
|
|
313
|
+
if (!Arr.isArray(items))
|
|
325
314
|
continue;
|
|
326
|
-
|
|
315
|
+
Arr.forEach(items, (item, index) => park(item, scope, id, [...pointer, key, String(index)], saved));
|
|
327
316
|
}
|
|
328
317
|
for (const key of NESTED_MAP) {
|
|
329
318
|
const map = asRecord(record[key]);
|
|
330
319
|
if (!map)
|
|
331
320
|
continue;
|
|
332
|
-
for (const child of
|
|
321
|
+
for (const child of Rec.keys(map))
|
|
333
322
|
park(map[child], scope, id, [...pointer, key, child], saved);
|
|
334
323
|
}
|
|
335
324
|
}
|
|
@@ -347,7 +336,7 @@ function restore(converted, saved) {
|
|
|
347
336
|
function descend(value, pointer) {
|
|
348
337
|
let current = value;
|
|
349
338
|
for (const token of pointer) {
|
|
350
|
-
if (
|
|
339
|
+
if (Arr.isArray(current)) {
|
|
351
340
|
current = current[Number(token)];
|
|
352
341
|
continue;
|
|
353
342
|
}
|
|
@@ -360,7 +349,7 @@ function descend(value, pointer) {
|
|
|
360
349
|
}
|
|
361
350
|
function restoreRootDefs(document) {
|
|
362
351
|
const visit = (value) => {
|
|
363
|
-
if (
|
|
352
|
+
if (Arr.isArray(value)) {
|
|
364
353
|
for (const item of value)
|
|
365
354
|
visit(item);
|
|
366
355
|
return;
|
|
@@ -368,19 +357,19 @@ function restoreRootDefs(document) {
|
|
|
368
357
|
const record = asRecord(value);
|
|
369
358
|
if (!record)
|
|
370
359
|
return;
|
|
371
|
-
if (typeof record.$ref === "string" &&
|
|
372
|
-
const name =
|
|
360
|
+
if (typeof record.$ref === "string" && Str.startsWith("#/root-defs/")(record.$ref)) {
|
|
361
|
+
const name = Str.slice("#/root-defs/".length)(record.$ref);
|
|
373
362
|
record.$ref = joinPointer("#/$defs", JsonPointer.unescapeToken(name));
|
|
374
363
|
}
|
|
375
|
-
for (const child of
|
|
364
|
+
for (const child of Rec.values(record))
|
|
376
365
|
visit(child);
|
|
377
366
|
};
|
|
378
367
|
visit(document);
|
|
379
368
|
}
|
|
380
369
|
function rewriteLeftoverDefinitionRefs(document) {
|
|
381
|
-
const names = new Set(
|
|
370
|
+
const names = new Set(Rec.keys(asRecord(asRecord(document.components)?.schemas) ?? {}));
|
|
382
371
|
const visit = (value) => {
|
|
383
|
-
if (
|
|
372
|
+
if (Arr.isArray(value)) {
|
|
384
373
|
for (const item of value)
|
|
385
374
|
visit(item);
|
|
386
375
|
return;
|
|
@@ -388,14 +377,14 @@ function rewriteLeftoverDefinitionRefs(document) {
|
|
|
388
377
|
const record = asRecord(value);
|
|
389
378
|
if (!record)
|
|
390
379
|
return;
|
|
391
|
-
if (typeof record.$ref === "string" &&
|
|
380
|
+
if (typeof record.$ref === "string" && Str.startsWith("#/$defs/")(record.$ref)) {
|
|
392
381
|
const tokens = JsonPointer.parseUriFragment(record.$ref);
|
|
393
382
|
const name = tokens?.[0] === "$defs" ? tokens[1] : undefined;
|
|
394
383
|
if (name && names.has(name)) {
|
|
395
384
|
record.$ref = joinPointer("#/components/schemas", [name, ...(tokens?.slice(2) ?? [])]);
|
|
396
385
|
}
|
|
397
386
|
}
|
|
398
|
-
for (const child of
|
|
387
|
+
for (const child of Rec.values(record))
|
|
399
388
|
visit(child);
|
|
400
389
|
};
|
|
401
390
|
visit(document);
|
|
@@ -403,7 +392,7 @@ function rewriteLeftoverDefinitionRefs(document) {
|
|
|
403
392
|
export function inlineRemaining(document, recursive, hooks) {
|
|
404
393
|
const visiting = new Set();
|
|
405
394
|
const walk = (value, parent, key, pointer = "#") => {
|
|
406
|
-
if (
|
|
395
|
+
if (Arr.isArray(value)) {
|
|
407
396
|
for (let index = 0; index < value.length; index++) {
|
|
408
397
|
walk(value[index], value, String(index), joinPointer(pointer, String(index)));
|
|
409
398
|
}
|
|
@@ -412,7 +401,7 @@ export function inlineRemaining(document, recursive, hooks) {
|
|
|
412
401
|
const record = asRecord(value);
|
|
413
402
|
if (!record)
|
|
414
403
|
return;
|
|
415
|
-
if (typeof record.$ref === "string" &&
|
|
404
|
+
if (typeof record.$ref === "string" && Str.startsWith("#")(record.$ref) && parent && key) {
|
|
416
405
|
const ref = record.$ref;
|
|
417
406
|
const name = componentSchemaName(ref);
|
|
418
407
|
if (name && recursive.has(name)) {
|
|
@@ -432,7 +421,7 @@ export function inlineRemaining(document, recursive, hooks) {
|
|
|
432
421
|
hooks?.onDereference?.(pointer, copy, parent, key);
|
|
433
422
|
return;
|
|
434
423
|
}
|
|
435
|
-
for (const [childKey, child] of
|
|
424
|
+
for (const [childKey, child] of Rec.toEntries(record)) {
|
|
436
425
|
walk(child, record, childKey, joinPointer(pointer, childKey));
|
|
437
426
|
}
|
|
438
427
|
};
|
package/dist/pointer.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
1
2
|
export declare function parsePointer(pointer: string, source?: string): string[];
|
|
2
3
|
/** Join pointer tokens without percent-encoding, so `$` stays literal. */
|
|
3
4
|
export declare function joinPointer(base: string, tokens: string | readonly string[]): string;
|
|
5
|
+
/** Reads the member or array element that a JSON Pointer token names (RFC 6901). */
|
|
6
|
+
export declare function childAt(value: unknown, token: string): Option.Option<unknown>;
|
|
4
7
|
export declare function getAt(root: unknown, pointer: string, source?: string): unknown;
|
|
5
8
|
export declare function setAt(root: object, pointer: string, value: unknown): void;
|
|
6
9
|
/** Copy enumerable data, including cycles, and drop non-enumerable parser symbols. */
|
package/dist/pointer.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { JsonPointer } from "effect";
|
|
1
|
+
import { Array as Arr, JsonPointer, Option, pipe, Predicate, Record as Rec, String as Str, } from "effect";
|
|
2
2
|
import { invalidPointerFailure, missingPointerFailure } from "./errors.js";
|
|
3
3
|
const unsafeTokens = new Set(["__proto__", "constructor", "prototype"]);
|
|
4
4
|
export function parsePointer(pointer, source = pointer) {
|
|
5
|
-
const hash = pointer.
|
|
5
|
+
const hash = pipe(pointer, Str.indexOf("#"), Option.match({ onNone: () => pointer, onSome: (index) => Str.slice(index)(pointer) }));
|
|
6
6
|
if (hash === "" || hash === "#")
|
|
7
7
|
return [];
|
|
8
|
-
const fragment =
|
|
8
|
+
const fragment = Str.startsWith("#")(hash) ? hash : `#${hash}`;
|
|
9
9
|
const tokens = JsonPointer.parseUriFragment(fragment) ?? splitPointer(fragment);
|
|
10
10
|
if (tokens === undefined)
|
|
11
11
|
throw invalidPointerFailure(fragment, source);
|
|
@@ -13,46 +13,49 @@ export function parsePointer(pointer, source = pointer) {
|
|
|
13
13
|
}
|
|
14
14
|
/** Effect's URI-fragment parser rejects spaces. JSON Pointer tokens may contain them. */
|
|
15
15
|
function splitPointer(fragment) {
|
|
16
|
-
const body =
|
|
16
|
+
const body = Str.startsWith("#")(fragment) ? Str.slice(1)(fragment) : fragment;
|
|
17
17
|
if (body === "")
|
|
18
18
|
return [];
|
|
19
|
-
if (!
|
|
19
|
+
if (!Str.startsWith("/")(body))
|
|
20
20
|
return undefined;
|
|
21
|
-
return body
|
|
22
|
-
.slice(1)
|
|
23
|
-
.split("/")
|
|
24
|
-
.map((token) => token.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
21
|
+
return pipe(body, Str.slice(1), Str.split("/"), Arr.map((token) => pipe(token, Str.replace(/~1/g, "/"), Str.replace(/~0/g, "~"))));
|
|
25
22
|
}
|
|
26
23
|
/** Join pointer tokens without percent-encoding, so `$` stays literal. */
|
|
27
24
|
export function joinPointer(base, tokens) {
|
|
28
25
|
const parts = typeof tokens === "string" ? [tokens] : tokens;
|
|
29
|
-
let pointer =
|
|
26
|
+
let pointer = Str.includes("#")(base) ? base : `${base}#`;
|
|
30
27
|
for (const token of parts)
|
|
31
28
|
pointer += `/${JsonPointer.escapeToken(token)}`;
|
|
32
29
|
return pointer;
|
|
33
30
|
}
|
|
31
|
+
const arrayIndex = /^(?:0|[1-9]\d*)$/;
|
|
32
|
+
/** Reads the member or array element that a JSON Pointer token names (RFC 6901). */
|
|
33
|
+
export function childAt(value, token) {
|
|
34
|
+
if (Arr.isArray(value)) {
|
|
35
|
+
return arrayIndex.test(token) ? Arr.get(value, Number(token)) : Option.none();
|
|
36
|
+
}
|
|
37
|
+
return Predicate.isObject(value) ? Rec.get(value, token) : Option.none();
|
|
38
|
+
}
|
|
34
39
|
export function getAt(root, pointer, source = pointer) {
|
|
35
40
|
const tokens = parsePointer(pointer, source);
|
|
36
41
|
let current = root;
|
|
37
42
|
for (const token of tokens) {
|
|
38
|
-
|
|
43
|
+
const child = childAt(current, token);
|
|
44
|
+
if (Option.isNone(child))
|
|
39
45
|
throw missingPointerFailure(token, source, pointer);
|
|
40
|
-
|
|
41
|
-
if (!Object.hasOwn(current, token))
|
|
42
|
-
throw missingPointerFailure(token, source, pointer);
|
|
43
|
-
current = current[token];
|
|
46
|
+
current = child.value;
|
|
44
47
|
}
|
|
45
48
|
return current;
|
|
46
49
|
}
|
|
47
50
|
export function setAt(root, pointer, value) {
|
|
48
51
|
const tokens = parsePointer(pointer, pointer);
|
|
49
|
-
if (tokens.length === 0 ||
|
|
52
|
+
if (tokens.length === 0 || Arr.some(tokens, (token) => unsafeTokens.has(token)))
|
|
50
53
|
return;
|
|
51
54
|
let current = root;
|
|
52
55
|
for (let index = 0; index < tokens.length - 1; index++) {
|
|
53
56
|
const token = tokens[index];
|
|
54
57
|
const next = current[token];
|
|
55
|
-
if (
|
|
58
|
+
if (!Predicate.isObject(next))
|
|
56
59
|
current[token] = {};
|
|
57
60
|
current = current[token];
|
|
58
61
|
}
|
|
@@ -60,21 +63,22 @@ export function setAt(root, pointer, value) {
|
|
|
60
63
|
}
|
|
61
64
|
/** Copy enumerable data, including cycles, and drop non-enumerable parser symbols. */
|
|
62
65
|
export function cloneData(value, seen = new Map()) {
|
|
63
|
-
if (
|
|
66
|
+
if (!Predicate.isObjectOrArray(value))
|
|
64
67
|
return value;
|
|
65
68
|
const cached = seen.get(value);
|
|
66
69
|
if (cached !== undefined)
|
|
67
70
|
return cached;
|
|
68
|
-
if (
|
|
71
|
+
if (Arr.isArray(value)) {
|
|
69
72
|
const copy = [];
|
|
70
73
|
seen.set(value, copy);
|
|
71
74
|
for (const item of value)
|
|
72
75
|
copy.push(cloneData(item, seen));
|
|
73
76
|
return copy;
|
|
74
77
|
}
|
|
78
|
+
const source = value;
|
|
75
79
|
const copy = {};
|
|
76
|
-
seen.set(
|
|
77
|
-
for (const key of
|
|
78
|
-
copy[key] = cloneData(
|
|
80
|
+
seen.set(source, copy);
|
|
81
|
+
for (const key of Rec.keys(source))
|
|
82
|
+
copy[key] = cloneData(source[key], seen);
|
|
79
83
|
return copy;
|
|
80
84
|
}
|