ata-validator 1.53.0 → 1.55.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.
@@ -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":{}}}}],
@@ -53,7 +53,7 @@ const branchTitle = (n) => (n && n.schema && typeof n.schema.title === 'string'
53
53
  function install(deps) {
54
54
  const { Plan, NOERRORS, err, evalLeaf, evalLeafV, runCustomV, dataBits, escapePointer,
55
55
  deepEqual, enumMember, allDistinct, multipleOfOk, cpAtLeast, cpAtMost,
56
- T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri } = deps;
56
+ T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri } = deps;
57
57
 
58
58
  const TRUE_FN = () => true;
59
59
  // `leaf`: the node compiled no subschema of its own (see compileNode). A
@@ -169,12 +169,21 @@ function install(deps) {
169
169
  }
170
170
  if (P.enum !== null) {
171
171
  if (P.enum.length === 0) checks.push(() => false);
172
+ else if (P.enumObjs.length === 0) { const prims = P.enumPrims; checks.push((data) => prims.has(data)); }
172
173
  else checks.push((data) => enumMember(P, data));
173
174
  }
174
175
  if (P.hasConst) {
175
176
  const c = P.const;
176
177
  checks.push((data) => deepEqual(c, data));
177
178
  }
179
+ // The value checks of each type, folded to one call or null. A node of
180
+ // one type and nothing but that type's keywords (the common leaf: a
181
+ // string with a length, an integer with a bound) is answered by one
182
+ // closure over typeof and that call, with no type bits computed and no
183
+ // chain of guarded checks: the interpreted engine spent a fifth of a
184
+ // document in those calls.
185
+ const reqFolded = P.required !== null && P.requiredUndef === null && P.properties !== null;
186
+ let numsRun = null;
178
187
  if (P.hasNumber) {
179
188
  const nums = [];
180
189
  if (P.minimum !== undefined) { const m = P.minimum; nums.push((d) => d >= m); }
@@ -182,42 +191,45 @@ function install(deps) {
182
191
  if (P.exclusiveMinimum !== undefined) { const m = P.exclusiveMinimum; nums.push((d) => d > m); }
183
192
  if (P.exclusiveMaximum !== undefined) { const m = P.exclusiveMaximum; nums.push((d) => d < m); }
184
193
  if (P.multipleOf !== undefined) { const m = P.multipleOf; nums.push((d) => multipleOfOk(d, m)); }
185
- const run = combineValue(nums);
186
- // A non-finite number is not of type `number` here: dataBits() gives it
187
- // no type bit, and an empty schema accepts it. So the numeric keywords
188
- // must not apply to it either, or `{ minimum: 3 }` rejects a value that
189
- // `{}` accepts and the compiled engine admits.
190
- if (run !== null) checks.push((data) => !Number.isFinite(data) || run(data));
194
+ numsRun = combineValue(nums);
191
195
  }
196
+ let strsRun = null;
192
197
  if (P.hasString) {
193
198
  const strs = [];
194
- if (P.minLength !== undefined) { const m = P.minLength; strs.push((d) => cpAtLeast(d, m)); }
195
- if (P.maxLength !== undefined) { const m = P.maxLength; strs.push((d) => cpAtMost(d, m)); }
199
+ if (P.minLength !== undefined && P.maxLength !== undefined) {
200
+ const lo = P.minLength, hi = P.maxLength;
201
+ strs.push((d) => { const n = d.length; return (n >= 2 * lo && n <= hi) || (cpAtLeast(d, lo) && cpAtMost(d, hi)); });
202
+ } else if (P.minLength !== undefined) { const m = P.minLength; strs.push((d) => cpAtLeast(d, m)); }
203
+ else if (P.maxLength !== undefined) { const m = P.maxLength; strs.push((d) => cpAtMost(d, m)); }
196
204
  if (P.pattern !== null) { const re = P.pattern; strs.push((d) => re.test(d)); }
197
205
  if (P.formatFn !== null) { const f = P.formatFn; strs.push((d) => f(d)); }
198
- const run = combineValue(strs);
199
- if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_STRING || run(data)); }
206
+ strsRun = combineValue(strs);
200
207
  }
