ata-validator 1.52.0 → 1.54.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/lib/keywords.js CHANGED
@@ -10,6 +10,12 @@
10
10
  // { macro(value, parent) -> schema } the returned schema is applied in
11
11
  // place, and the keyword reports an
12
12
  // error of its own when it fails
13
+ // with optional `dataContext: true`: the per-value function is also given
14
+ // `{ instancePath, parentData, parentDataProperty, rootData }`, as the default
15
+ // validator's keywords are, so it can read or change the value where it sits
16
+ // (validate(value, data, parent, ctx), or the compiled fn(data, ctx)). Such a
17
+ // schema runs on the interpreted engine's collecting walk, which knows where
18
+ // every value is.
13
19
  // with optional `type`: a JSON Schema type name or a list of them. A typed
14
20
  // keyword is skipped for data of any other type, which is what makes
15
21
  // `{ type: 'number', ... }` safe next to `type: ['number', 'string']`.
@@ -72,6 +78,12 @@ function normalizeKeywords(defs) {
72
78
  validate: hasValidate ? def.validate : null,
73
79
  compile: hasCompile ? def.compile : null,
74
80
  macro: hasMacro ? def.macro : null,
81
+ dataContext: def.dataContext === true,
82
+ // Order among the custom keywords of one node, as the default
83
+ // validator's keywords declare it: `before` names a keyword this one
84
+ // runs ahead of, `post` runs it after the others.
85
+ before: typeof def.before === 'string' ? def.before : null,
86
+ post: def.post === true,
75
87
  };
76
88
  }
77
89
  return out;
@@ -110,4 +122,15 @@ const NAME_MAPS = new Set([
110
122
  'dependentSchemas', 'dependentRequired', 'dependencies', 'propertyDependencies',
111
123
  ]);
112
124
 
113
- module.exports = { normalizeKeywords, schemaUsesKeywords };
125
+ // Does the schema use a registered keyword that asks for its data's context?
126
+ function schemaUsesDataContext(schema, keywords) {
127
+ if (!keywords) return false;
128
+ let any = false;
129
+ for (const name of Object.keys(keywords)) if (keywords[name].dataContext) { any = true; break; }
130
+ if (!any) return false;
131
+ const only = Object.create(null);
132
+ for (const name of Object.keys(keywords)) if (keywords[name].dataContext) only[name] = keywords[name];
133
+ return schemaUsesKeywords(schema, only);
134
+ }
135
+
136
+ module.exports = { normalizeKeywords, schemaUsesKeywords, schemaUsesDataContext };
@@ -1,20 +1,25 @@
1
1
  'use strict';
2
2
 
3
- // Bounded Levenshtein. Returns Infinity if distance > maxDistance (or, past
3
+ // Bounded edit distance. Returns Infinity if distance > maxDistance (or, past
4
4
  // the bound, possibly some number greater than it; callers compare).
5
5
  //
6
+ // Two adjacent characters swapped count as one edit, not two (the optimal
7
+ // string alignment distance): `nmae` for `name` is the commonest typo there
8
+ // is, and at two edits a four-letter name's budget of one turned it away.
9
+ //
6
10
  // Only the cells within the bound of the diagonal are computed: a path
7
11
  // through any other cell already costs more than the bound. A typo hint asks
8
12
  // with a bound of one or two and compares the missing name with every key
9
13
  // present, so a row is five cells instead of the whole key. Without a bound
10
14
  // the band is the whole row.
11
15
  //
12
- // Two rows of scratch, reused across calls. The function is not recursive
16
+ // Three rows of scratch, reused across calls. The function is not recursive
13
17
  // and the library is single-threaded, so nothing else is using them; growing
14
18
  // them once beats allocating two arrays on every comparison, which the
15
19
  // reject-path profile showed as most of this function's cost.
16
20
  let scratchA = new Int32Array(64);
17
21
  let scratchB = new Int32Array(64);
22
+ let scratchC = new Int32Array(64);
18
23
 
