1 import type {KeywordCxt} from "."
4 SchemaValidateFunction,
6 AddedKeywordDefinition,
7 MacroKeywordDefinition,
10 import type {SchemaObjCxt} from ".."
11 import {_, nil, not, stringify, Code, Name, CodeGen} from "../codegen"
12 import N from "../names"
13 import type {JSONType} from "../rules"
14 import {callValidateCode} from "../../vocabularies/code"
15 import {extendErrors} from "../errors"
17 type KeywordCompilationResult = AnySchema | SchemaValidateFunction | AnyValidateFunction
19 export function macroKeywordCode(cxt: KeywordCxt, def: MacroKeywordDefinition): void {
20 const {gen, keyword, schema, parentSchema, it} = cxt
21 const macroSchema = def.macro.call(it.self, schema, parentSchema, it)
22 const schemaRef = useKeyword(gen, keyword, macroSchema)
23 if (it.opts.validateSchema !== false) it.self.validateSchema(macroSchema, true)
25 const valid = gen.name("valid")
30 errSchemaPath: `${it.errSchemaPath}/${keyword}`,
31 topSchemaRef: schemaRef,
36 cxt.pass(valid, () => cxt.error(true))
39 export function funcKeywordCode(cxt: KeywordCxt, def: FuncKeywordDefinition): void {
40 const {gen, keyword, schema, parentSchema, $data, it} = cxt
41 checkAsyncKeyword(it, def)
43 !$data && def.compile ? def.compile.call(it.self, schema, parentSchema, it) : def.validate
44 const validateRef = useKeyword(gen, keyword, validate)
45 const valid = gen.let("valid")
46 cxt.block$data(valid, validateKeyword)
47 cxt.ok(def.valid ?? valid)
49 function validateKeyword(): void {
50 if (def.errors === false) {
52 if (def.modifying) modifyData(cxt)
53 reportErrs(() => cxt.error())
55 const ruleErrs = def.async ? validateAsync() : validateSync()
56 if (def.modifying) modifyData(cxt)
57 reportErrs(() => addErrs(cxt, ruleErrs))
61 function validateAsync(): Name {
62 const ruleErrs = gen.let("ruleErrs", null)
64 () => assignValid(_`await `),
66 gen.assign(valid, false).if(
67 _`${e} instanceof ${it.ValidationError as Name}`,
68 () => gen.assign(ruleErrs, _`${e}.errors`),
75 function validateSync(): Code {
76 const validateErrs = _`${validateRef}.errors`
77 gen.assign(validateErrs, null)
82 function assignValid(_await: Code = def.async ? _`await ` : nil): void {
83 const passCxt = it.opts.passContext ? N.this : N.self
84 const passSchema = !(("compile" in def && !$data) || def.schema === false)
87 _`${_await}${callValidateCode(cxt, validateRef, passCxt, passSchema)}`,
92 function reportErrs(errors: () => void): void {
93 gen.if(not(def.valid ?? valid), errors)
97 function modifyData(cxt: KeywordCxt): void {
98 const {gen, data, it} = cxt
99 gen.if(it.parentData, () => gen.assign(data, _`${it.parentData}[${it.parentDataProperty}]`))
102 function addErrs(cxt: KeywordCxt, errs: Code): void {
105 _`Array.isArray(${errs})`,
108 .assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`)
109 .assign(N.errors, _`${N.vErrors}.length`)
116 function checkAsyncKeyword({schemaEnv}: SchemaObjCxt, def: FuncKeywordDefinition): void {
117 if (def.async && !schemaEnv.$async) throw new Error("async keyword in sync schema")
120 function useKeyword(gen: CodeGen, keyword: string, result?: KeywordCompilationResult): Name {
121 if (result === undefined) throw new Error(`keyword "${keyword}" failed to compile`)
122 return gen.scopeValue(
124 typeof result == "function" ? {ref: result} : {ref: result, code: stringify(result)}
128 export function validSchemaType(
130 schemaType: JSONType[],
131 allowUndefined = false
135 !schemaType.length ||
136 schemaType.some((st) =>
138 ? Array.isArray(schema)
140 ? schema && typeof schema == "object" && !Array.isArray(schema)
141 : typeof schema == st || (allowUndefined && typeof schema == "undefined")
146 export function validateKeywordUsage(
147 {schema, opts, self, errSchemaPath}: SchemaObjCxt,
148 def: AddedKeywordDefinition,
151 /* istanbul ignore if */
152 if (Array.isArray(def.keyword) ? !def.keyword.includes(keyword) : def.keyword !== keyword) {
153 throw new Error("ajv implementation error")
156 const deps = def.dependencies
157 if (deps?.some((kwd) => !Object.prototype.hasOwnProperty.call(schema, kwd))) {
158 throw new Error(`parent schema must have dependencies of ${keyword}: ${deps.join(",")}`)
161 if (def.validateSchema) {
162 const valid = def.validateSchema(schema[keyword])
165 `keyword "${keyword}" value is invalid at path "${errSchemaPath}": ` +
166 self.errorsText(def.validateSchema.errors)
167 if (opts.validateSchema === "log") self.logger.error(msg)
168 else throw new Error(msg)