201
- // `hasArray` also covers prefixItems/items/contains, which are not leaf
202
- // keywords; only the three value-level ones belong here, exactly as
203
- // evalLeafV() reads them.
208
+ let arrsRun = null;
204
209
  if (P.minItems !== undefined || P.maxItems !== undefined || P.uniqueItems) {
205
210
  const arrs = [];
206
211
  if (P.minItems !== undefined) { const m = P.minItems; arrs.push((d) => d.length >= m); }
207
212
  if (P.maxItems !== undefined) { const m = P.maxItems; arrs.push((d) => d.length <= m); }
208
213
  if (P.uniqueItems) arrs.push(allDistinct);
209
- const run = combineValue(arrs);
210
- if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_ARRAY || run(data)); }
214
+ arrsRun = combineValue(arrs);
211
215
  }
212
- if (P.required !== null || P.minProperties !== undefined || P.maxProperties !== undefined || P.dependentRequired !== null) {
216
+ let objsRun = null;
217
+ if ((P.required !== null && !reqFolded) || P.minProperties !== undefined || P.maxProperties !== undefined || P.dependentRequired !== null) {
213
218
  const objs = [];
214
- if (P.required !== null) {
219
+ if (P.required !== null && !reqFolded) {
215
220
  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) => {
221
+ // A required key holding undefined is missing; for a declared
222
+ // property the property step finds it (requiredInProps).
223
+ const undef = P.requiredUndef;
224
+ 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)); }
225
+ else if (undef === null) objs.push((d) => {
218
226
  for (let i = 0; i < req.length; i++) if (!Object.hasOwn(d, req[i])) return false;
219
227
  return true;
220
228
  });
229
+ else objs.push((d) => {
230
+ for (let i = 0; i < req.length; i++) if (!Object.hasOwn(d, req[i]) || (undef[i] && d[req[i]] === undefined)) return false;
231
+ return true;
232
+ });
221
233
  }
222
234
  if (P.minProperties !== undefined) { const m = P.minProperties; objs.push((d) => Object.keys(d).length >= m); }
223
235
  if (P.maxProperties !== undefined) { const m = P.maxProperties; objs.push((d) => Object.keys(d).length <= m); }
@@ -232,9 +244,34 @@ function install(deps) {
232
244
  return true;
233
245
  });
234
246
  }
235
- const run = combineValue(objs);
236
- if (run !== null) { needsBits = true; checks.push((data, bits) => bits !== T_OBJECT || run(data)); }
247
+ objsRun = combineValue(objs);
237
248
  }
249
+ if (P.hasType && P.enum === null && !P.hasConst && !P.hasCustom && checks.length === 1) {
250
+ const m = P.typeMask;
251
+ // The run of the type's own keywords, and whether any other type's are present.
252
+ const own = m === T_STRING ? strsRun : m === T_INTEGER || m === T_NUMBER ? numsRun : m === T_OBJECT ? objsRun : m === T_ARRAY ? arrsRun : null;
253
+ const foreign = (numsRun !== null && m !== T_INTEGER && m !== T_NUMBER) || (strsRun !== null && m !== T_STRING) || (arrsRun !== null && m !== T_ARRAY) || (objsRun !== null && m !== T_OBJECT);
254
+ if (!foreign) {
255
+ const r = own;
256
+ switch (m) {
257
+ case T_STRING: return r === null ? (d) => typeof d === 'string' : (d) => typeof d === 'string' && r(d);
258
+ case T_INTEGER: return r === null ? (d) => Number.isInteger(d) : (d) => Number.isInteger(d) && r(d);
259
+ case T_NUMBER: return r === null ? (d) => typeof d === 'number' && isFinite(d) : (d) => typeof d === 'number' && isFinite(d) && r(d);
260
+ case T_BOOLEAN: return (d) => typeof d === 'boolean';
261
+ case T_NULL: return (d) => d === null;
262
+ case T_OBJECT: return r === null ? (d) => typeof d === 'object' && d !== null && !Array.isArray(d) : (d) => typeof d === 'object' && d !== null && !Array.isArray(d) && r(d);
263
+ case T_ARRAY: return r === null ? (d) => Array.isArray(d) : (d) => Array.isArray(d) && r(d);
264
+ }
265
+ }
266
+ }
267
+ // A non-finite number is not of type `number` here: dataBits() gives it
268
+ // no type bit, and an empty schema accepts it. So the numeric keywords
269
+ // must not apply to it either, or `{ minimum: 3 }` rejects a value that
270
+ // `{}` accepts and the compiled engine admits.
271
+ if (numsRun !== null) { const run = numsRun; checks.push((data) => !Number.isFinite(data) || run(data)); }
272
+ if (strsRun !== null) { const run = strsRun; needsBits = true; checks.push((data, bits) => bits !== T_STRING || run(data)); }
273
+ if (arrsRun !== null) { const run = arrsRun; needsBits = true; checks.push((data, bits) => bits !== T_ARRAY || run(data)); }
274
+ if (objsRun !== null) { const run = objsRun; needsBits = true; checks.push((data, bits) => bits !== T_OBJECT || run(data)); }
238
275
 
