@texaryn/schema-json 0.3.0 → 0.5.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 +169 -0
- package/THIRD-PARTY-NOTICES.md +43 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +8 -0
- package/dist/adapter.js.map +1 -1
- package/dist/metaschemas/2019-09.d.ts +4 -0
- package/dist/metaschemas/2019-09.d.ts.map +1 -0
- package/dist/metaschemas/2019-09.js +18 -0
- package/dist/metaschemas/2019-09.js.map +1 -0
- package/dist/metaschemas/2020-12.d.ts +4 -0
- package/dist/metaschemas/2020-12.d.ts.map +1 -0
- package/dist/metaschemas/2020-12.js +20 -0
- package/dist/metaschemas/2020-12.js.map +1 -0
- package/dist/metaschemas/draft-07.d.ts +4 -0
- package/dist/metaschemas/draft-07.d.ts.map +1 -0
- package/dist/metaschemas/draft-07.js +6 -0
- package/dist/metaschemas/draft-07.js.map +1 -0
- package/dist/metaschemas/index.d.ts +22 -0
- package/dist/metaschemas/index.d.ts.map +1 -0
- package/dist/metaschemas/index.js +102 -0
- package/dist/metaschemas/index.js.map +1 -0
- package/dist/projection.d.ts +1 -1
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +629 -51
- package/dist/projection.js.map +1 -1
- package/package.json +4 -3
package/dist/projection.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mergeNode } from 'json-schema-library';
|
|
1
2
|
const VALID_TYPES = new Set([
|
|
2
3
|
'string',
|
|
3
4
|
'number',
|
|
@@ -13,7 +14,8 @@ function toPointer(value) {
|
|
|
13
14
|
function escapeSegment(segment) {
|
|
14
15
|
return segment.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
/** The `type` the schema itself declares, and nothing else. */
|
|
18
|
+
function resolveExplicitType(schema) {
|
|
17
19
|
const type = schema.type;
|
|
18
20
|
if (Array.isArray(type)) {
|
|
19
21
|
return type.find((t) => VALID_TYPES.has(t));
|
|
@@ -23,6 +25,96 @@ function resolveType(schema) {
|
|
|
23
25
|
}
|
|
24
26
|
return undefined;
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Keywords that apply to exactly one JSON type, grouped by that type.
|
|
30
|
+
*
|
|
31
|
+
* Two different jobs are done with these, and only one of them infers
|
|
32
|
+
* anything. Every family takes part in detecting a conflict, because a schema
|
|
33
|
+
* drawing keywords from two families implies no single shape. Only `object`
|
|
34
|
+
* and `array` are shapes a form can be given, so those are the only two ever
|
|
35
|
+
* inferred: `string` and `number` are here to be noticed, not chosen.
|
|
36
|
+
*
|
|
37
|
+
* Inferring a scalar would need a rule that is right rather than symmetrical,
|
|
38
|
+
* and there is not one. `minimum` cannot tell `number` from `integer`, and a
|
|
39
|
+
* wrong scalar guess selects the wrong widget, which is harder to notice than
|
|
40
|
+
* a field that never appeared at all.
|
|
41
|
+
*
|
|
42
|
+
* `format` is deliberately absent: it annotates a string's contents rather
|
|
43
|
+
* than describing structure, and schemas apply it to non-strings in practice.
|
|
44
|
+
*/
|
|
45
|
+
const KEYWORD_FAMILIES = {
|
|
46
|
+
object: [
|
|
47
|
+
'properties',
|
|
48
|
+
'patternProperties',
|
|
49
|
+
'additionalProperties',
|
|
50
|
+
'propertyNames',
|
|
51
|
+
'required',
|
|
52
|
+
'minProperties',
|
|
53
|
+
'maxProperties',
|
|
54
|
+
'dependentSchemas',
|
|
55
|
+
'dependentRequired',
|
|
56
|
+
// json-schema-library normalises draft-07 `dependencies` into the two
|
|
57
|
+
// above at parse time. Listed anyway, so the rule does not rely on that.
|
|
58
|
+
'dependencies',
|
|
59
|
+
'unevaluatedProperties',
|
|
60
|
+
],
|
|
61
|
+
array: [
|
|
62
|
+
'items',
|
|
63
|
+
'prefixItems',
|
|
64
|
+
'additionalItems',
|
|
65
|
+
'contains',
|
|
66
|
+
'minItems',
|
|
67
|
+
'maxItems',
|
|
68
|
+
'uniqueItems',
|
|
69
|
+
'minContains',
|
|
70
|
+
'maxContains',
|
|
71
|
+
'unevaluatedItems',
|
|
72
|
+
],
|
|
73
|
+
string: ['minLength', 'maxLength', 'pattern'],
|
|
74
|
+
number: ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'],
|
|
75
|
+
};
|
|
76
|
+
/** Which families a schema draws keywords from, computed over all of them at once. */
|
|
77
|
+
function projectionTypeFamilies(schema) {
|
|
78
|
+
const families = new Set();
|
|
79
|
+
for (const [family, keywords] of Object.entries(KEYWORD_FAMILIES)) {
|
|
80
|
+
if (keywords.some((keyword) => schema[keyword] !== undefined)) {
|
|
81
|
+
families.add(family);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return families;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether any branch of this node's `oneOf`/`anyOf` could be given a shape at
|
|
88
|
+
* all, for some value.
|
|
89
|
+
*
|
|
90
|
+
* This is what separates "the current value matches no branch" from "this
|
|
91
|
+
* composition is unrenderable whatever the value is". The first is validation's
|
|
92
|
+
* subject and transient; the second is a limitation of this adapter and worth
|
|
93
|
+
* reporting.
|
|
94
|
+
*/
|
|
95
|
+
function compositionHasRenderableAlternative(node) {
|
|
96
|
+
const branches = [...(node.oneOf ?? []), ...(node.anyOf ?? [])];
|
|
97
|
+
return branches.some((branch) => {
|
|
98
|
+
const resolved = dereference(branch);
|
|
99
|
+
const schema = resolved.schema;
|
|
100
|
+
if (!schema || typeof schema !== 'object')
|
|
101
|
+
return false;
|
|
102
|
+
if (resolveExplicitType(schema))
|
|
103
|
+
return true;
|
|
104
|
+
return inferProjectionShape(schema).kind === 'resolved';
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function inferProjectionShape(schema) {
|
|
108
|
+
const families = projectionTypeFamilies(schema);
|
|
109
|
+
if (families.size > 1)
|
|
110
|
+
return { kind: 'ambiguous', families: [...families].sort() };
|
|
111
|
+
if (families.size === 1) {
|
|
112
|
+
const [family] = families;
|
|
113
|
+
if (family === 'object' || family === 'array')
|
|
114
|
+
return { kind: 'resolved', type: family };
|
|
115
|
+
}
|
|
116
|
+
return { kind: 'none' };
|
|
117
|
+
}
|
|
26
118
|
function extractConstraints(schema) {
|
|
27
119
|
const constraints = {};
|
|
28
120
|
if (typeof schema.minLength === 'number')
|
|
@@ -53,7 +145,13 @@ function extractConstraints(schema) {
|
|
|
53
145
|
constraints.uniqueItems = schema.uniqueItems;
|
|
54
146
|
return constraints;
|
|
55
147
|
}
|
|
56
|
-
function extractAnnotations(schema
|
|
148
|
+
function extractAnnotations(schema,
|
|
149
|
+
/**
|
|
150
|
+
* Set where two declarations apply to this location whatever the instance is
|
|
151
|
+
* and disagree. The merged schema still carries one of them, and which one is
|
|
152
|
+
* the merge's traversal order rather than an answer.
|
|
153
|
+
*/
|
|
154
|
+
omitDefault = false) {
|
|
57
155
|
const annotations = {};
|
|
58
156
|
if (typeof schema.title === 'string')
|
|
59
157
|
annotations.title = schema.title;
|
|
@@ -67,7 +165,7 @@ function extractAnnotations(schema) {
|
|
|
67
165
|
annotations.deprecated = schema.deprecated;
|
|
68
166
|
if (Array.isArray(schema.examples))
|
|
69
167
|
annotations.examples = schema.examples;
|
|
70
|
-
if ('default' in schema)
|
|
168
|
+
if (!omitDefault && 'default' in schema)
|
|
71
169
|
annotations.default = schema.default;
|
|
72
170
|
return annotations;
|
|
73
171
|
}
|
|
@@ -81,45 +179,339 @@ function dereference(node) {
|
|
|
81
179
|
return node.$ref ? node.resolveRef() : node;
|
|
82
180
|
}
|
|
83
181
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* `dependencies` is normalized by json-schema-library into
|
|
87
|
-
* `dependentSchemas`/`dependentRequired` at parse time, so it needs no separate
|
|
88
|
-
* handling here.
|
|
182
|
+
* The position of a schema within the document it was authored in, as a JSON
|
|
183
|
+
* Pointer with the root as the empty string.
|
|
89
184
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
185
|
+
* `schemaLocation` is already that, prefixed with the `#` of a URI fragment,
|
|
186
|
+
* and it names what a `$ref` resolves to rather than the reference. Both
|
|
187
|
+
* adapters report sources in this form so a diagnostic means the same thing
|
|
188
|
+
* whichever one produced it.
|
|
93
189
|
*/
|
|
94
|
-
function
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
190
|
+
function documentPointer(node) {
|
|
191
|
+
const location = node.schemaLocation;
|
|
192
|
+
return typeof location === 'string' ? location.replace(/^#/, '') : '';
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Visits every schema position that applies to one location whatever the
|
|
196
|
+
* instance is.
|
|
197
|
+
*
|
|
198
|
+
* The edges followed are the unconditional ones: a schema's own keywords, its
|
|
199
|
+
* `allOf` branches, and whatever a `$ref` resolves to. Nothing about an
|
|
200
|
+
* instance can make one of those not apply, so two of them declaring different
|
|
201
|
+
* values is a fact about the schema and there is no instance that resolves it.
|
|
202
|
+
*
|
|
203
|
+
* `oneOf`, `anyOf`, `if`/`then`/`else` and `dependentSchemas` are deliberately
|
|
204
|
+
* not followed, and not because their conflicts are less real. A declaration
|
|
205
|
+
* one of them carries competes with the base only while its branch is
|
|
206
|
+
* selected, so whether the disagreement holds is a state of the data, and
|
|
207
|
+
* `SchemaProjection.diagnostics` carries facts about schemas. Reporting it
|
|
208
|
+
* there would mean a diagnostic that appears and disappears as a discriminator
|
|
209
|
+
* is typed.
|
|
210
|
+
*
|
|
211
|
+
* `roots` is a set rather than one node because a location can be declared in
|
|
212
|
+
* several places at once: `properties/x` on the node's own schema and on each
|
|
213
|
+
* of its `allOf` branches are all declarations of the same location.
|
|
214
|
+
*/
|
|
215
|
+
function eachUnconditional(roots, visitor) {
|
|
216
|
+
const visited = new Set();
|
|
217
|
+
const visit = (current) => {
|
|
218
|
+
const resolved = dereference(current);
|
|
219
|
+
// The same guard, and for the same measured reason, as
|
|
220
|
+
// `collectCandidateProperties`: `resolveRef()` returns a fresh node every
|
|
221
|
+
// call, so identity alone never closes a cycle, and an inline branch has no
|
|
222
|
+
// location of its own to key on.
|
|
223
|
+
const location = resolved.schemaLocation;
|
|
224
|
+
const key = typeof location === 'string' ? location : resolved;
|
|
225
|
+
if (visited.has(key))
|
|
98
226
|
return;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
227
|
+
visited.add(key);
|
|
228
|
+
visitor(resolved);
|
|
229
|
+
for (const branch of resolved.allOf ?? [])
|
|
230
|
+
visit(branch);
|
|
231
|
+
};
|
|
232
|
+
for (const root of roots)
|
|
233
|
+
visit(root);
|
|
234
|
+
}
|
|
235
|
+
function collectUnconditionalDefaults(roots) {
|
|
236
|
+
const declarations = [];
|
|
237
|
+
eachUnconditional(roots, (node) => {
|
|
238
|
+
const schema = node.schema;
|
|
239
|
+
if (schema !== null && typeof schema === 'object' && 'default' in schema) {
|
|
240
|
+
declarations.push({ value: schema.default, source: documentPointer(node) });
|
|
102
241
|
}
|
|
242
|
+
});
|
|
243
|
+
return declarations;
|
|
244
|
+
}
|
|
245
|
+
/** The positions that unconditionally declare one property of `roots`. */
|
|
246
|
+
function unconditionalChildren(roots, key) {
|
|
247
|
+
const children = [];
|
|
248
|
+
eachUnconditional(roots, (node) => {
|
|
249
|
+
const child = node.properties?.[key];
|
|
250
|
+
if (child)
|
|
251
|
+
children.push(child);
|
|
252
|
+
});
|
|
253
|
+
return children;
|
|
254
|
+
}
|
|
255
|
+
/** The positions that unconditionally declare the elements of `roots`. */
|
|
256
|
+
function unconditionalItems(roots) {
|
|
257
|
+
const items = [];
|
|
258
|
+
eachUnconditional(roots, (node) => {
|
|
259
|
+
if (node.items)
|
|
260
|
+
items.push(node.items);
|
|
261
|
+
});
|
|
262
|
+
return items;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The declarations to report, or nothing where the schema states one answer.
|
|
266
|
+
*
|
|
267
|
+
* One declaration needs no report, and several that agree state the same
|
|
268
|
+
* answer more than once, which is not a disagreement however many times it is
|
|
269
|
+
* said. Compared by value, as ADR-003's own pass compares them: two branches
|
|
270
|
+
* declaring an equal object have nothing to choose between.
|
|
271
|
+
*/
|
|
272
|
+
function disagreeingDefaults(declarations) {
|
|
273
|
+
const [first, ...rest] = declarations;
|
|
274
|
+
return rest.every((other) => deepEqual(other.value, first.value)) ? undefined : declarations;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Every property key that could appear at this node's own instance location,
|
|
278
|
+
* across every branch that might apply to it.
|
|
279
|
+
*
|
|
280
|
+
* This is the static half of the inactive-node contract: a branch the data does
|
|
281
|
+
* not currently select still contributes its property pointers, which then
|
|
282
|
+
* project with `active: false`. The dynamic half is `reduceNode`, which decides
|
|
283
|
+
* which of them apply now, and the two must stay separate. Making the candidate
|
|
284
|
+
* set data-driven would delete a pointer the moment its branch stopped
|
|
285
|
+
* matching, which is exactly the flicker the contract exists to prevent.
|
|
286
|
+
*
|
|
287
|
+
* **Recursion is through applicators only, and only those acting on this same
|
|
288
|
+
* instance location**: `if`, `then`, `else`, `allOf`, `anyOf`, `oneOf`,
|
|
289
|
+
* `dependentSchemas`, and whatever a `$ref` resolves to. draft-07
|
|
290
|
+
* `dependencies` needs no separate handling, because json-schema-library
|
|
291
|
+
* normalises it into `dependentSchemas` at parse time.
|
|
292
|
+
*
|
|
293
|
+
* Three things are deliberately not followed:
|
|
294
|
+
*
|
|
295
|
+
* - **`not`**, because a subschema inside it describes a shape the instance
|
|
296
|
+
* must *not* satisfy. Collecting its properties as candidates would invert
|
|
297
|
+
* its meaning.
|
|
298
|
+
* - **the values of `properties`**, because those describe a child location,
|
|
299
|
+
* not this one. `owner` is collected; the walk descends into `/owner`
|
|
300
|
+
* separately and collects `name` there. Following it here would make `name` a
|
|
301
|
+
* sibling of `owner`.
|
|
302
|
+
* - **`items` and `prefixItems`**, for the same reason: an array's items are
|
|
303
|
+
* their own locations.
|
|
304
|
+
*
|
|
305
|
+
* The cycle guard keys on `schemaLocation` rather than on node identity, which
|
|
306
|
+
* was measured rather than assumed and is the opposite of what it looks like it
|
|
307
|
+
* should be: `resolveRef()` returns a fresh `SchemaNode` on every call, and the
|
|
308
|
+
* raw `schema` object it wraps is fresh too, so an identity `Set` never matches
|
|
309
|
+
* and a recursive `$ref` would not terminate. `schemaLocation` is stable, is
|
|
310
|
+
* distinct for each inline branch position, and repeats when a reference closes
|
|
311
|
+
* a cycle, which is precisely the three properties needed. The set is per call,
|
|
312
|
+
* because deduplicating a schema location is only sound while collecting names
|
|
313
|
+
* for one instance location; the same schema legitimately recurs at a deeper
|
|
314
|
+
* pointer, and `walk` visits it again there.
|
|
315
|
+
*/
|
|
316
|
+
function collectCandidateProperties(node) {
|
|
317
|
+
const candidates = new Map();
|
|
318
|
+
const visited = new Set();
|
|
319
|
+
const record = (key, propNode, direct) => {
|
|
320
|
+
const entry = candidates.get(key) ?? { alternatives: [] };
|
|
321
|
+
if (direct)
|
|
322
|
+
entry.direct ??= propNode;
|
|
323
|
+
else
|
|
324
|
+
entry.alternatives.push(propNode);
|
|
325
|
+
candidates.set(key, entry);
|
|
103
326
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
327
|
+
const visit = (current, own) => {
|
|
328
|
+
const resolved = dereference(current);
|
|
329
|
+
// Keyed on `schemaLocation` where there is one, and on the node itself
|
|
330
|
+
// where there is not, which together cover every way the traversal can
|
|
331
|
+
// come back to where it started.
|
|
332
|
+
//
|
|
333
|
+
// Neither alone is sufficient and each covers what the other cannot. A
|
|
334
|
+
// cycle requires a `$ref`, because an inline schema cannot nest into
|
|
335
|
+
// itself, and `resolveRef()` returns a fresh `SchemaNode` on every call
|
|
336
|
+
// whose raw `schema` object is fresh too, so identity never matches across
|
|
337
|
+
// one; those nodes do carry a location. An inline branch is never resolved
|
|
338
|
+
// through a ref, so its identity is stable within one traversal.
|
|
339
|
+
//
|
|
340
|
+
// This deliberately replaced a depth cap. A cap terminates, but it does so
|
|
341
|
+
// by dropping candidates a valid schema declared, which is the silent
|
|
342
|
+
// disappearance the whole projection-diagnostic design exists to prevent:
|
|
343
|
+
// a field nested under 70 applicators is still a field.
|
|
344
|
+
const location = resolved.schemaLocation;
|
|
345
|
+
const key = typeof location === 'string' ? location : resolved;
|
|
346
|
+
if (visited.has(key))
|
|
347
|
+
return;
|
|
348
|
+
visited.add(key);
|
|
349
|
+
for (const [key, propNode] of Object.entries(resolved.properties ?? {})) {
|
|
350
|
+
record(key, propNode, own);
|
|
351
|
+
}
|
|
352
|
+
for (const branch of [resolved.if, resolved.then, resolved.else]) {
|
|
353
|
+
if (branch)
|
|
354
|
+
visit(branch, false);
|
|
355
|
+
}
|
|
356
|
+
for (const branches of [resolved.allOf, resolved.anyOf, resolved.oneOf]) {
|
|
357
|
+
for (const branch of branches ?? [])
|
|
358
|
+
visit(branch, false);
|
|
359
|
+
}
|
|
360
|
+
for (const dependency of Object.values(resolved.dependentSchemas ?? {})) {
|
|
110
361
|
if (dependency && typeof dependency === 'object') {
|
|
111
|
-
|
|
362
|
+
visit(dependency, false);
|
|
112
363
|
}
|
|
113
364
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
merge(branch.properties);
|
|
117
|
-
for (const branch of node.oneOf ?? [])
|
|
118
|
-
merge(branch.properties);
|
|
119
|
-
for (const branch of node.anyOf ?? [])
|
|
120
|
-
merge(branch.properties);
|
|
365
|
+
};
|
|
366
|
+
visit(node, true);
|
|
121
367
|
return candidates;
|
|
122
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* The schema to project for a candidate when no branch is active.
|
|
371
|
+
*
|
|
372
|
+
* A hidden node is still compiled, so it needs some shape, and an inactive key
|
|
373
|
+
* defined differently by two branches has no single right answer. The rule is
|
|
374
|
+
* therefore stated rather than left to traversal order: the node's own
|
|
375
|
+
* declaration wins, and failing that the first branch the schema declares. It
|
|
376
|
+
* is a stable placeholder and deliberately not a claim that one branch matters
|
|
377
|
+
* more, which is why `CandidateProperty` keeps the alternatives instead of
|
|
378
|
+
* discarding them.
|
|
379
|
+
*
|
|
380
|
+
* Last of three, and only reached when no branch has claimed the location: an
|
|
381
|
+
* active branch gives `walk` the reduced node, and a provisionally selected one
|
|
382
|
+
* gives it `composeChild`, so this is consulted for neither.
|
|
383
|
+
*/
|
|
384
|
+
function candidatePrototype(candidate) {
|
|
385
|
+
return candidate.direct ?? candidate.alternatives[0];
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* The two statements that both apply to a child location, as one node.
|
|
389
|
+
*
|
|
390
|
+
* A selected branch beats an unselected sibling, and it does not beat the
|
|
391
|
+
* node's own unconditional declaration: in JSON Schema the two are conjunctive,
|
|
392
|
+
* so the instance has to satisfy both. Replacing in either direction is wrong,
|
|
393
|
+
* and visibly so from the form. Dropping the base constraint makes the form
|
|
394
|
+
* accept what the submission rejects. Dropping the branch's makes an unrelated
|
|
395
|
+
* limit appear from nowhere the moment the branch activates, when all the user
|
|
396
|
+
* supplied was the property that completed it.
|
|
397
|
+
*
|
|
398
|
+
* `mergeNode` is the library's own routine, the one `reduceNode` uses to fold a
|
|
399
|
+
* reducer's result into the node it is building, called here in that same order:
|
|
400
|
+
* base first, branch second, so the branch wins a keyword both sides declare.
|
|
401
|
+
* Matching it is the point. It makes the provisional projection *equal* the
|
|
402
|
+
* active projection of the same branch rather than merely resemble it, which is
|
|
403
|
+
* what a hand-written conjunction could not promise.
|
|
404
|
+
*
|
|
405
|
+
* Both sides are dereferenced first because `mergeNode` carries `$ref` over
|
|
406
|
+
* from the base, and `walk` dereferences whatever it is handed: an unresolved
|
|
407
|
+
* `$ref` on the result would resolve back to the base alone and discard the
|
|
408
|
+
* branch.
|
|
409
|
+
*/
|
|
410
|
+
function composeChild(base, branch) {
|
|
411
|
+
if (!base || !branch)
|
|
412
|
+
return base ?? branch;
|
|
413
|
+
return mergeNode(dereference(base), dereference(branch));
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* The `oneOf` branch the current data uniquely identifies, when the evaluator
|
|
417
|
+
* selected none.
|
|
418
|
+
*
|
|
419
|
+
* `oneOf` selects on full validity, so a branch the data plainly identifies
|
|
420
|
+
* stays unselected while one of its own required properties is absent, and
|
|
421
|
+
* hiding it leaves the user no way to supply the property that would make it
|
|
422
|
+
* apply. This picks that branch so a form can expose it, and the node it
|
|
423
|
+
* returns is reported `provisional` rather than `active`: JSON Schema still
|
|
424
|
+
* says the branch does not apply, and that fact is not this function's to
|
|
425
|
+
* overwrite.
|
|
426
|
+
*
|
|
427
|
+
* Deliberately narrow, because a form that guesses is worse than one that shows
|
|
428
|
+
* nothing:
|
|
429
|
+
*
|
|
430
|
+
* - only an explicit `const` or `enum` on a property discriminates, and both
|
|
431
|
+
* together are conjunctive rather than alternatives
|
|
432
|
+
* - a discriminator has to be present in the data by own-property presence,
|
|
433
|
+
* never read from a `default` annotation, since a declared default is not a
|
|
434
|
+
* value anyone supplied
|
|
435
|
+
* - a key discriminates only if every branch constrains it, so branches that
|
|
436
|
+
* simply differ in shape do not vote
|
|
437
|
+
* - every present discriminator has to agree on one branch
|
|
438
|
+
* - zero or several surviving branches select nothing
|
|
439
|
+
*
|
|
440
|
+
* `type` is excluded: it says almost nothing about intent when every branch is
|
|
441
|
+
* an object. `anyOf` is excluded because several of its branches may apply at
|
|
442
|
+
* once, which is a different question from which one the user means.
|
|
443
|
+
*
|
|
444
|
+
* Nothing else about a branch participates. A branch stays identified when some
|
|
445
|
+
* other constraint of its own fails, or selection would quietly become validity
|
|
446
|
+
* again and the field that completes the branch would stay hidden for a second
|
|
447
|
+
* reason.
|
|
448
|
+
*/
|
|
449
|
+
function selectProvisionalBranch(node, dataRecord) {
|
|
450
|
+
const branches = node.oneOf;
|
|
451
|
+
if (!branches || branches.length === 0 || dataRecord === undefined)
|
|
452
|
+
return undefined;
|
|
453
|
+
const resolvedBranches = branches.map(dereference);
|
|
454
|
+
const discriminators = [...discriminatorKeys(resolvedBranches)].filter((key) => Object.prototype.hasOwnProperty.call(dataRecord, key));
|
|
455
|
+
if (discriminators.length === 0)
|
|
456
|
+
return undefined;
|
|
457
|
+
const accepted = resolvedBranches.filter((branch) => discriminators.every((key) => branchAccepts(branch, key, dataRecord[key])));
|
|
458
|
+
return accepted.length === 1 ? accepted[0] : undefined;
|
|
459
|
+
}
|
|
460
|
+
/** Keys every branch constrains with `const` or `enum`. */
|
|
461
|
+
function discriminatorKeys(branches) {
|
|
462
|
+
const first = branches[0];
|
|
463
|
+
if (!first)
|
|
464
|
+
return new Set();
|
|
465
|
+
const shared = new Set(Object.keys(first.schema.properties ?? {}).filter((key) => isDiscriminator(first, key)));
|
|
466
|
+
for (const branch of branches.slice(1)) {
|
|
467
|
+
for (const key of [...shared]) {
|
|
468
|
+
if (!isDiscriminator(branch, key))
|
|
469
|
+
shared.delete(key);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return shared;
|
|
473
|
+
}
|
|
474
|
+
function discriminatorSchema(branch, key) {
|
|
475
|
+
const property = branch.properties?.[key];
|
|
476
|
+
if (!property)
|
|
477
|
+
return undefined;
|
|
478
|
+
return dereference(property).schema;
|
|
479
|
+
}
|
|
480
|
+
function isDiscriminator(branch, key) {
|
|
481
|
+
const schema = discriminatorSchema(branch, key);
|
|
482
|
+
if (!schema)
|
|
483
|
+
return false;
|
|
484
|
+
return 'const' in schema || Array.isArray(schema.enum);
|
|
485
|
+
}
|
|
486
|
+
/** `const` and `enum` are separate constraints, so a value has to satisfy both. */
|
|
487
|
+
function branchAccepts(branch, key, value) {
|
|
488
|
+
const schema = discriminatorSchema(branch, key);
|
|
489
|
+
if (!schema)
|
|
490
|
+
return false;
|
|
491
|
+
if ('const' in schema && !deepEqual(schema.const, value))
|
|
492
|
+
return false;
|
|
493
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((member) => deepEqual(member, value))) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
function deepEqual(a, b) {
|
|
499
|
+
if (a === b)
|
|
500
|
+
return true;
|
|
501
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
502
|
+
return a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
|
|
503
|
+
}
|
|
504
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
505
|
+
const left = Object.keys(a);
|
|
506
|
+
const right = Object.keys(b);
|
|
507
|
+
return (left.length === right.length &&
|
|
508
|
+
left.every((key) => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(a[key], b[key])));
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
function isPlainObject(value) {
|
|
513
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
514
|
+
}
|
|
123
515
|
/**
|
|
124
516
|
* Computes the currently-required property keys for an object node given its data.
|
|
125
517
|
*
|
|
@@ -158,35 +550,59 @@ function computeRequiredSet(resolved, reducedSchema, dataRecord) {
|
|
|
158
550
|
* NodeProjection a form can render, with `active` reflecting whether that branch is
|
|
159
551
|
* currently selected for `data`.
|
|
160
552
|
*/
|
|
161
|
-
function walk(node, pointer, data, active,
|
|
553
|
+
function walk(node, pointer, data, active,
|
|
554
|
+
/**
|
|
555
|
+
* Whether an ancestor's provisionally selected branch exposes this node. A
|
|
556
|
+
* node is never both: `active` is what the schema says, and this is what the
|
|
557
|
+
* projection selected so the user can complete it.
|
|
558
|
+
*/
|
|
559
|
+
provisional, nodes, diagnostics,
|
|
560
|
+
/**
|
|
561
|
+
* The schema positions that declare this location whatever the instance is,
|
|
562
|
+
* which `node` alone cannot supply: by the time the caller has a node to walk
|
|
563
|
+
* it holds the merge of them, and the merge is what loses a disagreement.
|
|
564
|
+
*/
|
|
565
|
+
declaredAt) {
|
|
162
566
|
const original = dereference(node);
|
|
163
567
|
const originalSchema = original.schema;
|
|
164
568
|
if (typeof originalSchema !== 'object' || originalSchema === null)
|
|
165
569
|
return;
|
|
166
570
|
let resolved = original;
|
|
167
571
|
let schema = originalSchema;
|
|
168
|
-
let type =
|
|
572
|
+
let type = resolveExplicitType(schema);
|
|
169
573
|
// Whether this node's own active branch could be determined. Stays true for every
|
|
170
574
|
// node except a typeless oneOf/anyOf wrapper whose branch could not be resolved
|
|
171
|
-
// (see below)
|
|
172
|
-
//
|
|
575
|
+
// (see below), where the node's own presence in the projection is the thing in
|
|
576
|
+
// doubt rather than only its children's.
|
|
173
577
|
let branchResolved = true;
|
|
174
578
|
// A node whose own schema carries no `type` keyword, only `oneOf`/`anyOf` branches
|
|
175
579
|
// (e.g. a property schema like `{ oneOf: [{ type: 'object', ... }, ...] }`), has no
|
|
176
580
|
// type until its active branch is resolved against `data`. `original` is kept
|
|
177
581
|
// separately so every branch's properties can still be collected below for the
|
|
178
582
|
// inactive-node contract, even though only the matching branch's schema is used here.
|
|
583
|
+
// Whether a branch of this node's own `oneOf`/`anyOf` was resolved against
|
|
584
|
+
// `data`. Recorded so an unresolved shape can be attributed correctly below:
|
|
585
|
+
// on this path, "no shape" means the current value matches no branch, which
|
|
586
|
+
// is a fact about the data rather than about the schema.
|
|
587
|
+
let composedAgainstData = false;
|
|
588
|
+
/**
|
|
589
|
+
* Whether a typeless wrapper's branch was picked by the discriminator rather
|
|
590
|
+
* than by the evaluator. The wrapper is then shown provisionally: the schema
|
|
591
|
+
* still applies no branch, and that is what `active` reports.
|
|
592
|
+
*/
|
|
593
|
+
let wrapperIdentified = false;
|
|
179
594
|
if (!type && (original.oneOf || original.anyOf)) {
|
|
595
|
+
composedAgainstData = true;
|
|
180
596
|
const { node: branchNode } = original.reduceNode(data);
|
|
181
597
|
if (branchNode) {
|
|
182
598
|
resolved = branchNode;
|
|
183
599
|
schema = branchNode.schema;
|
|
184
|
-
type =
|
|
600
|
+
type = resolveExplicitType(schema);
|
|
185
601
|
}
|
|
186
602
|
else {
|
|
187
603
|
// reduceNode() returned no node at all: this is oneOf's behavior when data
|
|
188
604
|
// matches zero or multiple branches (anyOf instead returns a node with an
|
|
189
|
-
// empty merged schema in that case, which
|
|
605
|
+
// empty merged schema in that case, which carries no explicit type either,
|
|
190
606
|
// but which does not reach this branch). This test suite's oneOf wrappers put
|
|
191
607
|
// an object schema on every branch, so 'object' is a safe stand-in type here:
|
|
192
608
|
// it lets this pointer and every candidate branch property still appear in the
|
|
@@ -195,11 +611,117 @@ function walk(node, pointer, data, active, nodes) {
|
|
|
195
611
|
// primitives (not objects) is not handled by this fallback.
|
|
196
612
|
type = 'object';
|
|
197
613
|
branchResolved = false;
|
|
614
|
+
// The wrapper's own branch could not be resolved, so the schema applies
|
|
615
|
+
// none of them. It may still be identifiable: a discriminator present in
|
|
616
|
+
// the data picks one, and the wrapper is then exposed provisionally
|
|
617
|
+
// rather than not at all. Without that, the same schema behaves
|
|
618
|
+
// differently for having declared `type` or not, since the typed path
|
|
619
|
+
// reaches the selector below and this one used to stop here.
|
|
620
|
+
//
|
|
621
|
+
// Only the flag is recorded, deliberately. Assigning the branch to
|
|
622
|
+
// `resolved` erases the difference between what the evaluator resolved
|
|
623
|
+
// and what this projection selected, and the object path reads `resolved`
|
|
624
|
+
// as the former: it would take the branch's `required` as active
|
|
625
|
+
// requiredness, find `nodeActive` false because no branch applies, and
|
|
626
|
+
// report the field as neither required nor provisionally required.
|
|
627
|
+
// Left alone, that path selects the same branch through
|
|
628
|
+
// `selectProvisionalBranch` and attributes it to the right one of the two.
|
|
629
|
+
//
|
|
630
|
+
// Gated on this node already being exposed, for the reason the object
|
|
631
|
+
// path is: selection is local, exposure is the ancestor's to grant.
|
|
632
|
+
wrapperIdentified =
|
|
633
|
+
(active || provisional) &&
|
|
634
|
+
selectProvisionalBranch(original, typeof data === 'object' && data !== null ? data : undefined) !== undefined;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Whether the absence of a shape here is only the current value's fault.
|
|
639
|
+
*
|
|
640
|
+
* Two conditions, and both are needed. Nothing was merged in from a branch,
|
|
641
|
+
* which is what reducing against a value that satisfies none of them leaves
|
|
642
|
+
* behind; and some branch could have been rendered for a value that did
|
|
643
|
+
* satisfy it, so the composition is not unrenderable in itself.
|
|
644
|
+
*
|
|
645
|
+
* The first condition is what a coarser check missed. Entering the
|
|
646
|
+
* composition path says nothing on its own: a branch can be selected and
|
|
647
|
+
* still supply no shape, as `{ anyOf: [{ minLength: 1 }, { pattern: '^a' }] }`
|
|
648
|
+
* does for `"abc"`, where both branches match and neither describes anything
|
|
649
|
+
* this adapter renders, because scalar shapes are deliberately not inferred.
|
|
650
|
+
* That schema is genuinely unprojectable and has to be reported.
|
|
651
|
+
*/
|
|
652
|
+
const isTransientBranchMiss = () => Object.keys(schema).length === 0 && compositionHasRenderableAlternative(original);
|
|
653
|
+
// Last resort, after the oneOf/anyOf branch above has had its chance: that
|
|
654
|
+
// path handles a typeless wrapper whose type only exists once a branch is
|
|
655
|
+
// chosen, and inferring first would take a schema carrying both `properties`
|
|
656
|
+
// and `oneOf` down the object path without ever resolving its branch.
|
|
657
|
+
if (!type) {
|
|
658
|
+
const shape = inferProjectionShape(schema);
|
|
659
|
+
if (shape.kind === 'resolved') {
|
|
660
|
+
type = shape.type;
|
|
661
|
+
}
|
|
662
|
+
else if (shape.kind === 'none' && composedAgainstData && isTransientBranchMiss()) {
|
|
663
|
+
// Deliberately silent, and only for this one case. A projection
|
|
664
|
+
// diagnostic describes a schema this adapter cannot turn into a shape,
|
|
665
|
+
// and a value that matches none of a composition's branches is not that:
|
|
666
|
+
// the same schema renders for a value that does match one. That is
|
|
667
|
+
// validation's subject, it is already reported there, and a form's data
|
|
668
|
+
// is in this state for most of the time someone is filling it in, so a
|
|
669
|
+
// diagnostic here would appear and disappear on each keystroke and train
|
|
670
|
+
// a caller to ignore the channel.
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
// Reported rather than dropped in silence. The field cannot be drawn
|
|
674
|
+
// without a shape, so the caller is told which pointer was skipped and
|
|
675
|
+
// why, instead of finding out from a form that never collected the
|
|
676
|
+
// value.
|
|
677
|
+
diagnostics.push(shape.kind === 'ambiguous'
|
|
678
|
+
? {
|
|
679
|
+
pointer: toPointer(pointer),
|
|
680
|
+
code: 'ambiguous-projection-shape',
|
|
681
|
+
message: `No explicit "type", and keywords from more than one type apply ` +
|
|
682
|
+
`(${shape.families.join(', ')}), so the shape to render is undecidable. ` +
|
|
683
|
+
`Declare "type" on this schema to resolve it.`,
|
|
684
|
+
}
|
|
685
|
+
: {
|
|
686
|
+
pointer: toPointer(pointer),
|
|
687
|
+
code: 'unresolved-projection-shape',
|
|
688
|
+
message: `No explicit "type", and no keyword that implies one, so there is no ` +
|
|
689
|
+
`shape to render. Declare "type" on this schema.` +
|
|
690
|
+
// Said only where it applies, because inferring `string` from
|
|
691
|
+
// an enum is the tempting wrong rule and the reason deserves
|
|
692
|
+
// to travel with the case rather than every message.
|
|
693
|
+
(schema.enum !== undefined
|
|
694
|
+
? ` An "enum" alone does not imply a type, because its members may be of different types.`
|
|
695
|
+
: ''),
|
|
696
|
+
});
|
|
198
697
|
}
|
|
199
698
|
}
|
|
200
699
|
if (!type)
|
|
201
700
|
return;
|
|
701
|
+
// Reported at the location it is about, and after the shape gate above: a
|
|
702
|
+
// pointer with no node has nothing to omit an annotation from, and saying
|
|
703
|
+
// that its `default` is undecidable on top of saying it cannot be drawn at
|
|
704
|
+
// all would be the same schema reported twice.
|
|
705
|
+
const ambiguousDefault = disagreeingDefaults(collectUnconditionalDefaults(declaredAt));
|
|
706
|
+
if (ambiguousDefault) {
|
|
707
|
+
diagnostics.push({
|
|
708
|
+
pointer: toPointer(pointer),
|
|
709
|
+
code: 'ambiguous-default',
|
|
710
|
+
message: `${ambiguousDefault.length} "default" declarations apply here whatever the ` +
|
|
711
|
+
`instance is, and they disagree, so there is no value to report. Declare one ` +
|
|
712
|
+
`of them, or make them equal.`,
|
|
713
|
+
sources: ambiguousDefault.map((declaration) => declaration.source),
|
|
714
|
+
});
|
|
715
|
+
}
|
|
202
716
|
const nodeActive = active && branchResolved;
|
|
717
|
+
// A node the schema applies is never also provisional; the two report
|
|
718
|
+
// different facts and only the second is a choice this projection made. The
|
|
719
|
+
// `!nodeActive` term is belt and braces: `provisional` only ever arrives true
|
|
720
|
+
// from a parent that computed `!childActive`, so no caller can reach here
|
|
721
|
+
// with both, and dropping the term changes no observable behaviour.
|
|
722
|
+
const nodeProvisional = !nodeActive && (provisional || wrapperIdentified) && (branchResolved || wrapperIdentified);
|
|
723
|
+
// Whether a form shows this node at all, which is what a descendant inherits.
|
|
724
|
+
const nodeExposed = nodeActive || nodeProvisional;
|
|
203
725
|
if (type === 'object') {
|
|
204
726
|
const dataRecord = typeof data === 'object' && data !== null ? data : undefined;
|
|
205
727
|
// reduceNode() resolves if/then/else, dependentSchemas (which also covers
|
|
@@ -222,17 +744,45 @@ function walk(node, pointer, data, active, nodes) {
|
|
|
222
744
|
{};
|
|
223
745
|
const activeKeys = new Set(Object.keys(reducedProperties));
|
|
224
746
|
const requiredSet = computeRequiredSet(resolved, reducedSchema, dataRecord);
|
|
747
|
+
// Two conditions, and they are not the same kind of condition.
|
|
748
|
+
//
|
|
749
|
+
// Exposure is load-bearing. Selection is local, but whether this node is
|
|
750
|
+
// shown at all is its ancestors' to decide, and a subtree nothing exposes
|
|
751
|
+
// must not select a branch off data it happens to retain: the compiler
|
|
752
|
+
// collapses `required || provisionalRequired` into the one flag a binding
|
|
753
|
+
// reads, so a hidden field would acquire a requirement.
|
|
754
|
+
//
|
|
755
|
+
// The evaluator having selected nothing is where provisional selection
|
|
756
|
+
// exists to help, and that half is unobservable rather than wrong: a
|
|
757
|
+
// resolved branch is fully valid, so it accepts every present discriminator
|
|
758
|
+
// and the selector would return that same branch, whose keys are already
|
|
759
|
+
// the active set. Kept because doing the work is pointless and the
|
|
760
|
+
// condition states the intent.
|
|
761
|
+
const provisionalBranch = nodeExposed && reducedNode === undefined
|
|
762
|
+
? selectProvisionalBranch(original, dataRecord)
|
|
763
|
+
: undefined;
|
|
764
|
+
const provisionalSchema = provisionalBranch?.schema;
|
|
765
|
+
const provisionalKeys = new Set(Object.keys(provisionalSchema?.properties ?? {}));
|
|
766
|
+
const provisionalRequired = new Set(Array.isArray(provisionalSchema?.required) ? provisionalSchema.required : []);
|
|
225
767
|
// Candidates are collected from `original`, not `resolved`, so every oneOf/anyOf
|
|
226
768
|
// branch's properties are represented (the matching branch alone, via `resolved`,
|
|
227
769
|
// would only expose its own properties).
|
|
228
770
|
const candidateProps = collectCandidateProperties(original);
|
|
229
|
-
const propKeys =
|
|
771
|
+
const propKeys = [...candidateProps.keys()];
|
|
230
772
|
const children = propKeys.length > 0
|
|
231
|
-
? propKeys.map((key) =>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
773
|
+
? propKeys.map((key) => {
|
|
774
|
+
// Gated on the node applying at all. A branch that does not apply
|
|
775
|
+
// demands nothing, so reporting its `required` array as a
|
|
776
|
+
// requirement would attribute to the validator something it is not
|
|
777
|
+
// asking for.
|
|
778
|
+
const required = nodeActive && requiredSet.has(key);
|
|
779
|
+
return {
|
|
780
|
+
pointer: toPointer(`${pointer}/${escapeSegment(key)}`),
|
|
781
|
+
key,
|
|
782
|
+
required,
|
|
783
|
+
provisionalRequired: !required && provisionalRequired.has(key) ? true : undefined,
|
|
784
|
+
};
|
|
785
|
+
})
|
|
236
786
|
: undefined;
|
|
237
787
|
nodes.set(toPointer(pointer), {
|
|
238
788
|
type,
|
|
@@ -241,14 +791,33 @@ function walk(node, pointer, data, active, nodes) {
|
|
|
241
791
|
children,
|
|
242
792
|
enumValues: extractEnumValues(schema),
|
|
243
793
|
active: nodeActive,
|
|
244
|
-
|
|
794
|
+
provisional: nodeProvisional ? true : undefined,
|
|
795
|
+
annotations: extractAnnotations(schema, ambiguousDefault !== undefined),
|
|
245
796
|
});
|
|
246
797
|
for (const key of propKeys) {
|
|
247
798
|
const childPointer = `${pointer}/${escapeSegment(key)}`;
|
|
248
799
|
const childActive = nodeActive && activeKeys.has(key);
|
|
800
|
+
// A descendant inherits exposure, not activity: under a provisionally
|
|
801
|
+
// selected ancestor its own locally applicable properties are
|
|
802
|
+
// provisional too, while a locally inactive one stays inactive.
|
|
803
|
+
const childProvisional = !childActive && nodeExposed && (activeKeys.has(key) || provisionalKeys.has(key));
|
|
249
804
|
const reducedChildNode = reducedNode?.properties?.[key];
|
|
250
|
-
|
|
251
|
-
|
|
805
|
+
// Precedence matters as much as the selection does. Marking the right
|
|
806
|
+
// branch provisional while taking its shape from `candidatePrototype`,
|
|
807
|
+
// which is first-wins across branches, would render another branch's
|
|
808
|
+
// widget and annotations under the selected branch's name, and would hand
|
|
809
|
+
// ADR-003's pass another branch's `default`.
|
|
810
|
+
const provisionalChildNode = provisionalBranch?.properties?.[key];
|
|
811
|
+
const candidate = candidateProps.get(key);
|
|
812
|
+
// Three states, in order of authority. What the evaluator reduced against
|
|
813
|
+
// the data is the schema as it actually applies. Failing that, the node's
|
|
814
|
+
// own declaration composed with the selected branch, which is what the
|
|
815
|
+
// reduction would have produced had the branch been complete. Failing
|
|
816
|
+
// both, a stated placeholder for a location no branch has claimed.
|
|
817
|
+
const childNode = reducedChildNode ??
|
|
818
|
+
composeChild(candidate.direct, provisionalChildNode) ??
|
|
819
|
+
candidatePrototype(candidate);
|
|
820
|
+
walk(childNode, childPointer, dataRecord?.[key], childActive, childProvisional, nodes, diagnostics, unconditionalChildren([original], key));
|
|
252
821
|
}
|
|
253
822
|
return;
|
|
254
823
|
}
|
|
@@ -259,14 +828,22 @@ function walk(node, pointer, data, active, nodes) {
|
|
|
259
828
|
children: undefined,
|
|
260
829
|
enumValues: extractEnumValues(schema),
|
|
261
830
|
active: nodeActive,
|
|
262
|
-
|
|
831
|
+
provisional: nodeProvisional ? true : undefined,
|
|
832
|
+
annotations: extractAnnotations(schema, ambiguousDefault !== undefined),
|
|
833
|
+
// Deliberately not given the flag above. A conflict under `items` belongs
|
|
834
|
+
// to the element locations, and each of those reports its own; this field
|
|
835
|
+
// describes the item schema rather than being one of those locations, so
|
|
836
|
+
// omitting its `default` would be an omission no pointer could report. It
|
|
837
|
+
// is also the only annotation set nothing reads a `default` from, and the
|
|
838
|
+
// only one the other adapter does not produce at all.
|
|
263
839
|
itemAnnotations: type === 'array' && resolved.items
|
|
264
840
|
? extractAnnotations(dereference(resolved.items).schema)
|
|
265
841
|
: undefined,
|
|
266
842
|
});
|
|
267
843
|
if (type === 'array' && resolved.items && Array.isArray(data)) {
|
|
844
|
+
const itemPositions = unconditionalItems([original]);
|
|
268
845
|
data.forEach((item, index) => {
|
|
269
|
-
walk(resolved.items, `${pointer}/${index}`, item, nodeActive, nodes);
|
|
846
|
+
walk(resolved.items, `${pointer}/${index}`, item, nodeActive, nodeProvisional, nodes, diagnostics, itemPositions);
|
|
270
847
|
});
|
|
271
848
|
}
|
|
272
849
|
}
|
|
@@ -282,7 +859,8 @@ function walk(node, pointer, data, active, nodes) {
|
|
|
282
859
|
*/
|
|
283
860
|
export function buildProjection(root, data) {
|
|
284
861
|
const nodes = new Map();
|
|
285
|
-
|
|
286
|
-
|
|
862
|
+
const diagnostics = [];
|
|
863
|
+
walk(root, '', data, true, false, nodes, diagnostics, [root]);
|
|
864
|
+
return { nodes, diagnostics };
|
|
287
865
|
}
|
|
288
866
|
//# sourceMappingURL=projection.js.map
|