ata-validator 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/binding/ata_napi.cpp +13 -7
- package/compat.js +1 -9
- package/include/ata.h +4 -0
- package/index.d.ts +12 -4
- package/index.js +91 -10
- package/lib/js-compiler.js +328 -152
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/ata-validator.node +0 -0
- package/src/ata.cpp +327 -1
package/lib/js-compiler.js
CHANGED
|
@@ -4,6 +4,34 @@
|
|
|
4
4
|
// Closure-based validator — no new Function() or eval().
|
|
5
5
|
// Returns null if the schema is too complex for JS compilation.
|
|
6
6
|
|
|
7
|
+
// AJV-compatible error message templates (compile-time, not runtime)
|
|
8
|
+
const AJV_MESSAGES = {
|
|
9
|
+
type: (p) => `must be ${p.type}`,
|
|
10
|
+
required: (p) => `must have required property '${p.missingProperty}'`,
|
|
11
|
+
additionalProperties: () => 'must NOT have additional properties',
|
|
12
|
+
enum: () => 'must be equal to one of the allowed values',
|
|
13
|
+
const: () => 'must be equal to constant',
|
|
14
|
+
minimum: (p) => `must be >= ${p.limit}`,
|
|
15
|
+
maximum: (p) => `must be <= ${p.limit}`,
|
|
16
|
+
exclusiveMinimum: (p) => `must be > ${p.limit}`,
|
|
17
|
+
exclusiveMaximum: (p) => `must be < ${p.limit}`,
|
|
18
|
+
minLength: (p) => `must NOT have fewer than ${p.limit} characters`,
|
|
19
|
+
maxLength: (p) => `must NOT have more than ${p.limit} characters`,
|
|
20
|
+
pattern: (p) => `must match pattern "${p.pattern}"`,
|
|
21
|
+
format: (p) => `must match format "${p.format}"`,
|
|
22
|
+
minItems: (p) => `must NOT have fewer than ${p.limit} items`,
|
|
23
|
+
maxItems: (p) => `must NOT have more than ${p.limit} items`,
|
|
24
|
+
uniqueItems: (p) => `must NOT have duplicate items (items ## ${p.j} and ${p.i} are identical)`,
|
|
25
|
+
minProperties: (p) => `must NOT have fewer than ${p.limit} properties`,
|
|
26
|
+
maxProperties: (p) => `must NOT have more than ${p.limit} properties`,
|
|
27
|
+
multipleOf: (p) => `must be multiple of ${p.multipleOf}`,
|
|
28
|
+
oneOf: () => 'must match exactly one schema in oneOf',
|
|
29
|
+
anyOf: () => 'must match a schema in anyOf',
|
|
30
|
+
allOf: () => 'must match all schemas in allOf',
|
|
31
|
+
not: () => 'must NOT be valid',
|
|
32
|
+
if: (p) => `must match "${p.failingKeyword}" schema`,
|
|
33
|
+
}
|
|
34
|
+
|
|
7
35
|
function compileToJS(schema, defs, schemaMap) {
|
|
8
36
|
if (typeof schema === 'boolean') {
|
|
9
37
|
return schema ? () => true : () => false
|
|
@@ -758,7 +786,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
758
786
|
if (reqChecks.length > 0) {
|
|
759
787
|
lines.push(`if(${reqChecks.join('||')})return false`)
|
|
760
788
|
}
|
|
761
|
-
} else if (schema.required) {
|
|
789
|
+
} else if (schema.required && schema.required.length > 0) {
|
|
762
790
|
if (isObj) {
|
|
763
791
|
const checks = schema.required.map(key => `${v}[${JSON.stringify(key)}]===undefined`)
|
|
764
792
|
lines.push(`if(${checks.join('||')})return false`)
|
|
@@ -810,10 +838,15 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
810
838
|
if (schema.maxProperties !== undefined) lines.push(`if(${objGuard}Object.keys(${v}).length>${schema.maxProperties})return false`)
|
|
811
839
|
|
|
812
840
|
if (schema.pattern) {
|
|
813
|
-
//
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
841
|
+
// Try inline charCode compilation for simple patterns (avoids RegExp engine)
|
|
842
|
+
const inlineCheck = compilePatternInline(schema.pattern, v)
|
|
843
|
+
if (inlineCheck) {
|
|
844
|
+
lines.push(isStr ? `if(!(${inlineCheck}))return false` : `if(typeof ${v}==='string'&&!(${inlineCheck}))return false`)
|
|
845
|
+
} else {
|
|
846
|
+
const ri = ctx.varCounter++
|
|
847
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(schema.pattern)})`)
|
|
848
|
+
lines.push(isStr ? `if(!_re${ri}.test(${v}))return false` : `if(typeof ${v}==='string'&&!_re${ri}.test(${v}))return false`)
|
|
849
|
+
}
|
|
817
850
|
}
|
|
818
851
|
|
|
819
852
|
if (schema.format) {
|
|
@@ -1583,6 +1616,79 @@ const FORMAT_CODEGEN = {
|
|
|
1583
1616
|
// Safe key escaping: use JSON.stringify to handle all special chars (newlines, null bytes, etc.)
|
|
1584
1617
|
function esc(s) { return JSON.stringify(s).slice(1, -1) }
|
|
1585
1618
|
|
|
1619
|
+
// Resolve child path at codegen time when parent is a static string literal.
|
|
1620
|
+
// This enables frozen pre-allocation for ALL nested error objects.
|
|
1621
|
+
function childPathExpr(parentExpr, suffix) {
|
|
1622
|
+
if (!parentExpr) return `'/${suffix}'`
|
|
1623
|
+
if (parentExpr.startsWith("'") && !parentExpr.includes('+')) {
|
|
1624
|
+
// Static parent: resolve at codegen time → '/parent/child' (single literal)
|
|
1625
|
+
return `'${parentExpr.slice(1, -1)}/${suffix}'`
|
|
1626
|
+
}
|
|
1627
|
+
// Dynamic parent: keep as concat expression
|
|
1628
|
+
return `${parentExpr}+'/${suffix}'`
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// Compile simple regex patterns to inline charCode checks — avoids RegExp engine overhead.
|
|
1632
|
+
// Returns null if pattern is too complex for inline compilation.
|
|
1633
|
+
// Handles: ^[charclass]{n}$, ^[charclass]+$, ^[charclass]*$, ^[charclass]{m,n}$
|
|
1634
|
+
function compilePatternInline(pattern, varName) {
|
|
1635
|
+
// Match: ^[chars]{exact}$ — e.g., ^[0-9]{5}$
|
|
1636
|
+
let m = pattern.match(/^\^(\[[\w\-]+\])\{(\d+)\}\$$/)
|
|
1637
|
+
if (m) {
|
|
1638
|
+
const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
|
|
1639
|
+
if (!rangeCheck) return null
|
|
1640
|
+
const len = parseInt(m[2])
|
|
1641
|
+
return `${varName}.length===${len}&&(()=>{for(let _pi=0;_pi<${len};_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
1642
|
+
}
|
|
1643
|
+
// Match: ^[chars]+$ — e.g., ^[a-z]+$
|
|
1644
|
+
m = pattern.match(/^\^(\[[\w\-]+\])\+\$$/)
|
|
1645
|
+
if (m) {
|
|
1646
|
+
const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
|
|
1647
|
+
if (!rangeCheck) return null
|
|
1648
|
+
return `${varName}.length>0&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
1649
|
+
}
|
|
1650
|
+
// Match: ^[chars]{m,n}$ — e.g., ^[a-zA-Z]{2,50}$
|
|
1651
|
+
m = pattern.match(/^\^(\[[\w\-]+\])\{(\d+),(\d+)\}\$$/)
|
|
1652
|
+
if (m) {
|
|
1653
|
+
const rangeCheck = charClassToCheck(m[1], `${varName}.charCodeAt(_pi)`)
|
|
1654
|
+
if (!rangeCheck) return null
|
|
1655
|
+
const min = parseInt(m[2]), max = parseInt(m[3])
|
|
1656
|
+
return `${varName}.length>=${min}&&${varName}.length<=${max}&&(()=>{for(let _pi=0;_pi<${varName}.length;_pi++){if(!(${rangeCheck}))return false}return true})()`
|
|
1657
|
+
}
|
|
1658
|
+
return null
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// Convert [charclass] to charCode range check expression.
|
|
1662
|
+
// Supports: [0-9], [a-z], [A-Z], [a-zA-Z], [a-zA-Z0-9], [0-9a-f], etc.
|
|
1663
|
+
function charClassToCheck(charClass, codeExpr) {
|
|
1664
|
+
// Strip brackets
|
|
1665
|
+
const inner = charClass.slice(1, -1)
|
|
1666
|
+
// Parse ranges
|
|
1667
|
+
const ranges = []
|
|
1668
|
+
let i = 0
|
|
1669
|
+
while (i < inner.length) {
|
|
1670
|
+
if (i + 2 < inner.length && inner[i + 1] === '-') {
|
|
1671
|
+
ranges.push([inner.charCodeAt(i), inner.charCodeAt(i + 2)])
|
|
1672
|
+
i += 3
|
|
1673
|
+
} else {
|
|
1674
|
+
ranges.push([inner.charCodeAt(i), inner.charCodeAt(i)])
|
|
1675
|
+
i++
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (ranges.length === 0) return null
|
|
1679
|
+
// Generate check: (c >= 48 && c <= 57) || (c >= 65 && c <= 90)
|
|
1680
|
+
const checks = ranges.map(([lo, hi]) =>
|
|
1681
|
+
lo === hi ? `${codeExpr}===${lo}` : `(${codeExpr}>=${lo}&&${codeExpr}<=${hi})`
|
|
1682
|
+
)
|
|
1683
|
+
return checks.join('||')
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Same but for dynamic segments (array indices)
|
|
1687
|
+
function childPathDynExpr(parentExpr, indexExpr) {
|
|
1688
|
+
if (!parentExpr) return `'/'+${indexExpr}`
|
|
1689
|
+
return `${parentExpr}+'/'+${indexExpr}`
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1586
1692
|
// Detect simple prefix patterns like "^x-", "^_", "^prefix" and generate fast charCodeAt checks
|
|
1587
1693
|
// Returns a JS expression string or null if pattern is too complex
|
|
1588
1694
|
function fastPrefixCheck(pattern, keyVar) {
|
|
@@ -1639,7 +1745,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
|
|
|
1639
1745
|
if (typeof schema === 'boolean') {
|
|
1640
1746
|
return schema
|
|
1641
1747
|
? () => ({ valid: true, errors: [] })
|
|
1642
|
-
: () => ({ valid: false, errors: [{
|
|
1748
|
+
: () => ({ valid: false, errors: [{ keyword: 'false schema', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
|
|
1643
1749
|
}
|
|
1644
1750
|
if (typeof schema !== 'object' || schema === null) return null
|
|
1645
1751
|
if (!codegenSafe(schema, schemaMap)) return null
|
|
@@ -1666,7 +1772,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
|
|
|
1666
1772
|
|
|
1667
1773
|
const ctx = { varCounter: 0, helperCode: [], rootDefs: schema.$defs || schema.definitions || null, refStack: new Set(), schemaMap: schemaMap || null }
|
|
1668
1774
|
const lines = []
|
|
1669
|
-
genCodeE(schema, 'd', '', lines, ctx)
|
|
1775
|
+
genCodeE(schema, 'd', '', lines, ctx, '#')
|
|
1670
1776
|
if (lines.length === 0) return (d) => ({ valid: true, errors: [] })
|
|
1671
1777
|
|
|
1672
1778
|
const body = `const _e=[];\n ` +
|
|
@@ -1685,7 +1791,8 @@ function compileToJSCodegenWithErrors(schema, schemaMap) {
|
|
|
1685
1791
|
// Error-collecting code generator.
|
|
1686
1792
|
// Instead of `return false`, pushes to `_e` array and optionally early-returns.
|
|
1687
1793
|
// `_all` parameter: if falsy, return after first error.
|
|
1688
|
-
function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
1794
|
+
function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
1795
|
+
if (!schemaPrefix) schemaPrefix = '#'
|
|
1689
1796
|
if (typeof schema !== 'object' || schema === null) return
|
|
1690
1797
|
|
|
1691
1798
|
// $ref — resolve local and cross-schema refs
|
|
@@ -1694,14 +1801,14 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1694
1801
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
1695
1802
|
if (ctx.refStack.has(schema.$ref)) return
|
|
1696
1803
|
ctx.refStack.add(schema.$ref)
|
|
1697
|
-
genCodeE(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx)
|
|
1804
|
+
genCodeE(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
|
|
1698
1805
|
ctx.refStack.delete(schema.$ref)
|
|
1699
1806
|
return
|
|
1700
1807
|
}
|
|
1701
1808
|
if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
|
|
1702
1809
|
if (ctx.refStack.has(schema.$ref)) return
|
|
1703
1810
|
ctx.refStack.add(schema.$ref)
|
|
1704
|
-
genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx)
|
|
1811
|
+
genCodeE(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
|
|
1705
1812
|
ctx.refStack.delete(schema.$ref)
|
|
1706
1813
|
return
|
|
1707
1814
|
}
|
|
@@ -1722,7 +1829,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1722
1829
|
}
|
|
1723
1830
|
})
|
|
1724
1831
|
const expected = types.join(', ')
|
|
1725
|
-
lines.push(`if(!(${conds.join('||')})){_e.push({
|
|
1832
|
+
lines.push(`if(!(${conds.join('||')})){_e.push({keyword:'type',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/type',params:{type:'${expected}'},message:'must be ${expected}'});if(!_all)return{valid:false,errors:_e}}`)
|
|
1726
1833
|
}
|
|
1727
1834
|
|
|
1728
1835
|
// In error mode, never assume type — always guard (data may have failed type check but allErrors continues)
|
|
@@ -1731,7 +1838,10 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1731
1838
|
const isStr = false
|
|
1732
1839
|
const isNum = false
|
|
1733
1840
|
|
|
1734
|
-
const fail = (
|
|
1841
|
+
const fail = (keyword, schemaSuffix, paramsCode, msgCode) => {
|
|
1842
|
+
const sp = schemaPrefix + '/' + schemaSuffix
|
|
1843
|
+
return `_e.push({keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}});if(!_all)return{valid:false,errors:_e}`
|
|
1844
|
+
}
|
|
1735
1845
|
|
|
1736
1846
|
// enum
|
|
1737
1847
|
if (schema.enum) {
|
|
@@ -1741,21 +1851,21 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1741
1851
|
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
1742
1852
|
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
1743
1853
|
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
1744
|
-
lines.push(`if(!(${allChecks || 'false'})){${fail('
|
|
1854
|
+
lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
1745
1855
|
}
|
|
1746
1856
|
|
|
1747
1857
|
// const — use canonical (sorted-key) comparison for objects
|
|
1748
1858
|
if (schema.const !== undefined) {
|
|
1749
1859
|
const cv = schema.const
|
|
1750
1860
|
if (cv === null || typeof cv !== 'object') {
|
|
1751
|
-
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('
|
|
1861
|
+
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
1752
1862
|
} else {
|
|
1753
1863
|
// Pre-compute canonical form of const value
|
|
1754
1864
|
const ci = ctx.varCounter++
|
|
1755
1865
|
const canonFn = `_cnE${ci}`
|
|
1756
1866
|
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(',')+'}'};`)
|
|
1757
1867
|
const expected = canonFn + '(' + JSON.stringify(cv) + ')'
|
|
1758
|
-
lines.push(`if(${canonFn}(${v})!==${expected}){${fail('
|
|
1868
|
+
lines.push(`if(${canonFn}(${v})!==${expected}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
1759
1869
|
}
|
|
1760
1870
|
}
|
|
1761
1871
|
|
|
@@ -1764,49 +1874,54 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1764
1874
|
const hoisted = {}
|
|
1765
1875
|
if (schema.required) {
|
|
1766
1876
|
for (const key of schema.required) {
|
|
1767
|
-
|
|
1768
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){_e.push({code:'required_missing',path:${p},message:'missing required: ${esc(key)}'});if(!_all)return{valid:false,errors:_e}}`)
|
|
1877
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){_e.push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required',params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"});if(!_all)return{valid:false,errors:_e}}`)
|
|
1769
1878
|
}
|
|
1770
1879
|
}
|
|
1771
1880
|
|
|
1772
1881
|
// numeric
|
|
1773
1882
|
if (schema.minimum !== undefined) {
|
|
1774
1883
|
const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`
|
|
1775
|
-
lines.push(`if(${c}){${fail('
|
|
1884
|
+
lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`)
|
|
1776
1885
|
}
|
|
1777
1886
|
if (schema.maximum !== undefined) {
|
|
1778
1887
|
const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`
|
|
1779
|
-
lines.push(`if(${c}){${fail('
|
|
1888
|
+
lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`)
|
|
1780
1889
|
}
|
|
1781
1890
|
if (schema.exclusiveMinimum !== undefined) {
|
|
1782
1891
|
const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`
|
|
1783
|
-
lines.push(`if(${c}){${fail('
|
|
1892
|
+
lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`)
|
|
1784
1893
|
}
|
|
1785
1894
|
if (schema.exclusiveMaximum !== undefined) {
|
|
1786
1895
|
const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`
|
|
1787
|
-
lines.push(`if(${c}){${fail('
|
|
1896
|
+
lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`)
|
|
1788
1897
|
}
|
|
1789
1898
|
if (schema.multipleOf !== undefined) {
|
|
1790
1899
|
const m = schema.multipleOf
|
|
1791
1900
|
const ci = ctx.varCounter++
|
|
1792
1901
|
// Use tolerance-based check for floating point (matches C++ behavior)
|
|
1793
|
-
lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('
|
|
1902
|
+
lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
|
|
1794
1903
|
}
|
|
1795
1904
|
|
|
1796
1905
|
// string
|
|
1797
1906
|
if (schema.minLength !== undefined) {
|
|
1798
1907
|
const c = isStr ? `${v}.length<${schema.minLength}` : `typeof ${v}==='string'&&${v}.length<${schema.minLength}`
|
|
1799
|
-
lines.push(`if(${c}){${fail('
|
|
1908
|
+
lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${schema.minLength}}`, `'must NOT have fewer than ${schema.minLength} characters'`)}}`)
|
|
1800
1909
|
}
|
|
1801
1910
|
if (schema.maxLength !== undefined) {
|
|
1802
1911
|
const c = isStr ? `${v}.length>${schema.maxLength}` : `typeof ${v}==='string'&&${v}.length>${schema.maxLength}`
|
|
1803
|
-
lines.push(`if(${c}){${fail('
|
|
1912
|
+
lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${schema.maxLength}}`, `'must NOT have more than ${schema.maxLength} characters'`)}}`)
|
|
1804
1913
|
}
|
|
1805
1914
|
if (schema.pattern) {
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1915
|
+
const inlineCheck = compilePatternInline(schema.pattern, v)
|
|
1916
|
+
if (inlineCheck) {
|
|
1917
|
+
const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
|
|
1918
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
|
|
1919
|
+
} else {
|
|
1920
|
+
const ri = ctx.varCounter++
|
|
1921
|
+
ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(schema.pattern)})`)
|
|
1922
|
+
const c = isStr ? `!_re${ri}.test(${v})` : `typeof ${v}==='string'&&!_re${ri}.test(${v})`
|
|
1923
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
|
|
1924
|
+
}
|
|
1810
1925
|
}
|
|
1811
1926
|
if (schema.format) {
|
|
1812
1927
|
const fc = FORMAT_CODEGEN[schema.format]
|
|
@@ -1817,7 +1932,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1817
1932
|
boolLines.push(fc(v, isStr))
|
|
1818
1933
|
// Replace `return false` with error push in the format check
|
|
1819
1934
|
const fmtCode = boolLines.join(';').replace(/return false/g,
|
|
1820
|
-
`{_e.push({
|
|
1935
|
+
`{_e.push({keyword:'format',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/format',params:{format:'${esc(schema.format)}'},message:'must match format "${esc(schema.format)}"'});if(!_all)return{valid:false,errors:_e}}`)
|
|
1821
1936
|
lines.push(fmtCode)
|
|
1822
1937
|
}
|
|
1823
1938
|
}
|
|
@@ -1825,37 +1940,44 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1825
1940
|
// array size
|
|
1826
1941
|
if (schema.minItems !== undefined) {
|
|
1827
1942
|
const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`
|
|
1828
|
-
lines.push(`if(${c}){${fail('
|
|
1943
|
+
lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`)
|
|
1829
1944
|
}
|
|
1830
1945
|
if (schema.maxItems !== undefined) {
|
|
1831
1946
|
const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`
|
|
1832
|
-
lines.push(`if(${c}){${fail('
|
|
1947
|
+
lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`)
|
|
1833
1948
|
}
|
|
1834
1949
|
|
|
1835
|
-
// uniqueItems
|
|
1950
|
+
// uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
|
|
1836
1951
|
if (schema.uniqueItems) {
|
|
1837
1952
|
const si = ctx.varCounter++
|
|
1838
1953
|
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
1839
1954
|
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1955
|
+
const maxItems = schema.maxItems
|
|
1956
|
+
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
1957
|
+
let inner
|
|
1958
|
+
if (isPrim && maxItems && maxItems <= 16) {
|
|
1959
|
+
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
|
|
1960
|
+
} else if (isPrim) {
|
|
1961
|
+
inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
|
|
1962
|
+
} else {
|
|
1963
|
+
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 Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
|
|
1964
|
+
}
|
|
1843
1965
|
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
1844
1966
|
}
|
|
1845
1967
|
|
|
1846
1968
|
// object size
|
|
1847
1969
|
if (schema.minProperties !== undefined) {
|
|
1848
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('
|
|
1970
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('minProperties', 'minProperties', `{limit:${schema.minProperties}}`, `'must NOT have fewer than ${schema.minProperties} properties'`)}}`)
|
|
1849
1971
|
}
|
|
1850
1972
|
if (schema.maxProperties !== undefined) {
|
|
1851
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('
|
|
1973
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('maxProperties', 'maxProperties', `{limit:${schema.maxProperties}}`, `'must NOT have more than ${schema.maxProperties} properties'`)}}`)
|
|
1852
1974
|
}
|
|
1853
1975
|
|
|
1854
1976
|
// additionalProperties: false
|
|
1855
1977
|
if (schema.additionalProperties === false && schema.properties) {
|
|
1856
1978
|
const allowed = Object.keys(schema.properties).map(k => `${JSON.stringify(k)}`).join(',')
|
|
1857
1979
|
const ci = ctx.varCounter++
|
|
1858
|
-
const inner = `const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++){if(!_a${ci}.has(_k${ci}[_i])){_e.push({
|
|
1980
|
+
const inner = `const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++){if(!_a${ci}.has(_k${ci}[_i])){_e.push({keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/additionalProperties',params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties'});if(!_all)return{valid:false,errors:_e}}}`
|
|
1859
1981
|
lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
1860
1982
|
}
|
|
1861
1983
|
|
|
@@ -1863,8 +1985,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1863
1985
|
if (schema.dependentRequired) {
|
|
1864
1986
|
for (const [key, deps] of Object.entries(schema.dependentRequired)) {
|
|
1865
1987
|
for (const dep of deps) {
|
|
1866
|
-
|
|
1867
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){_e.push({code:'required_missing',path:${p},message:'${esc(key)} requires ${esc(dep)}'});if(!_all)return{valid:false,errors:_e}}`)
|
|
1988
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){_e.push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired',params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"});if(!_all)return{valid:false,errors:_e}}`)
|
|
1868
1989
|
}
|
|
1869
1990
|
}
|
|
1870
1991
|
}
|
|
@@ -1872,9 +1993,9 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1872
1993
|
// properties — always guard (error mode, data may not be an object or may be array)
|
|
1873
1994
|
if (schema.properties) {
|
|
1874
1995
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1875
|
-
const childPath = pathExpr
|
|
1996
|
+
const childPath = childPathExpr(pathExpr, esc(key))
|
|
1876
1997
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
|
|
1877
|
-
genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx)
|
|
1998
|
+
genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
|
|
1878
1999
|
lines.push(`}`)
|
|
1879
2000
|
}
|
|
1880
2001
|
}
|
|
@@ -1887,7 +2008,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1887
2008
|
const ki = ctx.varCounter++
|
|
1888
2009
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){if(_re${ri}.test(_k${ki})){`)
|
|
1889
2010
|
const p = pathExpr ? `${pathExpr}+'/'+_k${ki}` : `'/'+_k${ki}`
|
|
1890
|
-
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx)
|
|
2011
|
+
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix+'/patternProperties')
|
|
1891
2012
|
lines.push(`}}}`)
|
|
1892
2013
|
}
|
|
1893
2014
|
}
|
|
@@ -1896,7 +2017,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1896
2017
|
if (schema.dependentSchemas) {
|
|
1897
2018
|
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
1898
2019
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
|
|
1899
|
-
genCodeE(depSchema, v, pathExpr, lines, ctx)
|
|
2020
|
+
genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+key)
|
|
1900
2021
|
lines.push(`}`)
|
|
1901
2022
|
}
|
|
1902
2023
|
}
|
|
@@ -1907,23 +2028,23 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1907
2028
|
const ki = ctx.varCounter++
|
|
1908
2029
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
1909
2030
|
if (pn.minLength !== undefined) {
|
|
1910
|
-
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('
|
|
2031
|
+
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
1911
2032
|
}
|
|
1912
2033
|
if (pn.maxLength !== undefined) {
|
|
1913
|
-
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('
|
|
2034
|
+
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
1914
2035
|
}
|
|
1915
2036
|
if (pn.pattern) {
|
|
1916
2037
|
const ri = ctx.varCounter++
|
|
1917
2038
|
ctx.helperCode.push(`const _re${ri}=new RegExp(${JSON.stringify(pn.pattern)})`)
|
|
1918
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('
|
|
2039
|
+
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
1919
2040
|
}
|
|
1920
2041
|
if (pn.const !== undefined) {
|
|
1921
|
-
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('
|
|
2042
|
+
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
1922
2043
|
}
|
|
1923
2044
|
if (pn.enum) {
|
|
1924
2045
|
const ei = ctx.varCounter++
|
|
1925
2046
|
ctx.helperCode.push(`const _es${ei}=new Set(${JSON.stringify(pn.enum)})`)
|
|
1926
|
-
lines.push(`if(!_es${ei}.has(_k${ki})){${fail('
|
|
2047
|
+
lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
1927
2048
|
}
|
|
1928
2049
|
lines.push(`}}`)
|
|
1929
2050
|
}
|
|
@@ -1934,18 +2055,18 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1934
2055
|
const idx = `_j${ctx.varCounter}`
|
|
1935
2056
|
const elem = `_ei${ctx.varCounter}`
|
|
1936
2057
|
ctx.varCounter++
|
|
1937
|
-
const childPath = pathExpr
|
|
2058
|
+
const childPath = childPathDynExpr(pathExpr, idx)
|
|
1938
2059
|
lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
|
|
1939
|
-
genCodeE(schema.items, elem, childPath, lines, ctx)
|
|
2060
|
+
genCodeE(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
|
|
1940
2061
|
lines.push(`}}`)
|
|
1941
2062
|
}
|
|
1942
2063
|
|
|
1943
2064
|
// prefixItems
|
|
1944
2065
|
if (schema.prefixItems) {
|
|
1945
2066
|
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
1946
|
-
const childPath = pathExpr
|
|
2067
|
+
const childPath = childPathExpr(pathExpr, String(i))
|
|
1947
2068
|
lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
|
|
1948
|
-
genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx)
|
|
2069
|
+
genCodeE(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
|
|
1949
2070
|
lines.push(`}`)
|
|
1950
2071
|
}
|
|
1951
2072
|
}
|
|
@@ -1959,17 +2080,17 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1959
2080
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
1960
2081
|
const maxC = schema.maxContains
|
|
1961
2082
|
lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
|
|
1962
|
-
lines.push(`if(_cc${ci}<${minC}){${fail('
|
|
2083
|
+
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{limit:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
|
|
1963
2084
|
if (maxC !== undefined) {
|
|
1964
|
-
lines.push(`if(_cc${ci}>${maxC}){${fail('
|
|
2085
|
+
lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{limit:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
|
|
1965
2086
|
}
|
|
1966
2087
|
lines.push(`}`)
|
|
1967
2088
|
}
|
|
1968
2089
|
|
|
1969
2090
|
// allOf
|
|
1970
2091
|
if (schema.allOf) {
|
|
1971
|
-
for (
|
|
1972
|
-
genCodeE(
|
|
2092
|
+
for (let _ai = 0; _ai < schema.allOf.length; _ai++) {
|
|
2093
|
+
genCodeE(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai)
|
|
1973
2094
|
}
|
|
1974
2095
|
}
|
|
1975
2096
|
|
|
@@ -1981,7 +2102,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1981
2102
|
genCode(sub, '_av', subLines, ctx)
|
|
1982
2103
|
return subLines.length === 0 ? `function(_av){return true}` : `function(_av){${subLines.join(';')};return true}`
|
|
1983
2104
|
})
|
|
1984
|
-
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am${fi}=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am${fi}=true;break}}if(!_am${fi}){${fail('
|
|
2105
|
+
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am${fi}=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am${fi}=true;break}}if(!_am${fi}){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
|
|
1985
2106
|
}
|
|
1986
2107
|
|
|
1987
2108
|
// oneOf
|
|
@@ -1992,7 +2113,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
1992
2113
|
genCode(sub, '_ov', subLines, ctx)
|
|
1993
2114
|
return subLines.length === 0 ? `function(_ov){return true}` : `function(_ov){${subLines.join(';')};return true}`
|
|
1994
2115
|
})
|
|
1995
|
-
lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc${fi}=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc${fi}++;if(_oc${fi}>1)break}if(_oc${fi}!==1){${fail('
|
|
2116
|
+
lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc${fi}=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc${fi}++;if(_oc${fi}>1)break}if(_oc${fi}!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
|
|
1996
2117
|
}
|
|
1997
2118
|
|
|
1998
2119
|
// not
|
|
@@ -2001,7 +2122,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
2001
2122
|
genCode(schema.not, '_nv', subLines, ctx)
|
|
2002
2123
|
const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
|
|
2003
2124
|
const fi = ctx.varCounter++
|
|
2004
|
-
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('
|
|
2125
|
+
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
2005
2126
|
}
|
|
2006
2127
|
|
|
2007
2128
|
// if/then/else
|
|
@@ -2015,12 +2136,12 @@ function genCodeE(schema, v, pathExpr, lines, ctx) {
|
|
|
2015
2136
|
lines.push(`{const _if${fi}=${ifFn}`)
|
|
2016
2137
|
if (schema.then) {
|
|
2017
2138
|
lines.push(`if(_if${fi}(${v})){`)
|
|
2018
|
-
genCodeE(schema.then, v, pathExpr, lines, ctx)
|
|
2139
|
+
genCodeE(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then')
|
|
2019
2140
|
lines.push(`}`)
|
|
2020
2141
|
}
|
|
2021
2142
|
if (schema.else) {
|
|
2022
2143
|
lines.push(`${schema.then ? 'else' : `if(!_if${fi}(${v}))`}{`)
|
|
2023
|
-
genCodeE(schema.else, v, pathExpr, lines, ctx)
|
|
2144
|
+
genCodeE(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else')
|
|
2024
2145
|
lines.push(`}`)
|
|
2025
2146
|
}
|
|
2026
2147
|
lines.push(`}`)
|
|
@@ -2040,7 +2161,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
|
|
|
2040
2161
|
if (typeof schema === 'boolean') {
|
|
2041
2162
|
return schema
|
|
2042
2163
|
? () => VALID_RESULT
|
|
2043
|
-
: () => ({ valid: false, errors: [{
|
|
2164
|
+
: () => ({ valid: false, errors: [{ keyword: 'false schema', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
|
|
2044
2165
|
}
|
|
2045
2166
|
if (typeof schema !== 'object' || schema === null) return null
|
|
2046
2167
|
if (!codegenSafe(schema, schemaMap)) return null
|
|
@@ -2068,7 +2189,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
|
|
|
2068
2189
|
const ctx = { varCounter: 0, helperCode: [], closureVars: [], closureVals: [],
|
|
2069
2190
|
rootDefs: schema.$defs || schema.definitions || null, refStack: new Set(), schemaMap: schemaMap || null }
|
|
2070
2191
|
const lines = []
|
|
2071
|
-
genCodeC(schema, 'd', '', lines, ctx)
|
|
2192
|
+
genCodeC(schema, 'd', '', lines, ctx, '#')
|
|
2072
2193
|
if (lines.length === 0) return () => VALID_RESULT
|
|
2073
2194
|
|
|
2074
2195
|
// Use factory pattern: closure vars (regexes, etc.) created once, not per call
|
|
@@ -2080,6 +2201,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
|
|
|
2080
2201
|
`\n return _e?{valid:false,errors:_e}:R`
|
|
2081
2202
|
|
|
2082
2203
|
try {
|
|
2204
|
+
if (process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
2083
2205
|
const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
|
|
2084
2206
|
`return function(d){${inner}}`)
|
|
2085
2207
|
return factory(VALID_RESULT, ...ctx.closureVals)
|
|
@@ -2092,7 +2214,8 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap) {
|
|
|
2092
2214
|
// Combined code generator: type-aware like genCode, error-collecting like genCodeE.
|
|
2093
2215
|
// After type check passes → use optimizations (destructuring, no guards).
|
|
2094
2216
|
// If type check fails → push error, skip property checks (they'd crash).
|
|
2095
|
-
function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
2217
|
+
function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
2218
|
+
if (!schemaPrefix) schemaPrefix = '#'
|
|
2096
2219
|
if (typeof schema !== 'object' || schema === null) return
|
|
2097
2220
|
|
|
2098
2221
|
// $ref — resolve local and cross-schema refs
|
|
@@ -2101,14 +2224,14 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2101
2224
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
2102
2225
|
if (ctx.refStack.has(schema.$ref)) return
|
|
2103
2226
|
ctx.refStack.add(schema.$ref)
|
|
2104
|
-
genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx)
|
|
2227
|
+
genCodeC(ctx.rootDefs[m[1]], v, pathExpr, lines, ctx, schemaPrefix)
|
|
2105
2228
|
ctx.refStack.delete(schema.$ref)
|
|
2106
2229
|
return
|
|
2107
2230
|
}
|
|
2108
2231
|
if (ctx.schemaMap && ctx.schemaMap.has(schema.$ref)) {
|
|
2109
2232
|
if (ctx.refStack.has(schema.$ref)) return
|
|
2110
2233
|
ctx.refStack.add(schema.$ref)
|
|
2111
|
-
genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx)
|
|
2234
|
+
genCodeC(ctx.schemaMap.get(schema.$ref), v, pathExpr, lines, ctx, schemaPrefix)
|
|
2112
2235
|
ctx.refStack.delete(schema.$ref)
|
|
2113
2236
|
return
|
|
2114
2237
|
}
|
|
@@ -2120,19 +2243,25 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2120
2243
|
// Pre-allocate error objects as closure variables for static paths.
|
|
2121
2244
|
// This shrinks the generated function body → better V8 JIT on valid path.
|
|
2122
2245
|
const isStaticPath = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
|
|
2123
|
-
const fail = (
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2246
|
+
const fail = (keyword, schemaSuffix, paramsCode, msgCode) => {
|
|
2247
|
+
const sp = schemaPrefix + '/' + schemaSuffix
|
|
2248
|
+
if (isStaticPath && msgCode.startsWith("'") && !msgCode.includes('+')) {
|
|
2249
|
+
// Try to evaluate paramsCode as a static constant
|
|
2250
|
+
let paramsVal
|
|
2251
|
+
try { paramsVal = Function('return ' + paramsCode)() } catch { /* dynamic params — fall through */ }
|
|
2252
|
+
if (paramsVal !== undefined) {
|
|
2253
|
+
// Static error: pre-allocate as frozen closure variable
|
|
2254
|
+
const ei = ctx.varCounter++
|
|
2255
|
+
const errVar = `_E${ei}`
|
|
2256
|
+
const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
|
|
2257
|
+
const msgVal = msgCode.slice(1, -1)
|
|
2258
|
+
ctx.closureVars.push(errVar)
|
|
2259
|
+
ctx.closureVals.push(Object.freeze({keyword, instancePath: pathVal, schemaPath: sp, params: Object.freeze(paramsVal), message: msgVal}))
|
|
2260
|
+
return `(_e||(_e=[])).push(${errVar})`
|
|
2261
|
+
}
|
|
2133
2262
|
}
|
|
2134
2263
|
// Dynamic path (e.g., array index): inline as before
|
|
2135
|
-
return `(_e||(_e=[])).push({
|
|
2264
|
+
return `(_e||(_e=[])).push({keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}})`
|
|
2136
2265
|
}
|
|
2137
2266
|
|
|
2138
2267
|
if (types) {
|
|
@@ -2152,7 +2281,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2152
2281
|
// Type check: push error but continue — wrap remaining in type-success block
|
|
2153
2282
|
const typeOk = `_tok${ctx.varCounter++}`
|
|
2154
2283
|
lines.push(`const ${typeOk}=${conds.join('||')}`)
|
|
2155
|
-
lines.push(`if(!${typeOk}){${fail('
|
|
2284
|
+
lines.push(`if(!${typeOk}){${fail('type', 'type', `{type:'${expected}'}`, `'must be ${expected}'`)}}`)
|
|
2156
2285
|
// Subsequent optimized code runs inside if(typeOk){...}
|
|
2157
2286
|
if (types.length === 1) {
|
|
2158
2287
|
isObj = types[0] === 'object'
|
|
@@ -2171,19 +2300,19 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2171
2300
|
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
2172
2301
|
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
2173
2302
|
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
2174
|
-
lines.push(`if(!(${allChecks || 'false'})){${fail('
|
|
2303
|
+
lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
2175
2304
|
}
|
|
2176
2305
|
|
|
2177
2306
|
// const
|
|
2178
2307
|
if (schema.const !== undefined) {
|
|
2179
2308
|
const cv = schema.const
|
|
2180
2309
|
if (cv === null || typeof cv !== 'object') {
|
|
2181
|
-
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('
|
|
2310
|
+
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
2182
2311
|
} else {
|
|
2183
2312
|
const ci = ctx.varCounter++
|
|
2184
2313
|
const canonFn = `_cn${ci}`
|
|
2185
2314
|
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(',')+'}'};`)
|
|
2186
|
-
lines.push(`if(${canonFn}(${v})!==${canonFn}(${JSON.stringify(cv)})){${fail('
|
|
2315
|
+
lines.push(`if(${canonFn}(${v})!==${canonFn}(${JSON.stringify(cv)})){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
2187
2316
|
}
|
|
2188
2317
|
}
|
|
2189
2318
|
|
|
@@ -2202,80 +2331,127 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2202
2331
|
if (destructKeys.length > 0) lines.push(`const{${destructKeys.join(',')}}=${v}`)
|
|
2203
2332
|
for (const key of schema.required) {
|
|
2204
2333
|
const check = hoisted[key] ? `${hoisted[key]}===undefined` : `${v}[${JSON.stringify(key)}]===undefined`
|
|
2205
|
-
|
|
2206
|
-
|
|
2334
|
+
// Pre-allocate required errors as frozen closure variables
|
|
2335
|
+
const ei = ctx.varCounter++
|
|
2336
|
+
const errVar = `_E${ei}`
|
|
2337
|
+
const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
|
|
2338
|
+
ctx.closureVars.push(errVar)
|
|
2339
|
+
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/required`, params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}))
|
|
2340
|
+
lines.push(`if(${check}){(_e||(_e=[])).push(${errVar})}`)
|
|
2207
2341
|
}
|
|
2208
2342
|
} else if (schema.required) {
|
|
2209
2343
|
for (const key of schema.required) {
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2344
|
+
const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
|
|
2345
|
+
if (isStatic) {
|
|
2346
|
+
const ei = ctx.varCounter++
|
|
2347
|
+
const errVar = `_E${ei}`
|
|
2348
|
+
const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
|
|
2349
|
+
ctx.closureVars.push(errVar)
|
|
2350
|
+
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/required`, params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}))
|
|
2351
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){(_e||(_e=[])).push(${errVar})}`)
|
|
2352
|
+
} else {
|
|
2353
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!(${JSON.stringify(key)} in ${v})){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required',params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"})}`)
|
|
2354
|
+
}
|
|
2212
2355
|
}
|
|
2213
2356
|
}
|
|
2214
2357
|
|
|
2215
2358
|
// numeric — skip type guard if known
|
|
2216
|
-
if (schema.minimum !== undefined) { const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`; lines.push(`if(${c}){${fail('
|
|
2217
|
-
if (schema.maximum !== undefined) { const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`; lines.push(`if(${c}){${fail('
|
|
2218
|
-
if (schema.exclusiveMinimum !== undefined) { const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`; lines.push(`if(${c}){${fail('
|
|
2219
|
-
if (schema.exclusiveMaximum !== undefined) { const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`; lines.push(`if(${c}){${fail('
|
|
2359
|
+
if (schema.minimum !== undefined) { const c = isNum ? `${v}<${schema.minimum}` : `typeof ${v}==='number'&&${v}<${schema.minimum}`; lines.push(`if(${c}){${fail('minimum', 'minimum', `{comparison:'>=',limit:${schema.minimum}}`, `'must be >= ${schema.minimum}'`)}}`) }
|
|
2360
|
+
if (schema.maximum !== undefined) { const c = isNum ? `${v}>${schema.maximum}` : `typeof ${v}==='number'&&${v}>${schema.maximum}`; lines.push(`if(${c}){${fail('maximum', 'maximum', `{comparison:'<=',limit:${schema.maximum}}`, `'must be <= ${schema.maximum}'`)}}`) }
|
|
2361
|
+
if (schema.exclusiveMinimum !== undefined) { const c = isNum ? `${v}<=${schema.exclusiveMinimum}` : `typeof ${v}==='number'&&${v}<=${schema.exclusiveMinimum}`; lines.push(`if(${c}){${fail('exclusiveMinimum', 'exclusiveMinimum', `{comparison:'>',limit:${schema.exclusiveMinimum}}`, `'must be > ${schema.exclusiveMinimum}'`)}}`) }
|
|
2362
|
+
if (schema.exclusiveMaximum !== undefined) { const c = isNum ? `${v}>=${schema.exclusiveMaximum}` : `typeof ${v}==='number'&&${v}>=${schema.exclusiveMaximum}`; lines.push(`if(${c}){${fail('exclusiveMaximum', 'exclusiveMaximum', `{comparison:'<',limit:${schema.exclusiveMaximum}}`, `'must be < ${schema.exclusiveMaximum}'`)}}`) }
|
|
2220
2363
|
if (schema.multipleOf !== undefined) {
|
|
2221
2364
|
const m = schema.multipleOf
|
|
2222
2365
|
const ci = ctx.varCounter++
|
|
2223
|
-
lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('
|
|
2366
|
+
lines.push(`{const _r${ci}=typeof ${v}==='number'?${v}%${m}:NaN;if(typeof ${v}==='number'&&Math.abs(_r${ci})>1e-8&&Math.abs(_r${ci}-${m})>1e-8){${fail('multipleOf', 'multipleOf', `{multipleOf:${m}}`, `'must be multiple of ${m}'`)}}}`)
|
|
2224
2367
|
}
|
|
2225
2368
|
|
|
2226
2369
|
// string — skip guard if known
|
|
2227
|
-
if (schema.minLength !== undefined) { const c = isStr ? `${v}.length<${schema.minLength}` : `typeof ${v}==='string'&&${v}.length<${schema.minLength}`; lines.push(`if(${c}){${fail('
|
|
2228
|
-
if (schema.maxLength !== undefined) { const c = isStr ? `${v}.length>${schema.maxLength}` : `typeof ${v}==='string'&&${v}.length>${schema.maxLength}`; lines.push(`if(${c}){${fail('
|
|
2370
|
+
if (schema.minLength !== undefined) { const c = isStr ? `${v}.length<${schema.minLength}` : `typeof ${v}==='string'&&${v}.length<${schema.minLength}`; lines.push(`if(${c}){${fail('minLength', 'minLength', `{limit:${schema.minLength}}`, `'must NOT have fewer than ${schema.minLength} characters'`)}}`) }
|
|
2371
|
+
if (schema.maxLength !== undefined) { const c = isStr ? `${v}.length>${schema.maxLength}` : `typeof ${v}==='string'&&${v}.length>${schema.maxLength}`; lines.push(`if(${c}){${fail('maxLength', 'maxLength', `{limit:${schema.maxLength}}`, `'must NOT have more than ${schema.maxLength} characters'`)}}`) }
|
|
2229
2372
|
if (schema.pattern) {
|
|
2230
|
-
const
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2373
|
+
const inlineCheck = compilePatternInline(schema.pattern, v)
|
|
2374
|
+
if (inlineCheck) {
|
|
2375
|
+
const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
|
|
2376
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
|
|
2377
|
+
} else {
|
|
2378
|
+
const ri = ctx.varCounter++
|
|
2379
|
+
const reVar = `_re${ri}`
|
|
2380
|
+
ctx.closureVars.push(reVar)
|
|
2381
|
+
ctx.closureVals.push(new RegExp(schema.pattern))
|
|
2382
|
+
const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
|
|
2383
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `'must match pattern "${schema.pattern}"'`)}}`)
|
|
2384
|
+
}
|
|
2236
2385
|
}
|
|
2237
2386
|
if (schema.format) {
|
|
2238
2387
|
const fc = FORMAT_CODEGEN[schema.format]
|
|
2239
2388
|
if (fc) {
|
|
2240
|
-
const code = fc(v, isStr).replace(/return false/g, `{${fail('
|
|
2389
|
+
const code = fc(v, isStr).replace(/return false/g, `{${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)}}`)
|
|
2241
2390
|
lines.push(code)
|
|
2242
2391
|
}
|
|
2243
2392
|
}
|
|
2244
2393
|
|
|
2245
2394
|
// array size
|
|
2246
|
-
if (schema.minItems !== undefined) { const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`; lines.push(`if(${c}){${fail('
|
|
2247
|
-
if (schema.maxItems !== undefined) { const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`; lines.push(`if(${c}){${fail('
|
|
2395
|
+
if (schema.minItems !== undefined) { const c = isArr ? `${v}.length<${schema.minItems}` : `Array.isArray(${v})&&${v}.length<${schema.minItems}`; lines.push(`if(${c}){${fail('minItems', 'minItems', `{limit:${schema.minItems}}`, `'must NOT have fewer than ${schema.minItems} items'`)}}`) }
|
|
2396
|
+
if (schema.maxItems !== undefined) { const c = isArr ? `${v}.length>${schema.maxItems}` : `Array.isArray(${v})&&${v}.length>${schema.maxItems}`; lines.push(`if(${c}){${fail('maxItems', 'maxItems', `{limit:${schema.maxItems}}`, `'must NOT have more than ${schema.maxItems} items'`)}}`) }
|
|
2248
2397
|
|
|
2249
|
-
// uniqueItems
|
|
2398
|
+
// uniqueItems — tiered: small primitive arrays use nested loop (no allocation)
|
|
2250
2399
|
if (schema.uniqueItems) {
|
|
2251
2400
|
const si = ctx.varCounter++
|
|
2252
2401
|
const itemType = schema.items && typeof schema.items === 'object' && schema.items.type
|
|
2253
2402
|
const isPrim = itemType === 'string' || itemType === 'number' || itemType === 'integer'
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2403
|
+
const maxItems = schema.maxItems
|
|
2404
|
+
const failExpr = (iVar, jVar) => fail('uniqueItems', 'uniqueItems', `{i:${iVar},j:${jVar}}`, `'must NOT have duplicate items (items ## '+${jVar}+' and '+${iVar}+' are identical)'`)
|
|
2405
|
+
let inner
|
|
2406
|
+
if (isPrim && maxItems && maxItems <= 16) {
|
|
2407
|
+
// Small primitive arrays: O(n²) nested loop, zero allocation
|
|
2408
|
+
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k]){${failExpr('_k', '_i')};break}}}`
|
|
2409
|
+
} else if (isPrim) {
|
|
2410
|
+
inner = `const _s${si}=new Map();for(let _i=0;_i<${v}.length;_i++){const _prev=_s${si}.get(${v}[_i]);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(${v}[_i],_i)}`
|
|
2411
|
+
} else {
|
|
2412
|
+
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 Map();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);const _prev=_s${si}.get(_k);if(_prev!==undefined){${failExpr('_prev', '_i')};break};_s${si}.set(_k,_i)}`
|
|
2413
|
+
}
|
|
2257
2414
|
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
2258
2415
|
}
|
|
2259
2416
|
|
|
2260
2417
|
// object size
|
|
2261
|
-
if (schema.minProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('
|
|
2262
|
-
if (schema.maxProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('
|
|
2418
|
+
if (schema.minProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length<${schema.minProperties}){${fail('minProperties', 'minProperties', `{limit:${schema.minProperties}}`, `'must NOT have fewer than ${schema.minProperties} properties'`)}}`)
|
|
2419
|
+
if (schema.maxProperties !== undefined) lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&Object.keys(${v}).length>${schema.maxProperties}){${fail('maxProperties', 'maxProperties', `{limit:${schema.maxProperties}}`, `'must NOT have more than ${schema.maxProperties} properties'`)}}`)
|
|
2263
2420
|
|
|
2264
2421
|
// additionalProperties — skip if patternProperties present (handled in unified loop below)
|
|
2422
|
+
// Small property sets: direct === chain (no Set allocation)
|
|
2265
2423
|
if (schema.additionalProperties === false && schema.properties && !schema.patternProperties) {
|
|
2266
|
-
const
|
|
2424
|
+
const propKeys = Object.keys(schema.properties)
|
|
2267
2425
|
const ci = ctx.varCounter++
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2426
|
+
if (propKeys.length <= 8) {
|
|
2427
|
+
// Direct chain: no Set allocation for small schemas
|
|
2428
|
+
const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&')
|
|
2429
|
+
lines.push(isObj
|
|
2430
|
+
? `{const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++)if(${checks}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`
|
|
2431
|
+
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++)if(${checks}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`)
|
|
2432
|
+
} else {
|
|
2433
|
+
const allowed = propKeys.map(k => JSON.stringify(k)).join(',')
|
|
2434
|
+
lines.push(isObj
|
|
2435
|
+
? `{const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i])){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`
|
|
2436
|
+
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i])){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:_k${ci}[_i]}`, "'must NOT have additional properties'")}}}`)
|
|
2437
|
+
}
|
|
2271
2438
|
}
|
|
2272
2439
|
|
|
2273
2440
|
// dependentRequired
|
|
2274
2441
|
if (schema.dependentRequired) {
|
|
2275
2442
|
for (const [key, deps] of Object.entries(schema.dependentRequired)) {
|
|
2276
2443
|
for (const dep of deps) {
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2444
|
+
const isStatic = !pathExpr || (pathExpr.startsWith("'") && !pathExpr.includes('+'))
|
|
2445
|
+
if (isStatic) {
|
|
2446
|
+
const ei = ctx.varCounter++
|
|
2447
|
+
const errVar = `_E${ei}`
|
|
2448
|
+
const pathVal = pathExpr ? pathExpr.slice(1, -1) : ''
|
|
2449
|
+
ctx.closureVars.push(errVar)
|
|
2450
|
+
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/dependentRequired`, params: Object.freeze({missingProperty: dep}), message: `must have required property '${dep}'`}))
|
|
2451
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){(_e||(_e=[])).push(${errVar})}`)
|
|
2452
|
+
} else {
|
|
2453
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${JSON.stringify(key)} in ${v}&&!(${JSON.stringify(dep)} in ${v})){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired',params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"})}`)
|
|
2454
|
+
}
|
|
2279
2455
|
}
|
|
2280
2456
|
}
|
|
2281
2457
|
}
|
|
@@ -2284,19 +2460,19 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2284
2460
|
if (schema.properties) {
|
|
2285
2461
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
2286
2462
|
const pv = hoisted[key] || `${v}[${JSON.stringify(key)}]`
|
|
2287
|
-
const childPath = pathExpr
|
|
2463
|
+
const childPath = childPathExpr(pathExpr, esc(key))
|
|
2288
2464
|
if (requiredSet.has(key) && isObj) {
|
|
2289
2465
|
lines.push(`if(${pv}!==undefined){`)
|
|
2290
|
-
genCodeC(prop, pv, childPath, lines, ctx)
|
|
2466
|
+
genCodeC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
|
|
2291
2467
|
lines.push(`}`)
|
|
2292
2468
|
} else if (isObj) {
|
|
2293
2469
|
const oi = ctx.varCounter++
|
|
2294
2470
|
lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined){`)
|
|
2295
|
-
genCodeC(prop, `_o${oi}`, childPath, lines, ctx)
|
|
2471
|
+
genCodeC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
|
|
2296
2472
|
lines.push(`}}`)
|
|
2297
2473
|
} else {
|
|
2298
2474
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
|
|
2299
|
-
genCodeC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx)
|
|
2475
|
+
genCodeC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+key)
|
|
2300
2476
|
lines.push(`}`)
|
|
2301
2477
|
}
|
|
2302
2478
|
}
|
|
@@ -2353,61 +2529,61 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2353
2529
|
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
2354
2530
|
// propertyNames checks (merged)
|
|
2355
2531
|
if (pn) {
|
|
2356
|
-
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('
|
|
2357
|
-
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('
|
|
2532
|
+
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
2533
|
+
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
2358
2534
|
if (pn.pattern) {
|
|
2359
2535
|
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
2360
2536
|
if (fast) {
|
|
2361
|
-
lines.push(`if(!(${fast})){${fail('
|
|
2537
|
+
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
2362
2538
|
} else {
|
|
2363
2539
|
const ri = ctx.varCounter++
|
|
2364
2540
|
ctx.closureVars.push(`_re${ri}`)
|
|
2365
2541
|
ctx.closureVals.push(new RegExp(pn.pattern))
|
|
2366
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('
|
|
2542
|
+
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
2367
2543
|
}
|
|
2368
2544
|
}
|
|
2369
|
-
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('
|
|
2545
|
+
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
2370
2546
|
if (pn.enum) {
|
|
2371
2547
|
const ei = ctx.varCounter++
|
|
2372
2548
|
ctx.closureVars.push(`_es${ei}`)
|
|
2373
2549
|
ctx.closureVals.push(new Set(pn.enum))
|
|
2374
|
-
lines.push(`if(!_es${ei}.has(${kVar})){${fail('
|
|
2550
|
+
lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
2375
2551
|
}
|
|
2376
2552
|
}
|
|
2377
2553
|
const matchExpr = keyCheck || `_as${pi}.has(${kVar})`
|
|
2378
2554
|
lines.push(`let _m${pi}=${matchExpr}`)
|
|
2379
2555
|
for (let i = 0; i < ppEntries.length; i++) {
|
|
2380
|
-
lines.push(`if(${matchers[i].check}){_m${pi}=true;if(!_ppf${pi}_${i}(${v}[${kVar}])){${fail('
|
|
2556
|
+
lines.push(`if(${matchers[i].check}){_m${pi}=true;if(!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern', 'patternProperties', `{pattern:'${ppEntries[i][0]}'}`, `'patternProperties: value invalid for key '+${kVar}`)}}}`)
|
|
2381
2557
|
}
|
|
2382
|
-
lines.push(`if(!_m${pi}){${fail('
|
|
2558
|
+
lines.push(`if(!_m${pi}){${fail('additionalProperties', 'additionalProperties', `{additionalProperty:${kVar}}`, "'must NOT have additional properties'")}}`)
|
|
2383
2559
|
lines.push(`}}`)
|
|
2384
2560
|
} else {
|
|
2385
2561
|
ctx._ppHandledPropertyNamesC = !!pn
|
|
2386
2562
|
lines.push(`${guard}{for(const ${kVar} in ${v}){`)
|
|
2387
2563
|
if (pn) {
|
|
2388
|
-
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('
|
|
2389
|
-
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('
|
|
2564
|
+
if (pn.minLength !== undefined) lines.push(`if(${kVar}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
2565
|
+
if (pn.maxLength !== undefined) lines.push(`if(${kVar}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
2390
2566
|
if (pn.pattern) {
|
|
2391
2567
|
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
2392
2568
|
if (fast) {
|
|
2393
|
-
lines.push(`if(!(${fast})){${fail('
|
|
2569
|
+
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
2394
2570
|
} else {
|
|
2395
2571
|
const ri = ctx.varCounter++
|
|
2396
2572
|
ctx.closureVars.push(`_re${ri}`)
|
|
2397
2573
|
ctx.closureVals.push(new RegExp(pn.pattern))
|
|
2398
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('
|
|
2574
|
+
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
2399
2575
|
}
|
|
2400
2576
|
}
|
|
2401
|
-
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('
|
|
2577
|
+
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
2402
2578
|
if (pn.enum) {
|
|
2403
2579
|
const ei = ctx.varCounter++
|
|
2404
2580
|
ctx.closureVars.push(`_es${ei}`)
|
|
2405
2581
|
ctx.closureVals.push(new Set(pn.enum))
|
|
2406
|
-
lines.push(`if(!_es${ei}.has(${kVar})){${fail('
|
|
2582
|
+
lines.push(`if(!_es${ei}.has(${kVar})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
2407
2583
|
}
|
|
2408
2584
|
}
|
|
2409
2585
|
for (let i = 0; i < ppEntries.length; i++) {
|
|
2410
|
-
lines.push(`if(${matchers[i].check}&&!_ppf${pi}_${i}(${v}[${kVar}])){${fail('
|
|
2586
|
+
lines.push(`if(${matchers[i].check}&&!_ppf${pi}_${i}(${v}[${kVar}])){${fail('pattern', 'patternProperties', `{pattern:'${ppEntries[i][0]}'}`, `'patternProperties: value invalid for key '+${kVar}`)}}`)
|
|
2411
2587
|
}
|
|
2412
2588
|
lines.push(`}}`)
|
|
2413
2589
|
}
|
|
@@ -2417,7 +2593,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2417
2593
|
if (schema.dependentSchemas) {
|
|
2418
2594
|
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
2419
2595
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${JSON.stringify(key)} in ${v}){`)
|
|
2420
|
-
genCodeC(depSchema, v, pathExpr, lines, ctx)
|
|
2596
|
+
genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+key)
|
|
2421
2597
|
lines.push(`}`)
|
|
2422
2598
|
}
|
|
2423
2599
|
}
|
|
@@ -2428,25 +2604,25 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2428
2604
|
const ki = ctx.varCounter++
|
|
2429
2605
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
|
|
2430
2606
|
if (pn.minLength !== undefined) {
|
|
2431
|
-
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('
|
|
2607
|
+
lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
|
|
2432
2608
|
}
|
|
2433
2609
|
if (pn.maxLength !== undefined) {
|
|
2434
|
-
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('
|
|
2610
|
+
lines.push(`if(_k${ki}.length>${pn.maxLength}){${fail('maxLength', 'propertyNames/maxLength', `{limit:${pn.maxLength}}`, `'must NOT have more than ${pn.maxLength} characters'`)}}`)
|
|
2435
2611
|
}
|
|
2436
2612
|
if (pn.pattern) {
|
|
2437
2613
|
const ri = ctx.varCounter++
|
|
2438
2614
|
ctx.closureVars.push(`_re${ri}`)
|
|
2439
2615
|
ctx.closureVals.push(new RegExp(pn.pattern))
|
|
2440
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('
|
|
2616
|
+
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `'must match pattern "${pn.pattern}"'`)}}`)
|
|
2441
2617
|
}
|
|
2442
2618
|
if (pn.const !== undefined) {
|
|
2443
|
-
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('
|
|
2619
|
+
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
2444
2620
|
}
|
|
2445
2621
|
if (pn.enum) {
|
|
2446
2622
|
const ei = ctx.varCounter++
|
|
2447
2623
|
ctx.closureVars.push(`_es${ei}`)
|
|
2448
2624
|
ctx.closureVals.push(new Set(pn.enum))
|
|
2449
|
-
lines.push(`if(!_es${ei}.has(_k${ki})){${fail('
|
|
2625
|
+
lines.push(`if(!_es${ei}.has(_k${ki})){${fail('enum', 'propertyNames/enum', `{allowedValues:${JSON.stringify(pn.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
2450
2626
|
}
|
|
2451
2627
|
lines.push(`}}`)
|
|
2452
2628
|
}
|
|
@@ -2456,18 +2632,18 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2456
2632
|
const startIdx = schema.prefixItems ? schema.prefixItems.length : 0
|
|
2457
2633
|
const idx = `_j${ctx.varCounter}`, elem = `_ei${ctx.varCounter}`
|
|
2458
2634
|
ctx.varCounter++
|
|
2459
|
-
const childPath = pathExpr
|
|
2635
|
+
const childPath = childPathDynExpr(pathExpr, idx)
|
|
2460
2636
|
lines.push(`if(Array.isArray(${v})){for(let ${idx}=${startIdx};${idx}<${v}.length;${idx}++){const ${elem}=${v}[${idx}]`)
|
|
2461
|
-
genCodeC(schema.items, elem, childPath, lines, ctx)
|
|
2637
|
+
genCodeC(schema.items, elem, childPath, lines, ctx, schemaPrefix+'/items')
|
|
2462
2638
|
lines.push(`}}`)
|
|
2463
2639
|
}
|
|
2464
2640
|
|
|
2465
2641
|
// prefixItems
|
|
2466
2642
|
if (schema.prefixItems) {
|
|
2467
2643
|
for (let i = 0; i < schema.prefixItems.length; i++) {
|
|
2468
|
-
const childPath = pathExpr
|
|
2644
|
+
const childPath = childPathExpr(pathExpr, String(i))
|
|
2469
2645
|
lines.push(`if(Array.isArray(${v})&&${v}.length>${i}){`)
|
|
2470
|
-
genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx)
|
|
2646
|
+
genCodeC(schema.prefixItems[i], `${v}[${i}]`, childPath, lines, ctx, schemaPrefix+'/prefixItems/'+i)
|
|
2471
2647
|
lines.push(`}`)
|
|
2472
2648
|
}
|
|
2473
2649
|
}
|
|
@@ -2481,26 +2657,26 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2481
2657
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
2482
2658
|
const maxC = schema.maxContains
|
|
2483
2659
|
lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
|
|
2484
|
-
lines.push(`if(_cc${ci}<${minC}){${fail('
|
|
2485
|
-
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('
|
|
2660
|
+
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{limit:${minC}}`, `'contains: need at least ${minC} match(es)'`)}}`)
|
|
2661
|
+
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{limit:${maxC}}`, `'contains: at most ${maxC} match(es)'`)}}`)
|
|
2486
2662
|
lines.push(`}`)
|
|
2487
2663
|
}
|
|
2488
2664
|
|
|
2489
2665
|
// allOf
|
|
2490
|
-
if (schema.allOf) { for (
|
|
2666
|
+
if (schema.allOf) { for (let _ai = 0; _ai < schema.allOf.length; _ai++) genCodeC(schema.allOf[_ai], v, pathExpr, lines, ctx, schemaPrefix+'/allOf/'+_ai) }
|
|
2491
2667
|
|
|
2492
2668
|
// anyOf
|
|
2493
2669
|
if (schema.anyOf) {
|
|
2494
2670
|
const fi = ctx.varCounter++
|
|
2495
2671
|
const fns = schema.anyOf.map(sub => { const sl = []; genCode(sub, '_av', sl, ctx); return sl.length === 0 ? `function(_av){return true}` : `function(_av){${sl.join(';')};return true}` })
|
|
2496
|
-
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('
|
|
2672
|
+
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
|
|
2497
2673
|
}
|
|
2498
2674
|
|
|
2499
2675
|
// oneOf
|
|
2500
2676
|
if (schema.oneOf) {
|
|
2501
2677
|
const fi = ctx.varCounter++
|
|
2502
2678
|
const fns = schema.oneOf.map(sub => { const sl = []; genCode(sub, '_ov', sl, ctx); return sl.length === 0 ? `function(_ov){return true}` : `function(_ov){${sl.join(';')};return true}` })
|
|
2503
|
-
lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('
|
|
2679
|
+
lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
|
|
2504
2680
|
}
|
|
2505
2681
|
|
|
2506
2682
|
// not
|
|
@@ -2508,7 +2684,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2508
2684
|
const sl = []; genCode(schema.not, '_nv', sl, ctx)
|
|
2509
2685
|
const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
|
|
2510
2686
|
const fi = ctx.varCounter++
|
|
2511
|
-
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('
|
|
2687
|
+
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
2512
2688
|
}
|
|
2513
2689
|
|
|
2514
2690
|
// if/then/else
|
|
@@ -2517,8 +2693,8 @@ function genCodeC(schema, v, pathExpr, lines, ctx) {
|
|
|
2517
2693
|
const fi = ctx.varCounter++
|
|
2518
2694
|
const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
|
|
2519
2695
|
lines.push(`{const _if${fi}=${ifFn}`)
|
|
2520
|
-
if (schema.then) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx); lines.push(`}`) }
|
|
2521
|
-
if (schema.else) { lines.push(`${schema.then ? 'else' : `if(!_if${fi}(${v}))`}{`); genCodeC(schema.else, v, pathExpr, lines, ctx); lines.push(`}`) }
|
|
2696
|
+
if (schema.then) { lines.push(`if(_if${fi}(${v})){`); genCodeC(schema.then, v, pathExpr, lines, ctx, schemaPrefix+'/then'); lines.push(`}`) }
|
|
2697
|
+
if (schema.else) { lines.push(`${schema.then ? 'else' : `if(!_if${fi}(${v}))`}{`); genCodeC(schema.else, v, pathExpr, lines, ctx, schemaPrefix+'/else'); lines.push(`}`) }
|
|
2522
2698
|
lines.push(`}`)
|
|
2523
2699
|
}
|
|
2524
2700
|
|
|
@@ -2648,4 +2824,4 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
|
|
|
2648
2824
|
// not → contributes nothing (spec: annotations from not are discarded)
|
|
2649
2825
|
}
|
|
2650
2826
|
|
|
2651
|
-
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated }
|
|
2827
|
+
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, AJV_MESSAGES }
|