ata-validator 1.7.0 → 1.7.2

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.
@@ -1,5 +1,77 @@
1
1
  'use strict'
2
2
 
3
+ // uniqueItems helpers for the boolean generator, hoisted once per compiled
4
+ // function (and carried into standalone output through the preamble).
5
+ // _deq is JSON equality: numbers by value, objects by own keys regardless of
6
+ // order, arrays positionally. _uq answers whether every item is distinct.
7
+ // Structural JSON equality, hoisted once per compiled function. `const` and
8
+ // `enum` compare instances by value: two objects with the same members are
9
+ // equal whatever order their keys were written in. Comparing serialized
10
+ // forms answers a different question and costs a string per call.
11
+ const DEQ_HELPER = "function _deq(a,b){if(a===b)return true;if(a===null||b===null||typeof a!=='object'||typeof b!=='object')return false;var aa=Array.isArray(a);if(aa!==Array.isArray(b))return false;var i;if(aa){if(a.length!==b.length)return false;for(i=0;i<a.length;i++)if(!_deq(a[i],b[i]))return false;return true}var ka=Object.keys(a);if(ka.length!==Object.keys(b).length)return false;for(i=0;i<ka.length;i++){var k=ka[i];if(!Object.prototype.hasOwnProperty.call(b,k)||!_deq(a[k],b[k]))return false}return true}"
12
+
13
+ // uniqueItems helpers for the boolean generator. `_uq` answers whether every
14
+ // item is distinct: short arrays compare pairwise and allocate nothing,
15
+ // longer ones use a Set of primitives and fall back to sorted-key canonical
16
+ // strings only once an object actually appears.
17
+ const UQ_HELPERS = "function _cn(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn(x[k])}).join(',')+'}'}" +
18
+ "function _uq(a){var n=a.length,i,k;if(n<2)return true;if(n<=12){for(i=1;i<n;i++)for(k=0;k<i;k++)if(_deq(a[i],a[k]))return false;return true}var s=new Set();for(i=0;i<n;i++){var x=a[i];if(x!==null&&typeof x==='object')break;if(s.has(x))return false;s.add(x)}if(i===n)return true;s=new Set();for(i=0;i<n;i++){var c=_cn(a[i]);if(s.has(c))return false;s.add(c)}return true}"
19
+
20
+ // Where a generator hoists a declaration so it runs once per compiled
21
+ // function rather than once per call. The boolean generator has a preamble
22
+ // for exactly this; the error and combined generators emit `helperCode` into
23
+ // their function source. The boolean generator's `helperCode` is only
24
+ // scanned for regex entries and must not be used here.
25
+ function hoistOnce(ctx, key, code) {
26
+ if (ctx[key]) return
27
+ ctx[key] = true
28
+ if (ctx.preamble) ctx.preamble.push(code)
29
+ else if (ctx.helperCode) ctx.helperCode.push(code)
30
+ }
31
+
32
+ function emitDeq(ctx) {
33
+ hoistOnce(ctx, '_deqHoisted', DEQ_HELPER)
34
+ return '_deq'
35
+ }
36
+
37
+ // `_uq` compares items with `_deq`, so it can never be hoisted alone.
38
+ function emitUq(ctx) {
39
+ emitDeq(ctx)
40
+ hoistOnce(ctx, '_uqHoisted', UQ_HELPERS)
41
+ return '_uq'
42
+ }
43
+
44
+ // The compared value, parsed once at compile time. `JSON.parse` rather than a
45
+ // JS object literal so a `__proto__` member stays an ordinary property.
46
+ function emitConstant(ctx, value) {
47
+ if (!ctx._constPool) ctx._constPool = new Map()
48
+ const json = JSON.stringify(value)
49
+ let name = ctx._constPool.get(json)
50
+ if (name === undefined) {
51
+ name = '_kv' + ctx._constPool.size
52
+ ctx._constPool.set(json, name)
53
+ const decl = `const ${name}=JSON.parse(${JSON.stringify(json)})`
54
+ if (ctx.preamble) ctx.preamble.push(decl)
55
+ else if (ctx.helperCode) ctx.helperCode.push(decl)
56
+ }
57
+ return name
58
+ }
59
+
60
+ // One `enum` test: primitives compare with `===`, which needs no call, and
61
+ // object members go through structural equality.
62
+ function enumCondition(ctx, vals, v) {
63
+ const parts = []
64
+ const prims = vals.filter((x) => x === null || typeof x !== 'object')
65
+ const objs = vals.filter((x) => x !== null && typeof x === 'object')
66
+ if (prims.length) parts.push(prims.map((x) => `${v}===${JSON.stringify(x)}`).join('||'))
67
+ if (objs.length) {
68
+ const deq = emitDeq(ctx)
69
+ parts.push(objs.map((o) => `${deq}(${v},${emitConstant(ctx, o)})`).join('||'))
70
+ }
71
+ return parts.filter(Boolean).join('||') || 'false'
72
+ }
73
+
74
+
3
75
  const { codeFor } = require('./error-codes')