239
276
  if (P.hasCustom) {
240
277
  needsBits = true;
@@ -310,6 +347,7 @@ function install(deps) {
310
347
 
311
348
  const steps = []; // collect variant: (data, errors, ip, sp, stack, rec)
312
349
  const vsteps = []; // verdict variant: (data, stack, rec)
350
+ let leafV = TRUE_FN;
313
351
 
314
352
  // $ref / $dynamicRef, resolved at compile time.
315
353
  for (let which = 0; which < 2; which++) {
@@ -385,7 +423,8 @@ function install(deps) {
385
423
  P.required !== null || P.minProperties !== undefined || P.maxProperties !== undefined ||
386
424
  P.dependentRequired !== null || P.hasCustom) {
387
425
  steps.push((data, errors, instancePath, schemaPath) => evalLeaf(P, data, errors, instancePath, schemaPath));
388
- vsteps.push(compileLeafV(P));
426
+ leafV = compileLeafV(P);
427
+ if (leafV !== TRUE_FN) vsteps.push(leafV);
389
428
  }
390
429
 
391
430
  // Arrays
@@ -490,8 +529,10 @@ function install(deps) {
490
529
  // Declared properties: a linear scan over a short list beats a Map
491
530
  // lookup, and most schemas declare a handful.
492
531
  const propKeys = props !== null ? [...props.keys()] : null;
532
+ // req: the key is also required, so holding undefined it is missing.
533
+ const reqIn = P.requiredInProps;
493
534
  const propList = props !== null
494
- ? [...props.values()].map((entry) => ({ fn: child(entry.node), seg: entry.seg, schemaSeg: entry.schemaSeg }))
535
+ ? [...props].map(([key, entry]) => ({ fn: child(entry.node), seg: entry.seg, schemaSeg: entry.schemaSeg, req: reqIn !== null && reqIn.has(key) }))
495
536
  : null;
496
537
  const propMap = props !== null && props.size > 8 ? new Map(propKeys.map((k, i) => [k, propList[i]])) : null;
497
538
  const lookup = propMap !== null
@@ -503,6 +544,10 @@ function install(deps) {
503
544
  ? P.patternProperties.map((e) => ({ re: e.re, src: e.src, fn: child(e.node) }))
504
545
  : null;
505
546
  const apFn = P.additionalProperties !== undefined ? child(P.additionalProperties) : null;
547
+ // The node's value checks, when they are the step before this one,
548
+ // run inside it: one closure call per object instead of two, and the
549
+ // composition above it goes away.
550
+ const fusedLeaf = vsteps.length > 0 && vsteps[vsteps.length - 1] === leafV && leafV !== TRUE_FN ? vsteps.pop() : null;
506
551
  // `additionalProperties: false` is a boolean subschema, but its error is
507
552
  // not the generic "boolean schema is false": every consumer switches on
508
553
  // keyword === 'additionalProperties' and reads params.additionalProperty
@@ -533,12 +578,18 @@ function install(deps) {
533
578
  let evaluated = false;
534
579
  const prop = lookup !== null ? lookup(key) : undefined;
535
580
  // A declared property whose value is undefined is not checked (see
536
- // the interpreter's eval).
581
+ // the interpreter's eval); required, it is missing.
537
582
  if (prop !== undefined) {
538
583
  const value = data[key];
539
- if (value !== undefined && !((prop.fn.leaf && prop.fn.v(value, stack)) || prop.fn.c(value, errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack))) {
584
+ if (value !== undefined) {
585
+ if (!((prop.fn.leaf && prop.fn.v(value, stack)) || prop.fn.c(value, errors, instancePath + prop.seg, schemaPath + prop.schemaSeg, stack))) {
586
+ ok = false;
587
+ if (errors === NOERRORS) return false;
588
+ }
589
+ } else if (prop.req) {
540
590
  ok = false;
541
591
  if (errors === NOERRORS) return false;
592
+ errors.push(err('required', 'required', instancePath, schemaPath + '/required', { missingProperty: key }, `must have required property '${key}'`));
542
593
  }
543
594
  evaluated = true;
544
595
  }
@@ -576,30 +627,58 @@ function install(deps) {
576
627
  // not mistaken for data. A long list walks the data's keys instead,
577
628
  // since the data usually carries far fewer.
578
629
  const n = propKeys.length;
630
+ // On an ordinary object (its prototype is Object.prototype) a key
631
+ // that names nothing on that prototype is read directly: a value
632
+ // means the key is the object's own, undefined means absent or held
633
+ // undefined, which the schema treats alike. Other objects, and keys
634
+ // such as `constructor` or `toString`, keep the own-property test.
635
+ const direct = propKeys.map((k) => !(k in Object.prototype));
636
+ const assertsObject = fusedLeaf !== null && P.hasType && P.typeMask === T_OBJECT;
579
637
  vsteps.push((data, stack, rec) => {
580
- if (dataBits(data) !== T_OBJECT) return true;
638
+ if (fusedLeaf !== null && !fusedLeaf(data)) return false;
639
+ if (!assertsObject && dataBits(data) !== T_OBJECT) return true;
640
+ const plain = Object.getPrototypeOf(data) === Object.prototype;
581
641
  for (let i = 0; i < n; i++) {
582
642
  const key = propKeys[i];
583
- if (!Object.hasOwn(data, key)) continue;
584
- const value = data[key];
585
- if (value !== undefined && !propList[i].fn.v(value, stack)) return false;
643
+ let value;
644
+ if (plain && direct[i]) {
645
+ value = data[key];
646
+ // Absent or held undefined: missing when required (the leaf
647
+ // step no longer checks it, reqFolded in compileLeafV). A key
648
+ // held undefined still counts as evaluated for the record.
649
+ if (value === undefined) { if (propList[i].req) return false; if (collect && Object.hasOwn(data, key)) addProp(rec, key); continue; }
650
+ } else {
651
+ if (!Object.hasOwn(data, key)) { if (propList[i].req) return false; continue; }
652
+ value = data[key];
653
+ if (value === undefined) { if (propList[i].req) return false; if (collect) addProp(rec, key); continue; }
654
+ }
655
+ if (!propList[i].fn.v(value, stack)) return false;
586
656
  if (collect) addProp(rec, key);
587
657
  }
588
658
  return true;
589
659
  });
590
- } else vsteps.push((data, stack, rec) => {
660
+ } else {
661
+ // Required keys that are declared properties are counted as the walk
662
+ // meets them (reqFolded in compileLeafV); one short at the end means
663
+ // one is missing.
664
+ const reqN = P.required !== null && P.requiredUndef === null && P.requiredInProps !== null ? P.requiredInProps.size : 0;
665
+ vsteps.push((data, stack, rec) => {
666
+ if (fusedLeaf !== null && !fusedLeaf(data)) return false;
591
667
  if (dataBits(data) !== T_OBJECT) return true;
592
668
  const keys = collect ? keysOf(rec, data) : Object.keys(data);
593
669
  if (pnFn !== null) {
594
670
  for (const k of keys) if (!pnFn.v(k, stack)) return false;
595
671
  }
672
+ let seenReq = 0;
596
673
  for (let k = 0; k < keys.length; k++) {
597
674
  const key = keys[k];
598
675
  let evaluated = false;
599
676
  const prop = lookup !== null ? lookup(key) : undefined;
600
677
  if (prop !== undefined) {
601
678
  const value = data[key];
602
- if (value !== undefined && !prop.fn.v(value, stack)) return false;
679
+ if (value !== undefined) { if (!prop.fn.v(value, stack)) return false; }
680
+ else if (prop.req) return false;
681
+ if (prop.req) seenReq++;
603
682
  evaluated = true;
604
683
  }
605
684
  if (patterns !== null) {
@@ -617,8 +696,9 @@ function install(deps) {
617
696
  }
618
697
  if (evaluated && collect) addProp(rec, key);
619
698
  }
620
- return true;
699
+ return seenReq === reqN;
621
700
  });
701
+ }
622
702
  }
623
703
  if (P.dependentSchemas !== null) {
624
704
  const entries = P.dependentSchemas.map(([k, v]) => [k, inPlace(v, base), escapePointer(k)]);
@@ -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,23 +30,10 @@ 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 (compat.js), read
34
- // on the same walk so that a document is not walked again for them.
35
- const HAS_REF = 1 << 8; // `$ref` anywhere
36
- const BEYOND_ITEMS = 1 << 9; // a subschema keyword other than `properties` and object `items`
37
- const DISCRIMINATOR = 1 << 10; // `discriminator` anywhere
38
- const HAS_DEFAULT = 1 << 11; // `default` anywhere
39
- const HAS_ALLOF = 1 << 12; // `allOf` anywhere
40
- const HAS_FORMAT = 1 << 13; // `format` anywhere
41
- const KEY_BITS = new Map([
42
- ['$ref', HAS_REF | BEYOND_ITEMS], ['$dynamicRef', BEYOND_ITEMS], ['allOf', HAS_ALLOF | BEYOND_ITEMS],
43
- ['anyOf', BEYOND_ITEMS], ['oneOf', BEYOND_ITEMS], ['not', BEYOND_ITEMS], ['if', BEYOND_ITEMS],
44
- ['then', BEYOND_ITEMS], ['else', BEYOND_ITEMS], ['additionalProperties', BEYOND_ITEMS],
45
- ['patternProperties', BEYOND_ITEMS], ['prefixItems', BEYOND_ITEMS], ['additionalItems', BEYOND_ITEMS],
46
- ['contains', BEYOND_ITEMS], ['dependentSchemas', BEYOND_ITEMS], ['dependencies', BEYOND_ITEMS],
47
- ['propertyNames', BEYOND_ITEMS], ['unevaluatedProperties', BEYOND_ITEMS], ['unevaluatedItems', BEYOND_ITEMS],
48
- ['discriminator', DISCRIMINATOR], ['default', HAS_DEFAULT], ['format', HAS_FORMAT],
49
- ]);
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;
50
37
 
51
38
  // Everything only draft-07 normalization acts on. A schema of another dialect
52
39
  // can carry these without them meaning anything, so they are read together
@@ -71,7 +58,7 @@ const ANCHOR_ID_RE = /^#[A-Za-z][A-Za-z0-9_.:-]*$/;
71
58
  // This assumes a schema is not mutated after being handed to a Validator,
72
59
  // which is already true of everything else here: the compile cache, the
73
60
  // identity cache and the schema map would all be stale too.
74
- const CACHE = new WeakMap();
61
+ let CACHE = new WeakMap();
75
62
 
76
63
  function scan(schema) {
77
64
  if (typeof schema !== 'object' || schema === null) return 0;
@@ -110,11 +97,13 @@ function scan(schema) {
110
97
  }
111
98
  // Every key, not just the subschema keywords, so this cannot miss a place
112
99
  // the normalizers would reach.
113
- for (let i = 0; i < keys.length; i++) {
114
- const b = KEY_BITS.get(keys[i]);
115
- if (b !== undefined) bits |= b;
116
- walk(node[keys[i]]);
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
+ }
117
105
  }
106
+ for (let i = 0; i < keys.length; i++) walk(node[keys[i]]);
118
107
  };
119
108
 
120
109
  walk(schema);
@@ -146,10 +135,12 @@ module.exports = {
146
135
  EXCLUSIVE_BOOL,
147
136
  ENCODED_REF,
148
137
  DRAFT7_WORK,
149
- HAS_REF,
150
- BEYOND_ITEMS,
151
- DISCRIMINATOR,
152
- HAS_DEFAULT,
153
- HAS_ALLOF,
154
- HAS_FORMAT,
155
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
  }