1 | import type {AnySchema, SchemaMap} from "../types"
|
2 | import type {SchemaCxt} from "../compile"
|
3 | import type {KeywordCxt} from "../compile/validate"
|
4 | import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen"
|
5 | import {alwaysValidSchema, Type} from "../compile/util"
|
6 | import N from "../compile/names"
|
7 | import {useFunc} from "../compile/util"
|
8 | export function checkReportMissingProp(cxt: KeywordCxt, prop: string): void {
|
9 | const {gen, data, it} = cxt
|
10 | gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => {
|
11 | cxt.setParams({missingProperty: _`${prop}`}, true)
|
12 | cxt.error()
|
13 | })
|
14 | }
|
15 |
|
16 | export function checkMissingProp(
|
17 | {gen, data, it: {opts}}: KeywordCxt,
|
18 | properties: string[],
|
19 | missing: Name
|
20 | ): Code {
|
21 | return or(
|
22 | ...properties.map((prop) =>
|
23 | and(noPropertyInData(gen, data, prop, opts.ownProperties), _`${missing} = ${prop}`)
|
24 | )
|
25 | )
|
26 | }
|
27 |
|
28 | export function reportMissingProp(cxt: KeywordCxt, missing: Name): void {
|
29 | cxt.setParams({missingProperty: missing}, true)
|
30 | cxt.error()
|
31 | }
|
32 |
|
33 | export function hasPropFunc(gen: CodeGen): Name {
|
34 | return gen.scopeValue("func", {
|
35 |
|
36 | ref: Object.prototype.hasOwnProperty,
|
37 | code: _`Object.prototype.hasOwnProperty`,
|
38 | })
|
39 | }
|
40 |
|
41 | export function isOwnProperty(gen: CodeGen, data: Name, property: Name | string): Code {
|
42 | return _`${hasPropFunc(gen)}.call(${data}, ${property})`
|
43 | }
|
44 |
|
45 | export function propertyInData(
|
46 | gen: CodeGen,
|
47 | data: Name,
|
48 | property: Name | string,
|
49 | ownProperties?: boolean
|
50 | ): Code {
|
51 | const cond = _`${data}${getProperty(property)} !== undefined`
|
52 | return ownProperties ? _`${cond} && ${isOwnProperty(gen, data, property)}` : cond
|
53 | }
|
54 |
|
55 | export function noPropertyInData(
|
56 | gen: CodeGen,
|
57 | data: Name,
|
58 | property: Name | string,
|
59 | ownProperties?: boolean
|
60 | ): Code {
|
61 | const cond = _`${data}${getProperty(property)} === undefined`
|
62 | return ownProperties ? or(cond, not(isOwnProperty(gen, data, property))) : cond
|
63 | }
|
64 |
|
65 | export function allSchemaProperties(schemaMap?: SchemaMap): string[] {
|
66 | return schemaMap ? Object.keys(schemaMap).filter((p) => p !== "__proto__") : []
|
67 | }
|
68 |
|
69 | export function schemaProperties(it: SchemaCxt, schemaMap: SchemaMap): string[] {
|
70 | return allSchemaProperties(schemaMap).filter(
|
71 | (p) => !alwaysValidSchema(it, schemaMap[p] as AnySchema)
|
72 | )
|
73 | }
|
74 |
|
75 | export function callValidateCode(
|
76 | {schemaCode, data, it: {gen, topSchemaRef, schemaPath, errorPath}, it}: KeywordCxt,
|
77 | func: Code,
|
78 | context: Code,
|
79 | passSchema?: boolean
|
80 | ): Code {
|
81 | const dataAndSchema = passSchema ? _`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data
|
82 | const valCxt: [Name, Code | number][] = [
|
83 | [N.instancePath, strConcat(N.instancePath, errorPath)],
|
84 | [N.parentData, it.parentData],
|
85 | [N.parentDataProperty, it.parentDataProperty],
|
86 | [N.rootData, N.rootData],
|
87 | ]
|
88 | if (it.opts.dynamicRef) valCxt.push([N.dynamicAnchors, N.dynamicAnchors])
|
89 | const args = _`${dataAndSchema}, ${gen.object(...valCxt)}`
|
90 | return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})`
|
91 | }
|
92 |
|
93 | const newRegExp = _`new RegExp`
|
94 |
|
95 | export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name {
|
96 | const u = opts.unicodeRegExp ? "u" : ""
|
97 | const {regExp} = opts.code
|
98 | const rx = regExp(pattern, u)
|
99 |
|
100 | return gen.scopeValue("pattern", {
|
101 | key: rx.toString(),
|
102 | ref: rx,
|
103 | code: _`${regExp.code === "new RegExp" ? newRegExp : useFunc(gen, regExp)}(${pattern}, ${u})`,
|
104 | })
|
105 | }
|
106 |
|
107 | export function validateArray(cxt: KeywordCxt): Name {
|
108 | const {gen, data, keyword, it} = cxt
|
109 | const valid = gen.name("valid")
|
110 | if (it.allErrors) {
|
111 | const validArr = gen.let("valid", true)
|
112 | validateItems(() => gen.assign(validArr, false))
|
113 | return validArr
|
114 | }
|
115 | gen.var(valid, true)
|
116 | validateItems(() => gen.break())
|
117 | return valid
|
118 |
|
119 | function validateItems(notValid: () => void): void {
|
120 | const len = gen.const("len", _`${data}.length`)
|
121 | gen.forRange("i", 0, len, (i) => {
|
122 | cxt.subschema(
|
123 | {
|
124 | keyword,
|
125 | dataProp: i,
|
126 | dataPropType: Type.Num,
|
127 | },
|
128 | valid
|
129 | )
|
130 | gen.if(not(valid), notValid)
|
131 | })
|
132 | }
|
133 | }
|
134 |
|
135 | export function validateUnion(cxt: KeywordCxt): void {
|
136 | const {gen, schema, keyword, it} = cxt
|
137 |
|
138 | if (!Array.isArray(schema)) throw new Error("ajv implementation error")
|
139 | const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch))
|
140 | if (alwaysValid && !it.opts.unevaluated) return
|
141 |
|
142 | const valid = gen.let("valid", false)
|
143 | const schValid = gen.name("_valid")
|
144 |
|
145 | gen.block(() =>
|
146 | schema.forEach((_sch: AnySchema, i: number) => {
|
147 | const schCxt = cxt.subschema(
|
148 | {
|
149 | keyword,
|
150 | schemaProp: i,
|
151 | compositeRule: true,
|
152 | },
|
153 | schValid
|
154 | )
|
155 | gen.assign(valid, _`${valid} || ${schValid}`)
|
156 | const merged = cxt.mergeValidEvaluated(schCxt, schValid)
|
157 |
|
158 |
|
159 | if (!merged) gen.if(not(valid))
|
160 | })
|
161 | )
|
162 |
|
163 | cxt.result(
|
164 | valid,
|
165 | () => cxt.reset(),
|
166 | () => cxt.error(true)
|
167 | )
|
168 | }
|