4
76
  const { compileSafe, patternIsSafe } = require('./safe-regex')
5
77
 
@@ -53,7 +125,8 @@ function buildErrorLiteral (opts) {
53
125
  function _cpLen(s) {
54
126
  const len = s.length;
55
127
  for (let i = 0; i < len; i++) {
56
- if (s.charCodeAt(i) >= 0xD800 && s.charCodeAt(i) <= 0xDBFF) {
128
+ // Wraparound test: one compare classifies a high surrogate.
129
+ if (((s.charCodeAt(i) - 0xD800) >>> 0) < 0x400) {
57
130
  // Found a high surrogate — count code points the slow way
58
131
  let n = 0; for (const _ of s) n++; return n;
59
132
  }
@@ -354,7 +427,7 @@ function compileToJS(schema, defs, schemaMap) {
354
427
  }
355
428
  if (schema.maxLength !== undefined) {
356
429
  const max = schema.maxLength
357
- checks.push((d) => typeof d !== 'string' || d.length <= max || _cpLen(d) <= max)
430
+ checks.push((d) => typeof d !== 'string' || d.length <= max || (d.length <= 2 * max + 1 && _cpLen(d) <= max))
358
431
  }
359
432
  if (schema.pattern) {
360
433
  try {
@@ -1706,13 +1779,7 @@ function genCode(schema, v, lines, ctx, knownType) {
1706
1779
 
1707
1780
  // enum
1708
1781
  if (schema.enum) {
1709
- const vals = schema.enum
1710
- const primitives = vals.filter(v => v === null || typeof v !== 'object')
1711
- const objects = vals.filter(v => v !== null && typeof v === 'object')
1712
- const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
1713
- const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
1714
- const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
1715
- lines.push(`if(!(${allChecks || 'false'}))return false`)
1782
+ lines.push(`if(!(${enumCondition(ctx, schema.enum, v)}))return false`)
1716
1783
  }
1717
1784
 
1718
1785
  // const
@@ -1721,12 +1788,9 @@ function genCode(schema, v, lines, ctx, knownType) {
1721
1788
  if (cv === null || typeof cv !== 'object') {
1722
1789
  lines.push(`if(${v}!==${JSON.stringify(cv)})return false`)
1723
1790
  } else {
1724
- // Key-order independent comparison, defined inline so every emitter
1725
- // (runtime, hybrid, standalone) carries it.
1726
- const ci = ctx.varCounter++
1727
- const canonFn = `_cnB${ci}`
1728
- const expected = _canonical(cv)
1729
- lines.push(`{const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};if(${canonFn}(${v})!==${JSON.stringify(expected)})return false}`)
1791
+ // Structural comparison against a value parsed once, so the hot path
1792
+ // neither builds a closure nor serializes the instance.
1793
+ lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)}))return false`)
1730
1794
  }
1731
1795
  }
1732
1796
 
@@ -1881,11 +1945,22 @@ function genCode(schema, v, lines, ctx, knownType) {
1881
1945
  const maxItems = schema.maxItems
1882
1946
  // Small primitive arrays (maxItems <= 16): nested loop is 6x faster than Set
1883
1947
  // No allocation, no hash computation — just direct === comparison
1884
- const inner = isPrimItems && maxItems && maxItems <= 16
1885
- ? `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k])return false}}`
1886
- : isPrimItems
1887
- ? `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i]))return false;_s${si}.add(${v}[_i])}`
1888
- : `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k))return false;_s${si}.add(_k)}`
1948
+ let inner
1949
+ if (isPrimItems && maxItems && maxItems <= 16) {
1950
+ inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k])return false}}`
1951
+ } else if (isPrimItems) {
1952
+ inner = `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i]))return false;_s${si}.add(${v}[_i])}`
1953
+ } else if (ctx.preamble) {
1954
+ // Unknown item types: one hoisted helper per compiled function. Short
1955
+ // arrays compare pairwise with a structural equality that ignores key
1956
+ // order and allocates nothing; longer ones fall back to a Set of
1957
+ // primitives, or to sorted-key canonical strings once an object shows
1958
+ // up. The previous form built the canonical closure and a Set on
1959
+ // every call, even for [1, 2, 3].
1960
+ inner = `if(!${emitUq(ctx)}(${v}))return false`
1961
+ } else {
1962
+ inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k))return false;_s${si}.add(_k)}`
1963
+ }
1889
1964
  lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
1890
1965
  }
1891
1966
 
@@ -3187,13 +3262,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3187
3262
 
3188
3263
  // enum
3189
3264
  if (schema.enum) {
3190
- const vals = schema.enum
3191
- const primitives = vals.filter(v => v === null || typeof v !== 'object')
3192
- const objects = vals.filter(v => v !== null && typeof v === 'object')
3193
- const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
3194
- const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
3195
- const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
3196
- lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3265
+ lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3197
3266
  }
3198
3267
 
3199
3268
  // const — use canonical (sorted-key) comparison for objects
@@ -3202,12 +3271,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3202
3271
  if (cv === null || typeof cv !== 'object') {
3203
3272
  lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
3204
3273
  } else {
3205
- // Pre-compute canonical form of const value
3206
- const ci = ctx.varCounter++
3207
- const canonFn = `_cnE${ci}`
3208
- ctx.helperCode.push(`const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};`)
3209
- const expected = canonFn + '(JSON.parse(' + JSON.stringify(JSON.stringify(cv)) + '))'
3210
- lines.push(`if(${canonFn}(${v})!==${expected}){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
3274
+ lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
3211
3275
  }
3212
3276
  }
3213
3277
 
@@ -3797,13 +3861,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3797
3861
 
3798
3862
  // enum
3799
3863
  if (schema.enum) {
3800
- const vals = schema.enum
3801
- const primitives = vals.filter(v => v === null || typeof v !== 'object')
3802
- const objects = vals.filter(v => v !== null && typeof v === 'object')
3803
- const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
3804
- const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
3805
- const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
3806
- lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3864
+ lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
3807
3865
  }
3808
3866
 
3809
3867
  // const
@@ -3812,10 +3870,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3812
3870
  if (cv === null || typeof cv !== 'object') {
3813
3871
  lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
3814
3872
  } else {
3815
- const ci = ctx.varCounter++
3816
- const canonFn = `_cn${ci}`
3817
- ctx.helperCode.push(`const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};`)
3818
- lines.push(`if(${canonFn}(${v})!==${canonFn}(JSON.parse(${JSON.stringify(JSON.stringify(cv))}))){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
3873
+ lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
3819
3874
  }
3820
3875
  }
3821
3876