| 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 {getErrorPath, Type} from "./util" |
| import N from "./names" |
| |
| export const keywordError: KeywordErrorDefinition = { |
| message: ({keyword}) => str`must 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 interface ErrorPaths { |
| instancePath?: Code |
| schemaPath?: string |
| parentSchema?: boolean |
| } |
| |
| export function reportError( |
| cxt: KeywordErrorCxt, |
| error: KeywordErrorDefinition = keywordError, |
| errorPaths?: ErrorPaths, |
| overrideAllErrors?: boolean |
| ): void { |
| const {it} = cxt |
| const {gen, compositeRule, allErrors} = it |
| const errObj = errorObjectCode(cxt, error, errorPaths) |
| if (overrideAllErrors ?? (compositeRule || allErrors)) { |
| addError(gen, errObj) |
| } else { |
| returnErrors(it, _`[${errObj}]`) |
| } |
| } |
| |
| export function reportExtraError( |
| cxt: KeywordErrorCxt, |
| error: KeywordErrorDefinition = keywordError, |
| errorPaths?: ErrorPaths |
| ): void { |
| const {it} = cxt |
| const {gen, compositeRule, allErrors} = it |
| const errObj = errorObjectCode(cxt, error, errorPaths) |
| 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}.instancePath === undefined`, () => |
| gen.assign(_`${err}.instancePath`, strConcat(N.instancePath, 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"), |
| } |
| |
| function errorObjectCode( |
| cxt: KeywordErrorCxt, |
| error: KeywordErrorDefinition, |
| errorPaths?: ErrorPaths |
| ): Code { |
| const {createErrors} = cxt.it |
| if (createErrors === false) return _`{}` |
| return errorObject(cxt, error, errorPaths) |
| } |
| |
| function errorObject( |
| cxt: KeywordErrorCxt, |
| error: KeywordErrorDefinition, |
| errorPaths: ErrorPaths = {} |
| ): Code { |
| const {gen, it} = cxt |
| const keyValues: [Name, SafeExpr | string][] = [ |
| errorInstancePath(it, errorPaths), |
| errorSchemaPath(cxt, errorPaths), |
| ] |
| extraErrorProps(cxt, error, keyValues) |
| return gen.object(...keyValues) |
| } |
| |
| function errorInstancePath({errorPath}: SchemaCxt, {instancePath}: ErrorPaths): [Name, Code] { |
| const instPath = instancePath |
| ? str`${errorPath}${getErrorPath(instancePath, Type.Str)}` |
| : errorPath |
| return [N.instancePath, strConcat(N.instancePath, instPath)] |
| } |
| |
| function errorSchemaPath( |
| {keyword, it: {errSchemaPath}}: KeywordErrorCxt, |
| {schemaPath, parentSchema}: ErrorPaths |
| ): [Name, string | Code] { |
| let schPath = parentSchema ? errSchemaPath : str`${errSchemaPath}/${keyword}` |
| if (schemaPath) { |
| schPath = str`${schPath}${getErrorPath(schemaPath, Type.Str)}` |
| } |
| return [E.schemaPath, schPath] |
| } |
| |
| function extraErrorProps( |
| cxt: KeywordErrorCxt, |
| {params, message}: KeywordErrorDefinition, |
| keyValues: [Name, SafeExpr | string][] |
| ): void { |
| const {keyword, data, schemaValue, it} = cxt |
| const {opts, propertyName, topSchemaRef, schemaPath} = it |
| keyValues.push( |
| [E.keyword, keyword], |
| [E.params, typeof params == "function" ? params(cxt) : params || _`{}`] |
| ) |
| 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] |
| ) |
| } |
| if (propertyName) keyValues.push([E.propertyName, propertyName]) |
| } |