import type {KeywordErrorCxt, KeywordErrorDefinition} from "../types" import type {SchemaCxt} from "./index" import {CodeGen, _, str, strConcat, Code, Name} from "./codegen" import {SafeExpr} from "./codegen/code" import N from "./names" export const keywordError: KeywordErrorDefinition = { message: ({keyword}) => str`should pass "${keyword}" keyword validation`, } export const keyword$DataError: KeywordErrorDefinition = { message: ({keyword, schemaType}) => schemaType ? str`"${keyword}" keyword must be ${schemaType} ($data)` : str`"${keyword}" keyword is invalid ($data)`, } export function reportError( cxt: KeywordErrorCxt, error: KeywordErrorDefinition = keywordError, overrideAllErrors?: boolean ): void { const {it} = cxt const {gen, compositeRule, allErrors} = it const errObj = errorObjectCode(cxt, error) if (overrideAllErrors ?? (compositeRule || allErrors)) { addError(gen, errObj) } else { returnErrors(it, _`[${errObj}]`) } } export function reportExtraError( cxt: KeywordErrorCxt, error: KeywordErrorDefinition = keywordError ): void { const {it} = cxt const {gen, compositeRule, allErrors} = it const errObj = errorObjectCode(cxt, error) addError(gen, errObj) if (!(compositeRule || allErrors)) { returnErrors(it, N.vErrors) } } export function resetErrorsCount(gen: CodeGen, errsCount: Name): void { gen.assign(N.errors, errsCount) gen.if(_`${N.vErrors} !== null`, () => gen.if( errsCount, () => gen.assign(_`${N.vErrors}.length`, errsCount), () => gen.assign(N.vErrors, null) ) ) } export function extendErrors({ gen, keyword, schemaValue, data, errsCount, it, }: KeywordErrorCxt): void { /* istanbul ignore if */ if (errsCount === undefined) throw new Error("ajv implementation error") const err = gen.name("err") gen.forRange("i", errsCount, N.errors, (i) => { gen.const(err, _`${N.vErrors}[${i}]`) gen.if(_`${err}.dataPath === undefined`, () => gen.assign(_`${err}.dataPath`, strConcat(N.dataPath, it.errorPath)) ) gen.assign(_`${err}.schemaPath`, str`${it.errSchemaPath}/${keyword}`) if (it.opts.verbose) { gen.assign(_`${err}.schema`, schemaValue) gen.assign(_`${err}.data`, data) } }) } function addError(gen: CodeGen, errObj: Code): void { const err = gen.const("err", errObj) gen.if( _`${N.vErrors} === null`, () => gen.assign(N.vErrors, _`[${err}]`), _`${N.vErrors}.push(${err})` ) gen.code(_`${N.errors}++`) } function returnErrors(it: SchemaCxt, errs: Code): void { const {gen, validateName, schemaEnv} = it if (schemaEnv.$async) { gen.throw(_`new ${it.ValidationError as Name}(${errs})`) } else { gen.assign(_`${validateName}.errors`, errs) gen.return(false) } } const E = { keyword: new Name("keyword"), schemaPath: new Name("schemaPath"), // also used in JTD errors params: new Name("params"), propertyName: new Name("propertyName"), message: new Name("message"), schema: new Name("schema"), parentSchema: new Name("parentSchema"), // JTD error properties instancePath: new Name("instancePath"), } function errorObjectCode(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Code { const {createErrors, opts} = cxt.it if (createErrors === false) return _`{}` return (opts.jtd && !opts.ajvErrors ? jtdErrorObject : ajvErrorObject)(cxt, error) } function jtdErrorObject(cxt: KeywordErrorCxt, {message}: KeywordErrorDefinition): Code { const {gen, keyword, it} = cxt const {errorPath, errSchemaPath, opts} = it const keyValues: [Name, SafeExpr | string][] = [ [E.instancePath, strConcat(N.dataPath, errorPath)], [E.schemaPath, str`${errSchemaPath}/${keyword}`], ] if (opts.messages) { keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]) } return gen.object(...keyValues) } function ajvErrorObject(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Code { const {gen, keyword, data, schemaValue, it} = cxt const {topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts} = it const {params, message} = error const keyValues: [Name, SafeExpr | string][] = [ [E.keyword, keyword], [N.dataPath, strConcat(N.dataPath, errorPath)], [E.schemaPath, str`${errSchemaPath}/${keyword}`], [E.params, typeof params == "function" ? params(cxt) : params || _`{}`], ] if (propertyName) keyValues.push([E.propertyName, propertyName]) if (opts.messages) { keyValues.push([E.message, typeof message == "function" ? message(cxt) : message]) } if (opts.verbose) { keyValues.push( [E.schema, schemaValue], [E.parentSchema, _`${topSchemaRef}${schemaPath}`], [N.data, data] ) } return gen.object(...keyValues) }