19
24
  function levenshtein (a, b, maxDistance) {
20
25
  if (a === b) return 0;
@@ -28,7 +33,11 @@ function levenshtein (a, b, maxDistance) {
28
33
  if (scratchA.length < m + 2) {
29
34
  scratchA = new Int32Array(m + 2);
30
35
  scratchB = new Int32Array(m + 2);
36
+ scratchC = new Int32Array(m + 2);
31
37
  }
38
+ // The row before the previous one, read for a swap: within the band of
39
+ // row i the cell two back on the diagonal is inside the band of row i - 2.
40
+ let prev2 = scratchC;
32
41
  let prev = scratchA;
33
42
  let curr = scratchB;
34
43
  for (let j = 0; j <= m; j++) prev[j] = j <= max ? j : big;
@@ -40,20 +49,27 @@ function levenshtein (a, b, maxDistance) {
40
49
  // charCodeAt rather than indexing: reading a character by index allocates
41
50
  // a one-character string, and this is the innermost loop.
42
51
  const ca = a.charCodeAt(i - 1);
52
+ const pa = i > 1 ? a.charCodeAt(i - 2) : -1;
43
53
  for (let j = lo; j <= hi; j++) {
44
- let v = prev[j - 1] + (ca === b.charCodeAt(j - 1) ? 0 : 1);
54
+ const cb = b.charCodeAt(j - 1);
55
+ let v = prev[j - 1] + (ca === cb ? 0 : 1);
45
56
  const del = curr[j - 1] + 1;
46
57
  if (del < v) v = del;
47
58
  const ins = prev[j] + 1;
48
59
  if (ins < v) v = ins;
60
+ if (j > 1 && ca !== cb && ca === b.charCodeAt(j - 2) && pa === cb) {
61
+ const swap = prev2[j - 2] + 1;
62
+ if (swap < v) v = swap;
63
+ }
49
64
  if (v > big) v = big;
50
65
  curr[j] = v;
51
66
  if (v < rowMin) rowMin = v;
52
67
  }
53
68
  if (hi < m) curr[hi + 1] = big;
54
69
  if (rowMin > max) return Infinity;
55
- // A destructured swap builds an array per row; a temporary does not.
56
- const t = prev;
70
+ // A destructured swap builds an array per row; temporaries do not.
71
+ const t = prev2;
72
+ prev2 = prev;
57
73
  prev = curr;
58
74
  curr = t;
59
75
  }
@@ -2,11 +2,12 @@
2
2
 
3
3
  // The official meta-schemas, vendored so a `$ref` to one of them resolves
4
4
  // without a network. Draft 2020-12 is eight documents joined by $dynamicRef;
5
- // draft-07 and draft-06 are one each; 2019-09 is seven. Sources:
5
+ // draft-07, draft-06 and draft-04 are one each; 2019-09 is seven. Sources:
6
6
  // https://json-schema.org/draft/2020-12/schema and
7
- // http://json-schema.org/draft-07/schema, fetched 2026-08-23, and
7
+ // http://json-schema.org/draft-07/schema, fetched 2026-08-23,
8
8
  // http://json-schema.org/draft-06/schema and
9
- // https://json-schema.org/draft/2019-09/schema, fetched 2026-10-10, unmodified.
9
+ // https://json-schema.org/draft/2019-09/schema, fetched 2026-10-10, and
10
+ // http://json-schema.org/draft-04/schema, fetched 2026-10-14, unmodified.
10
11
  // The 2019-09 documents are read through the same 2019-09 rewrite as a
11
12
  // caller's schema (lib/draft7.js) once, at load, since they use
12
13
  // `$recursiveRef`, which the engines read as `$dynamicRef`.
@@ -22,6 +23,7 @@ const METASCHEMAS = new Map([
22
23
  ["https://json-schema.org/draft/2020-12/meta/content", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/content","$dynamicAnchor":"meta","title":"Content vocabulary meta-schema","type":["object","boolean"],"properties":{"contentEncoding":{"type":"string"},"contentMediaType":{"type":"string"},"contentSchema":{"$dynamicRef":"#meta"}}}],
23
24
  ["https://json-schema.org/draft/2020-12/meta/unevaluated", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/unevaluated","$dynamicAnchor":"meta","title":"Unevaluated applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"unevaluatedItems":{"$dynamicRef":"#meta"},"unevaluatedProperties":{"$dynamicRef":"#meta"}}}],
24
25
  ["http://json-schema.org/draft-07/schema#", {"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}],
26
+ ["http://json-schema.org/draft-04/schema#", {"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}],
25
27
  ["http://json-schema.org/draft-06/schema#", {"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}],
26
28
  ["https://json-schema.org/draft/2019-09/schema", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/schema","$vocabulary":{"https://json-schema.org/draft/2019-09/vocab/core":true,"https://json-schema.org/draft/2019-09/vocab/applicator":true,"https://json-schema.org/draft/2019-09/vocab/validation":true,"https://json-schema.org/draft/2019-09/vocab/meta-data":true,"https://json-schema.org/draft/2019-09/vocab/format":false,"https://json-schema.org/draft/2019-09/vocab/content":true},"$recursiveAnchor":true,"title":"Core and Validation specifications meta-schema","allOf":[{"$ref":"meta/core"},{"$ref":"meta/applicator"},{"$ref":"meta/validation"},{"$ref":"meta/meta-data"},{"$ref":"meta/format"},{"$ref":"meta/content"}],"type":["object","boolean"],"properties":{"definitions":{"$comment":"While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.","type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}},"dependencies":{"$comment":"\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"","type":"object","additionalProperties":{"anyOf":[{"$recursiveRef":"#"},{"$ref":"meta/validation#/$defs/stringArray"}]}}}}],
27
29
  ["https://json-schema.org/draft/2019-09/meta/core", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/core","$recursiveAnchor":true,"title":"Core vocabulary meta-schema","type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference","$comment":"Non-empty fragments not allowed.","pattern":"^[^#]*#?$"},"$schema":{"type":"string","format":"uri"},"$anchor":{"type":"string","pattern":"^[A-Za-z][-A-Za-z0-9.:_]*$"},"$ref":{"type":"string","format":"uri-reference"},"$recursiveRef":{"type":"string","format":"uri-reference"},"$recursiveAnchor":{"type":"boolean","default":false},"$vocabulary":{"type":"object","propertyNames":{"type":"string","format":"uri"},"additionalProperties":{"type":"boolean"}},"$comment":{"type":"string"},"$defs":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}}}}],
@@ -213,11 +213,18 @@ function install(deps) {
213
213
  const objs = [];
214
214
  if (P.required !== null) {
215
215
  const req = P.required;
216
- if (req.length === 1) { const k = req[0]; objs.push((d) => Object.hasOwn(d, k)); }
217
- else objs.push((d) => {
216
+ // A required key holding undefined is missing; for a declared
217
+ // property the property step finds it (requiredInProps).
218
+ const undef = P.requiredUndef;
219
+ if (req.length === 1) { const k = req[0]; objs.push(undef !== null && undef[0] ? (d) => Object.hasOwn(d, k) && d[k] !== undefined : (d) => Object.hasOwn(d, k)); }
220
+ else if (undef === null) objs.push((d) => {
218
221
  for (let i = 0; i < req.length; i++) if (!Object.hasOwn(d, req[i])) return false;
219
222
  return true;
220
223
  });
224
+ else objs.push((d) => {
225
+ for (let i = 0; i < req.length; i++) if (!Object.hasOwn(d, req[i]) || (undef[i] && d[req[i]] === undefined)) return false;
226
+ return true;
227
+ });
221
228
  }
222
229
  if (P.minProperties !== undefined) { const m = P.minProperties; objs.push((d) => Object.keys(d).length >= m); }
223
230
  if (P.maxProperties !== undefined) { const m = P.maxProperties; objs.push((d) => Object.keys(d).length <= m); }
@@ -490,8 +497,10 @@ function install(deps) {
490
497
  // Declared properties: a linear scan over a short list beats a Map
491
498
  // lookup, and most schemas declare a handful.
492
499
  const propKeys = props !== null ? [...props.keys()] : null;
500
+ // req: the key is also required, so holding undefined it is missing.
501
+ const reqIn = P.requiredInProps;
493
502
  const propList = props !== null
494
- ? [...props.values()].map((entry) => ({ fn: child(entry.node), seg: entry.seg, schemaSeg: entry.schemaSeg }))
503
+ ? [...props].map(([key, entry]) => ({ fn: child(entry.node), seg: entry.seg, schemaSeg: entry.schemaSeg, req: reqIn !== null && reqIn.has(key) }))
495
504
  : null;
496
505
  const propMap = props !== null && props.size > 8 ? new Map(propKeys.map((k, i) => [k, propList[i]])) : null;
497
506
  const lookup = propMap !== null
@@ -532,10 +541,19 @@ function install(deps) {
532
541
  const key = keys[k];
533
542
  let evaluated = false;
534
543
  const prop = lookup !== null ? lookup(key) : undefined;
544
+ // A declared property whose value is undefined is not checked (see
545
+ // the interpreter's eval); required, it is missing.
535
546
  if (prop !== undefined) {
536
- if (!((prop.fn.leaf && prop.fn.v(data[key], stack)) || prop.fn.c(data[key], errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack))) {
547
+ const value = data[key];
548
+ if (value !== undefined) {
549
+ if (!((prop.fn.leaf && prop.fn.v(value, stack)) || prop.fn.c(value, errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack))) {
550
+ ok = false;
551
+ if (errors === NOERRORS) return false;
552
+ }
553
+ } else if (prop.req) {
537
554
  ok = false;
538
555
  if (errors === NOERRORS) return false;
556
+ errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`));
539
557
  }
540
558
  evaluated = true;
541
559
  }
@@ -578,7 +596,9 @@ function install(deps) {
578
596
  for (let i = 0; i < n; i++) {
579
597
  const key = propKeys[i];
580
598
  if (!Object.hasOwn(data, key)) continue;
581
- if (!propList[i].fn.v(data[key], stack)) return false;
599
+ const value = data[key];
600
+ if (value !== undefined) { if (!propList[i].fn.v(value, stack)) return false; }
601
+ else if (propList[i].req) return false;
582
602
  if (collect) addProp(rec, key);
583
603
  }
584
604
  return true;
@@ -594,7 +614,9 @@ function install(deps) {
594
614
  let evaluated = false;
595
615
  const prop = lookup !== null ? lookup(key) : undefined;
596
616
  if (prop !== undefined) {
597
- if (!prop.fn.v(data[key], stack)) return false;
617
+ const value = data[key];
618
+ if (value !== undefined) { if (!prop.fn.v(value, stack)) return false; }
619
+ else if (prop.req) return false;
598
620
  evaluated = true;
599
621
  }
600
622
  if (patterns !== null) {
@@ -210,6 +210,86 @@ function deterministicRule (ast) {
210
210
  return unbounded === 0 || anchored(ast)
211
211
  }
212
212
 
213
+ // Two more shapes the rules above decline, both anchored with `^` and `$`
214
+ // around one top-level sequence.
215
+ //
216
+ // One choice: characters and classes, each repeated a fixed or bounded number
217
+ // of times, with only one of the repeats able to vary, by at most 256
218
+ // (`^[a-z0-9_-]{1,128}$`).
219
+ // The engine backtracks through that one repeat and nowhere else, at most its
220
+ // width per attempt, and the anchor allows one attempt.
221
+ //
222
+ // A delimited loop: `^P(Xd)*Q$` or with `+`, where d is one character or
223
+ // class, X and Q match nothing d matches (Q may end with d, or an optional
224
+ // d), and P holds no unbounded quantifier. Dotted identifiers and paths are
225
+ // written so: `^([A-Za-z_$][\w$]*\.)*[A-Za-z_$][\w$]*$`, `^/?([^/]+/)*[^/]+/?$`.
226
+ // Every iteration and Q's match end at a d or at the end, so each wrong
227
+ // choice between another iteration and Q fails within one segment, and the
228
+ // segments together are the input once. X and Q each hold at most one
229
+ // unbounded quantifier, over a character or class, so a segment is not split
230
+ // two ways either.
231
+ function sequenceOf (ast) {
232
+ if (ast.t !== 'concat') return null
233
+ const parts = ast.parts
234
+ if (parts.length < 3 || parts[0].t !== 'bol' || parts[parts.length - 1].t !== 'eol') return null
235
+ return parts.slice(1, -1)
236
+ }
237
+ const repeatOfAtom = (n) => (n.t === 'star' || n.t === 'plus' || n.t === 'quest' || n.t === 'repeat') && atom(n.child)
238
+ const unboundedNode = (n) => n.t === 'star' || n.t === 'plus' || (n.t === 'repeat' && n.max === Infinity)
239
+
240
+ function oneChoiceRule (ast) {
241
+ const seq = sequenceOf(ast)
242
+ if (seq === null) return false
243
+ let varying = 0
244
+ for (const n of seq) {
245
+ if (atom(n)) continue
246
+ if (n.t === 'repeat' && atom(n.child) && n.max !== Infinity) {
247
+ if (n.max > 256) return false
248
+ if (n.max > n.min) varying++
249
+ continue
250
+ }
251
+ return false
252
+ }
253
+ return varying <= 1
254
+ }
255
+
256
+ function delimitedLoopRule (ast) {
257
+ const seq = sequenceOf(ast)
258
+ if (seq === null) return false
259
+ const loopAt = seq.findIndex((n) => unboundedNode(n) && !atom(n.child))
260
+ if (loopAt < 0) return false
261
+ let body = seq[loopAt].child
262
+ while (body.t === 'group') body = body.child
263
+ if (body.t !== 'concat' || body.parts.length < 2) return false
264
+ const d = body.parts[body.parts.length - 1]
265
+ if (!atom(d)) return false
266
+ const D = charSet(d)
267
+ // A run of characters, classes and their repeats that matches nothing in
268
+ // D, with at most one unbounded quantifier.
269
+ const run = (nodes) => {
270
+ let unbounded = 0
271
+ for (const n of nodes) {
272
+ const a = atom(n) ? n : repeatOfAtom(n) ? n.child : null
273
+ if (a === null || overlap(charSet(a), D)) return false
274
+ if (unboundedNode(n)) unbounded++
275
+ if (n.t === 'repeat' && n.max !== Infinity && n.max - n.min > 64) return false
276
+ }
277
+ return unbounded <= 1
278
+ }
279
+ if (!run(body.parts.slice(0, -1))) return false
280
+ const prefix = seq.slice(0, loopAt)
281
+ if (prefix.some(hasUnbounded)) return false
282
+ const pb = simpleRule({ t: 'concat', parts: prefix })
283
+ if (!pb && prefix.length > 0) return false
284
+ let suffix = seq.slice(loopAt + 1)
285
+ const last = suffix[suffix.length - 1]
286
+ if (last !== undefined) {
287
+ const tail = atom(last) ? last : last.t === 'quest' && atom(last.child) ? last.child : null
288
+ if (tail !== null && !overlap(charSet(tail), { bits: D.bits.map((b) => b ^ 1), high: D.high ? 0 : 1 })) suffix = suffix.slice(0, -1)
289
+ }
290
+ return run(suffix)
291
+ }
292
+
213
293
  function nativeIsLinear (src) {
214
294
  if (typeof src !== 'string' || src.length > 512) return false
215
295
  for (let i = 0; i < src.length; i++) {
@@ -225,7 +305,7 @@ function nativeIsLinear (src) {
225
305
  }
226
306
  let ast
227
307
  try { ast = parse(src) } catch { return false }
228
- return simpleRule(ast) || deterministicRule(ast)
308
+ return simpleRule(ast) || deterministicRule(ast) || oneChoiceRule(ast) || delimitedLoopRule(ast)
229
309
  }
230
310
 
231
311
  module.exports = { nativeIsLinear }
package/lib/safe-regex.js CHANGED
@@ -473,25 +473,28 @@ function __ataSafeRegex () {
473
473
  }
474
474
  if (id < 0) return testNFA(s)
475
475
  if (len === 0) return (flags[id] & 1) === 1
476
- // The last character is stepped with the end-of-input flag set and is
477
- // handled after the loop, so the loop body selects no table and tests
478
- // no position.
476
+ if ((flags[id] & 1) === 1) return true
477
+ // The last character, with the end flag, is stepped after the loop.
478
+ // Tables are locals, reread after `step` (which may grow them).
479
479
  const last = len - 1
480
+ let T = trans
481
+ let F = flags
480
482
  for (let pos = 0; pos < last; pos++) {
481
- if ((flags[id] & 1) === 1) return true
482
483
  const c = s.charCodeAt(pos)
483
- let nid
484
- if (c < 128) {
485
- nid = trans[(id << 7) | c]
486
- if (nid === -2) { nid = step(states[id], c, false); if (nid >= 0 || overflow) trans[(id << 7) | c] = nid }
487
- } else {
484
+ let nid = c < 128 ? T[(id << 7) | c] : -2
485
+ if (nid === -2) {
488
486
  nid = step(states[id], c, false)
487
+ T = trans; F = flags
488
+ if (c < 128 && (nid >= 0 || overflow)) T[(id << 7) | c] = nid
489
489
  }
490
490
  if (nid < 0) return testNFA(s)
491
491
  id = nid
492
- if ((flags[id] & 2) === 2) return false
492
+ const f = F[id]
493
+ if (f !== 0) {
494
+ if ((f & 2) === 2) return false
495
+ return true
496
+ }
493
497
  }
494
- if ((flags[id] & 1) === 1) return true
495
498
  const c = s.charCodeAt(last)
496
499
  let nid
497
500
  if (c < 128) {
@@ -30,6 +30,11 @@ const TUPLE_ITEMS = 1 << 5; // array-valued `items`, split into `prefixItem
30
30
  const EXCLUSIVE_BOOL = 1 << 6; // draft-04's boolean `exclusiveMinimum`/`exclusiveMaximum`
31
31
  const ENCODED_REF = 1 << 7; // a fragment `$ref` holding a percent escape, decoded by normalizeNullable's walk
32
32
 
33
+ // Questions compat asks of a schema it is about to compile, read on the same
34
+ // walk so that a document is not walked again for them. compat sets the keys
35
+ // and their bits (setExtraKeys); without compat the walk reads none.
36
+ let EXTRA = null;
37
+
33
38
  // Everything only draft-07 normalization acts on. A schema of another dialect
34
39
  // can carry these without them meaning anything, so they are read together
35
40
  // with whether the document is draft-07 at all.
@@ -53,7 +58,7 @@ const ANCHOR_ID_RE = /^#[A-Za-z][A-Za-z0-9_.:-]*$/;
53
58
  // This assumes a schema is not mutated after being handed to a Validator,
54
59
  // which is already true of everything else here: the compile cache, the
55
60
  // identity cache and the schema map would all be stale too.
56
- const CACHE = new WeakMap();
61
+ let CACHE = new WeakMap();
57
62
 
58
63
  function scan(schema) {
59
64
  if (typeof schema !== 'object' || schema === null) return 0;
@@ -92,6 +97,12 @@ function scan(schema) {
92
97
  }
93
98
  // Every key, not just the subschema keywords, so this cannot miss a place
94
99
  // the normalizers would reach.
100
+ if (EXTRA !== null) {
101
+ for (let i = 0; i < keys.length; i++) {
102
+ const b = EXTRA.get(keys[i]);
103
+ if (b !== undefined) bits |= b;
104
+ }
105
+ }
95
106
  for (let i = 0; i < keys.length; i++) walk(node[keys[i]]);
96
107
  };
97
108
 
@@ -125,3 +136,11 @@ module.exports = {
125
136
  ENCODED_REF,
126
137
  DRAFT7_WORK,
127
138
  };
139
+
140
+ // A schema scanned before the keys were set is scanned again: what was
141
+ // remembered for it does not answer the new questions.
142
+ function setExtraKeys(map) {
143
+ EXTRA = map;
144
+ CACHE = new WeakMap();
145
+ }
146
+ Object.defineProperty(module.exports, '_setExtraKeys', { value: setExtraKeys, enumerable: false });
package/lib/tier0.js CHANGED
@@ -173,6 +173,7 @@ function tier0ValidateObject(plan, data) {
173
173
  // treated as absent, as the interpreted engine treats it.
174
174
  if (v !== undefined && (c.protoName || (_PN ? data.__proto__ : Object.getPrototypeOf(data)) !== Object.prototype) && !_hop.call(data, c.key)) v = undefined;
175
175
  if (v === undefined) {
176
+ // A required key holding undefined is missing, as in every engine.
176
177
  if (reqMask & (1 << i)) return false;
177
178
  continue;
178
179
  }
@@ -22,17 +22,28 @@ function _deq(a, b) {
22
22
  if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;
23
23
  var aa = Array.isArray(a);
24
24
  if (aa !== Array.isArray(b)) return false;
25
- var i;
25
+ var i, k, x, y;
26
26
  if (aa) {
27
27
  if (a.length !== b.length) return false;
28
- for (i = 0; i < a.length; i++) if (!_deq(a[i], b[i])) return false;
28
+ for (i = a.length; i-- !== 0;) if (!_deq(a[i], b[i])) return false;
29
29
  return true;
30
30
  }
31
31
  var ka = Object.keys(a);
32
32
  if (ka.length !== Object.keys(b).length) return false;
33
- for (i = 0; i < ka.length; i++) {
34
- var k = ka[i];
35
- if (!Object.prototype.hasOwnProperty.call(b, k) || !_deq(a[k], b[k])) return false;
33
+ // Keys and scalar values first, nested values after, from the last key back:
34
+ // two items of a list most often differ in a name, a path or an id, and
35
+ // walking a nested list first made each comparison of rust-project's crates
36
+ // three times what it needed (250 against 78 ns for the three; the default
37
+ // validator's deep equal takes 115).
38
+ for (i = ka.length; i-- !== 0;) {
39
+ k = ka[i];
40
+ if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
41
+ x = a[k]; y = b[k];
42
+ if (x !== y && (x === null || y === null || typeof x !== 'object' || typeof y !== 'object')) return false;
43
+ }
44
+ for (i = ka.length; i-- !== 0;) {
45
+ k = ka[i]; x = a[k];
46
+ if (x !== null && typeof x === 'object' && !_deq(x, b[k])) return false;
36
47
  }
37
48
  return true;
38
49
  }
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ // RFC 3986 URI and URI-reference, RFC 3987 IRI and IRI-reference, as one walk
4
+ // over the string with no slicing. `mode` bit 1 asks for a scheme (uri, iri),
5
+ // bit 2 lets non-ASCII through (iri, iri-reference). The rules the walk holds
6
+ // that a character-class scan does not: a second "@" in the authority, a port
7
+ // that is not digits, square brackets anywhere but around an IP literal, the
8
+ // literal itself (RFC 3986 IPv6 with an embedded IPv4 of no leading zeros, an
9
+ // RFC 6874 zone id, IPvFuture), a colon in the first segment of a relative
10
+ // path, and a second "#". A reference made only of unreserved characters,
11
+ // sub-delimiters, "/" and "@" (a file name, a path) is one whatever its
12
+ // shape, and is answered by one scan before any of that.
13
+ //
14
+ // A factory, like lib/safe-regex.js: the interpreted engine calls the
15
+ // function it returns, and the code generator emits the factory's source
16
+ // text (`source`) into the program it builds, so a standalone module carries
17
+ // the same walk without importing it. Nothing inside the factory reads a
18
+ // free variable, and no comments inside it: its text is what gets emitted.
19
+ // `ip6` is the IPv6 check for a bracketed host, one string in: lib/formats.js
20
+ // gives its own and the generator emits ipv6Source, so neither carries a
21
+ // second copy. The slice it costs happens only on an IP literal.
22
+ function __ataUri (ip6) {
23
+ const C = new Uint8Array(128);
24
+ for (let i = 48; i < 58; i++) C[i] = 3;
25
+ for (let i = 65; i < 91; i++) C[i] = 3;
26
+ for (let i = 97; i < 123; i++) C[i] = 3;
27
+ C[43] = 3; C[45] = 3; C[46] = 3;
28
+ C[95] = 1; C[126] = 1;
29
+ C[33] = 1; C[36] = 1; C[38] = 1; C[39] = 1; C[40] = 1; C[41] = 1; C[42] = 1; C[44] = 1; C[59] = 1; C[61] = 1;
30
+ const H = new Uint8Array(128);
31
+ for (let i = 48; i < 58; i++) H[i] = 1;
32
+ for (let i = 65; i < 71; i++) H[i] = 1;
33
+ for (let i = 97; i < 103; i++) H[i] = 1;
34
+ function pct (s, i) {
35
+ const a = s.charCodeAt(i + 1), b = s.charCodeAt(i + 2);
36
+ return a < 128 && b < 128 && H[a] === 1 && H[b] === 1;
37
+ }
38
+ const P = new Uint8Array(128);
39
+ for (let i = 0; i < 128; i++) P[i] = C[i] & 1;
40
+ P[58] = 1; P[64] = 1; P[47] = 1;
41
+ const Q = new Uint8Array(P);
42
+ Q[63] = 1;
43
+ const S = new Uint8Array(P);
44
+ S[58] = 0;
45
+ function literal (s, from, to) {
46
+ if (from >= to) return false;
47
+ const f = s.charCodeAt(from);
48
+ if (f === 118 || f === 86) {
49
+ let i = from + 1, h = 0;
50
+ for (; i < to; i++) { const c = s.charCodeAt(i); if (c < 128 && H[c] === 1) h++; else break; }
51
+ if (h === 0 || s.charCodeAt(i) !== 46 || i + 1 >= to) return false;
52
+ for (i++; i < to; i++) { const c = s.charCodeAt(i); if (!(c < 128 && ((C[c] & 1) === 1 || c === 58))) return false; }
53
+ return true;
54
+ }
55
+ let z = -1;
56
+ for (let i = from; i < to; i++) if (s.charCodeAt(i) === 37) { z = i; break; }
57
+ if (z === -1) return ip6(s.slice(from, to));
58
+ if (s.charCodeAt(z + 1) !== 50 || s.charCodeAt(z + 2) !== 53 || z + 3 >= to) return false;
59
+ for (let i = z + 3; i < to; i++) {
60
+ const c = s.charCodeAt(i);
61
+ if (c === 37) { if (!pct(s, i)) return false; i += 2; continue; }
62
+ if (!(c < 128 && (C[c] & 1) === 1)) return false;
63
+ }
64
+ return ip6(s.slice(from, z));
65
+ }
66
+ return function (s, mode) {
67
+ const n = s.length;
68
+ const wide = (mode & 2) !== 0;
69
+ if ((mode & 1) === 0 && !(s.charCodeAt(0) === 47 && s.charCodeAt(1) === 47)) {
70
+ let k = 0;
71
+ for (; k < n; k++) { const c = s.charCodeAt(k); if (c > 127 || S[c] === 0) break; }
72
+ if (k === n) return true;
73
+ }
74
+ let i = 0, c = s.charCodeAt(0), scheme = false;
75
+ if ((c >= 65 && c <= 90) || (c >= 97 && c <= 122)) {
76
+ let j = 1;
77
+ while (j < n) { c = s.charCodeAt(j); if (c === 58 || c > 127 || (C[c] & 2) === 0) break; j++; }
78
+ if (j < n && c === 58) { scheme = true; i = j + 1; }
79
+ }
80
+ if (!scheme && (mode & 1) !== 0) return false;
81
+ let auth = false;
82
+ if (s.charCodeAt(i) === 47 && s.charCodeAt(i + 1) === 47) {
83
+ auth = true;
84
+ const st = i + 2;
85
+ let end = n, at = -1, br = false, brAt = false, colon = -1;
86
+ for (let k = st; k < n; k++) {
87
+ c = s.charCodeAt(k);
88
+ if (c === 47 || c === 63 || c === 35) { end = k; break; }
89
+ if (c < 128) {
90
+ if ((C[c] & 1) === 1) continue;
91
+ if (c === 37) { if (!pct(s, k)) return false; k += 2; continue; }
92
+ if (c === 58) { if (colon === -1) colon = k; continue; }
93
+ if (c === 64) { if (at !== -1) return false; at = k; brAt = br; colon = -1; continue; }
94
+ if (c === 91 || c === 93) { br = true; continue; }
95
+ return false;
96
+ }
97
+ if (!wide) return false;
98
+ }
99
+ if (brAt) return false;
100
+ const hs = at === -1 ? st : at + 1;
101
+ if (s.charCodeAt(hs) === 91) {
102
+ let close = -1;
103
+ for (let k = hs + 1; k < end; k++) if (s.charCodeAt(k) === 93) { close = k; break; }
104
+ if (close === -1 || !literal(s, hs + 1, close)) return false;
105
+ if (close + 1 < end) {
106
+ if (s.charCodeAt(close + 1) !== 58) return false;
107
+ for (let k = close + 2; k < end; k++) { c = s.charCodeAt(k); if (c < 48 || c > 57) return false; }
108
+ }
109
+ } else {
110
+ if (br) return false;
111
+ if (colon !== -1) for (let k = colon + 1; k < end; k++) { c = s.charCodeAt(k); if (c < 48 || c > 57) return false; }
112
+ }
113
+ i = end;
114
+ }
115
+ if (!scheme && !auth && s.charCodeAt(i) !== 47) {
116
+ for (; i < n; i++) {
117
+ c = s.charCodeAt(i);
118
+ if (c === 47 || c === 63 || c === 35) break;
119
+ if (c === 58) return false;
120
+ if (c === 37) { if (!pct(s, i)) return false; i += 2; continue; }
121
+ if (c < 128 ? P[c] === 0 : !wide) return false;
122
+ }
123
+ }
124
+ for (; i < n; i++) {
125
+ c = s.charCodeAt(i);
126
+ if (c < 128 ? P[c] === 0 : !wide) {
127
+ if (c === 63 || c === 35) break;
128
+ if (c === 37) { const a = s.charCodeAt(i + 1), b = s.charCodeAt(i + 2); if (!(a < 128 && b < 128 && H[a] === 1 && H[b] === 1)) return false; i += 2; continue; }
129
+ return false;
130
+ }
131
+ }
132
+ if (i >= n) return true;
133
+ if (c === 63) {
134
+ for (i++; i < n; i++) {
135
+ c = s.charCodeAt(i);
136
+ if (c < 128 ? Q[c] === 0 : !wide) {
137
+ if (c === 35) break;
138
+ if (c === 37) { if (!pct(s, i)) return false; i += 2; continue; }
139
+ return false;
140
+ }
141
+ }
142
+ if (i >= n) return true;
143
+ }
144
+ for (i++; i < n; i++) {
145
+ c = s.charCodeAt(i);
146
+ if (c < 128 ? Q[c] === 0 : !wide) {
147
+ if (c === 37) { if (!pct(s, i)) return false; i += 2; continue; }
148
+ return false;
149
+ }
150
+ }
151
+ return true;
152
+ };
153
+ }
154
+
155
+ module.exports = { __ataUri };