From 61e7520d4bf76ac4f6a81bf9585d9142378d3081 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 16 Sep 2020 17:37:55 +0100 Subject: [PATCH 1/8] refactor: import keyword definiions (rather than require) --- lib/ajv.ts | 1 + lib/types/index.ts | 8 ++-- .../applicator/additionalItems.ts | 14 ++++--- .../applicator/additionalProperties.ts | 15 ++++--- lib/vocabularies/applicator/allOf.ts | 2 +- lib/vocabularies/applicator/anyOf.ts | 2 +- lib/vocabularies/applicator/contains.ts | 2 +- lib/vocabularies/applicator/dependencies.ts | 28 +++++++------ lib/vocabularies/applicator/if.ts | 16 +++---- lib/vocabularies/applicator/index.ts | 42 ++++++++++++------- lib/vocabularies/applicator/items.ts | 2 +- lib/vocabularies/applicator/not.ts | 2 +- lib/vocabularies/applicator/oneOf.ts | 14 ++++--- .../applicator/patternProperties.ts | 2 +- lib/vocabularies/applicator/properties.ts | 2 +- lib/vocabularies/applicator/propertyNames.ts | 14 ++++--- lib/vocabularies/applicator/thenElse.ts | 2 +- lib/vocabularies/core/index.ts | 3 +- lib/vocabularies/core/ref.ts | 15 +++---- lib/vocabularies/format/format.ts | 13 +++--- lib/vocabularies/format/index.ts | 3 +- lib/vocabularies/validation/const.ts | 14 ++++--- lib/vocabularies/validation/enum.ts | 14 ++++--- lib/vocabularies/validation/index.ts | 30 ++++++++----- lib/vocabularies/validation/limit.ts | 14 ++++--- lib/vocabularies/validation/limitItems.ts | 20 +++++---- lib/vocabularies/validation/limitLength.ts | 20 +++++---- .../validation/limitProperties.ts | 20 +++++---- lib/vocabularies/validation/multipleOf.ts | 14 ++++--- lib/vocabularies/validation/pattern.ts | 14 ++++--- lib/vocabularies/validation/required.ts | 15 +++---- lib/vocabularies/validation/uniqueItems.ts | 16 +++---- 32 files changed, 226 insertions(+), 167 deletions(-) diff --git a/lib/ajv.ts b/lib/ajv.ts index 5df36f414..4b001b783 100644 --- a/lib/ajv.ts +++ b/lib/ajv.ts @@ -3,6 +3,7 @@ export { FormatDefinition, AsyncFormatDefinition, KeywordDefinition, + KeywordErrorDefinition, CodeKeywordDefinition, MacroKeywordDefinition, FuncKeywordDefinition, diff --git a/lib/types/index.ts b/lib/types/index.ts index 14adce914..581f82dae 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -141,14 +141,14 @@ export interface AsyncValidateFunction extends ValidateFunction { export type AnyValidateFunction = ValidateFunction | AsyncValidateFunction -export interface ErrorObject { - keyword: string +export interface ErrorObject { + keyword: T dataPath: string schemaPath: string params: Record // TODO add interface - // Added to validation errors of propertyNames keyword schema + // Added to validation errors of "propertyNames" keyword schema propertyName?: string - // Excluded if messages set to false. + // Excluded if option `messages` set to false. message?: string // These are added with the `verbose` option. schema?: unknown diff --git a/lib/vocabularies/applicator/additionalItems.ts b/lib/vocabularies/applicator/additionalItems.ts index fc79b6b9b..92c7c423c 100644 --- a/lib/vocabularies/applicator/additionalItems.ts +++ b/lib/vocabularies/applicator/additionalItems.ts @@ -1,14 +1,20 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema, checkStrictMode} from "../util" import {applySubschema, Type} from "../../compile/subschema" import {_, Name, str} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: ({params: {len}}) => str`should NOT have more than ${len} items`, + params: ({params: {len}}) => _`{limit: ${len}}`, +} + const def: CodeKeywordDefinition = { keyword: "additionalItems", type: "array", schemaType: ["boolean", "object"], before: "uniqueItems", + error, code(cxt: KeywordCxt) { const {gen, schema, parentSchema, data, it} = cxt const len = gen.const("len", _`${data}.length`) @@ -33,10 +39,6 @@ const def: CodeKeywordDefinition = { }) } }, - error: { - message: ({params: {len}}) => str`should NOT have more than ${len} items`, - params: ({params: {len}}) => _`{limit: ${len}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/additionalProperties.ts b/lib/vocabularies/applicator/additionalProperties.ts index 109fe658a..7e4f55a91 100644 --- a/lib/vocabularies/applicator/additionalProperties.ts +++ b/lib/vocabularies/applicator/additionalProperties.ts @@ -1,14 +1,20 @@ -import type {CodeKeywordDefinition, KeywordErrorCxt} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import {allSchemaProperties, schemaRefOrVal, alwaysValidSchema, usePattern} from "../util" import {applySubschema, SubschemaApplication, Type} from "../../compile/subschema" import {_, nil, or, Code, Name} from "../../compile/codegen" import N from "../../compile/names" +const error: KeywordErrorDefinition = { + message: "should NOT have additional properties", + params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`, +} + const def: CodeKeywordDefinition = { keyword: "additionalProperties", type: "object", schemaType: ["boolean", "object", "undefined"], // "undefined" is needed to support option removeAdditional: "all" trackErrors: true, + error, code(cxt) { const {gen, schema, parentSchema, data, errsCount, it} = cxt if (!errsCount) throw new Error("ajv implementation error") @@ -91,13 +97,6 @@ const def: CodeKeywordDefinition = { applySubschema(it, subschema, valid) } }, - error: { - message: "should NOT have additional properties", - params: ({params}: KeywordErrorCxt): Code => - _`{additionalProperty: ${params.additionalProperty}}`, - }, } -module.exports = def - export default def diff --git a/lib/vocabularies/applicator/allOf.ts b/lib/vocabularies/applicator/allOf.ts index 33aa3cc7c..540a012b2 100644 --- a/lib/vocabularies/applicator/allOf.ts +++ b/lib/vocabularies/applicator/allOf.ts @@ -18,4 +18,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/anyOf.ts b/lib/vocabularies/applicator/anyOf.ts index a7f3a6ce0..d9beac300 100644 --- a/lib/vocabularies/applicator/anyOf.ts +++ b/lib/vocabularies/applicator/anyOf.ts @@ -44,4 +44,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/contains.ts b/lib/vocabularies/applicator/contains.ts index e4b0aa28b..28b29c405 100644 --- a/lib/vocabularies/applicator/contains.ts +++ b/lib/vocabularies/applicator/contains.ts @@ -40,4 +40,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/dependencies.ts b/lib/vocabularies/applicator/dependencies.ts index 5efb26782..da279861b 100644 --- a/lib/vocabularies/applicator/dependencies.ts +++ b/lib/vocabularies/applicator/dependencies.ts @@ -1,4 +1,4 @@ -import type {CodeKeywordDefinition, SchemaMap, AnySchema} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition, SchemaMap, AnySchema} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema, propertyInData} from "../util" import {applySubschema} from "../../compile/subschema" @@ -11,10 +11,23 @@ interface PropertyDependencies { type SchemaDependencies = SchemaMap +const error: KeywordErrorDefinition = { + message: ({params: {property, depsCount, deps}}) => { + const property_ies = depsCount === 1 ? "property" : "properties" + return str`should have ${property_ies} ${deps} when property ${property} is present` + }, + params: ({params: {property, depsCount, deps, missingProperty}}) => + _`{property: ${property}, + missingProperty: ${missingProperty}, + depsCount: ${depsCount}, + deps: ${deps}}`, // TODO change to reference? +} + const def: CodeKeywordDefinition = { keyword: "dependencies", type: "object", schemaType: "object", + error, code(cxt: KeywordCxt) { const {gen, schema, data, it} = cxt const [propDeps, schDeps] = splitDependencies() @@ -71,17 +84,6 @@ const def: CodeKeywordDefinition = { } } }, - error: { - message: ({params: {property, depsCount, deps}}) => { - const property_ies = depsCount === 1 ? "property" : "properties" - return str`should have ${property_ies} ${deps} when property ${property} is present` - }, - params: ({params: {property, depsCount, deps, missingProperty}}) => - _`{property: ${property}, - missingProperty: ${missingProperty}, - depsCount: ${depsCount}, - deps: ${deps}}`, // TODO change to reference? - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/if.ts b/lib/vocabularies/applicator/if.ts index af8997c20..4bcd3654c 100644 --- a/lib/vocabularies/applicator/if.ts +++ b/lib/vocabularies/applicator/if.ts @@ -1,13 +1,19 @@ -import type {CodeKeywordDefinition, SchemaObjCxt} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition, SchemaObjCxt} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema, checkStrictMode} from "../util" import {applySubschema} from "../../compile/subschema" import {_, str, Name} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: ({params}) => str`should match "${params.ifClause}" schema`, + params: ({params}) => _`{failingKeyword: ${params.ifClause}}`, +} + const def: CodeKeywordDefinition = { keyword: "if", schemaType: ["object", "boolean"], trackErrors: true, + error, code(cxt: KeywordCxt) { const {gen, parentSchema, it} = cxt if (parentSchema.then === undefined && parentSchema.else === undefined) { @@ -56,15 +62,11 @@ const def: CodeKeywordDefinition = { } } }, - error: { - message: ({params}) => str`should match "${params.ifClause}" schema`, - params: ({params}) => _`{failingKeyword: ${params.ifClause}}`, - }, } -module.exports = def - function hasSchema(it: SchemaObjCxt, keyword: string): boolean { const schema = it.schema[keyword] return schema !== undefined && !alwaysValidSchema(it, schema) } + +export default def diff --git a/lib/vocabularies/applicator/index.ts b/lib/vocabularies/applicator/index.ts index 086c1ad13..15abf1fdf 100644 --- a/lib/vocabularies/applicator/index.ts +++ b/lib/vocabularies/applicator/index.ts @@ -1,23 +1,37 @@ import type {Vocabulary} from "../../types" +import additionalItems from "./additionalItems" +import items from "./items" +import contains from "./contains" +import dependencies from "./dependencies" +import propertyNames from "./propertyNames" +import additionalProperties from "./additionalProperties" +import properties from "./properties" +import patternProperties from "./patternProperties" +import notKeyword from "./not" +import anyOf from "./anyOf" +import oneOf from "./oneOf" +import allOf from "./allOf" +import ifKeyword from "./if" +import thenElse from "./thenElse" const applicator: Vocabulary = [ // array - require("./additionalItems"), - require("./items"), - require("./contains"), + additionalItems, + items, + contains, // object - require("./dependencies"), - require("./propertyNames"), - require("./additionalProperties"), - require("./properties"), - require("./patternProperties"), + dependencies, + propertyNames, + additionalProperties, + properties, + patternProperties, // any - require("./not"), - require("./anyOf"), - require("./oneOf"), - require("./allOf"), - require("./if"), - require("./thenElse"), + notKeyword, + anyOf, + oneOf, + allOf, + ifKeyword, + thenElse, ] export default applicator diff --git a/lib/vocabularies/applicator/items.ts b/lib/vocabularies/applicator/items.ts index 15a89db1e..68cba36d1 100644 --- a/lib/vocabularies/applicator/items.ts +++ b/lib/vocabularies/applicator/items.ts @@ -48,4 +48,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/not.ts b/lib/vocabularies/applicator/not.ts index bb183fa77..4c2deabfc 100644 --- a/lib/vocabularies/applicator/not.ts +++ b/lib/vocabularies/applicator/not.ts @@ -37,4 +37,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/oneOf.ts b/lib/vocabularies/applicator/oneOf.ts index 8c17f6bfc..cc4c74d54 100644 --- a/lib/vocabularies/applicator/oneOf.ts +++ b/lib/vocabularies/applicator/oneOf.ts @@ -1,13 +1,19 @@ -import type {CodeKeywordDefinition, AnySchema} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition, AnySchema} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema} from "../util" import {applySubschema} from "../../compile/subschema" import {_} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: "should match exactly one schema in oneOf", + params: ({params}) => _`{passingSchemas: ${params.passing}}`, +} + const def: CodeKeywordDefinition = { keyword: "oneOf", schemaType: "array", trackErrors: true, + error, code(cxt: KeywordCxt) { const {gen, schema, it} = cxt if (!Array.isArray(schema)) throw new Error("ajv implementation error") @@ -54,10 +60,6 @@ const def: CodeKeywordDefinition = { }) } }, - error: { - message: "should match exactly one schema in oneOf", - params: ({params}) => _`{passingSchemas: ${params.passing}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/patternProperties.ts b/lib/vocabularies/applicator/patternProperties.ts index 7d11ea4cc..c8ffa2e3c 100644 --- a/lib/vocabularies/applicator/patternProperties.ts +++ b/lib/vocabularies/applicator/patternProperties.ts @@ -61,4 +61,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/properties.ts b/lib/vocabularies/applicator/properties.ts index 493af3b1c..76926a343 100644 --- a/lib/vocabularies/applicator/properties.ts +++ b/lib/vocabularies/applicator/properties.ts @@ -47,4 +47,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/propertyNames.ts b/lib/vocabularies/applicator/propertyNames.ts index 42e8638df..72d67c4f0 100644 --- a/lib/vocabularies/applicator/propertyNames.ts +++ b/lib/vocabularies/applicator/propertyNames.ts @@ -1,13 +1,19 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema} from "../util" import {applySubschema} from "../../compile/subschema" import {_, str} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: ({params}) => str`property name '${params.propertyName}' is invalid`, // TODO double quotes? + params: ({params}) => _`{propertyName: ${params.propertyName}}`, +} + const def: CodeKeywordDefinition = { keyword: "propertyNames", type: "object", schemaType: ["object", "boolean"], + error, code(cxt: KeywordCxt) { const {gen, schema, data, it} = cxt if (alwaysValidSchema(it, schema)) return @@ -28,10 +34,6 @@ const def: CodeKeywordDefinition = { cxt.ok(valid) }, - error: { - message: ({params}) => str`property name '${params.propertyName}' is invalid`, // TODO double quotes? - params: ({params}) => _`{propertyName: ${params.propertyName}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/applicator/thenElse.ts b/lib/vocabularies/applicator/thenElse.ts index ba0541a19..e3002a015 100644 --- a/lib/vocabularies/applicator/thenElse.ts +++ b/lib/vocabularies/applicator/thenElse.ts @@ -10,4 +10,4 @@ const def: CodeKeywordDefinition = { }, } -module.exports = def +export default def diff --git a/lib/vocabularies/core/index.ts b/lib/vocabularies/core/index.ts index 5845d0384..5b8fb6f36 100644 --- a/lib/vocabularies/core/index.ts +++ b/lib/vocabularies/core/index.ts @@ -1,5 +1,6 @@ import type {Vocabulary} from "../../types" +import refKeyword from "./ref" -const core: Vocabulary = ["$schema", "$id", "$defs", "definitions", require("./ref")] +const core: Vocabulary = ["$schema", "$id", "$defs", "definitions", refKeyword] export default core diff --git a/lib/vocabularies/core/ref.ts b/lib/vocabularies/core/ref.ts index d76b5ad89..ea316a0d0 100644 --- a/lib/vocabularies/core/ref.ts +++ b/lib/vocabularies/core/ref.ts @@ -1,4 +1,4 @@ -import type {CodeKeywordDefinition, AnySchema} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition, AnySchema} from "../../types" import type KeywordCxt from "../../compile/context" import {MissingRefError} from "../../compile/error_classes" import {applySubschema} from "../../compile/subschema" @@ -7,9 +7,15 @@ import {_, str, nil, Code, Name} from "../../compile/codegen" import N from "../../compile/names" import {SchemaEnv, resolveRef} from "../../compile" +const error: KeywordErrorDefinition = { + message: ({schema}) => str`can't resolve reference ${schema}`, + params: ({schema}) => _`{ref: ${schema}}`, +} + const def: CodeKeywordDefinition = { keyword: "$ref", schemaType: "string", + error, code(cxt: KeywordCxt) { const {gen, schema, it} = cxt const {allErrors, baseId, schemaEnv: env, opts, validateName, self} = it @@ -101,11 +107,6 @@ const def: CodeKeywordDefinition = { gen.assign(N.errors, _`${N.vErrors}.length`) } }, - // TODO incorrect error message - error: { - message: ({schema}) => str`can't resolve reference ${schema}`, - params: ({schema}) => _`{ref: ${schema}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/format/format.ts b/lib/vocabularies/format/format.ts index 22b6e4ca2..437af8477 100644 --- a/lib/vocabularies/format/format.ts +++ b/lib/vocabularies/format/format.ts @@ -3,6 +3,7 @@ import type { FormatValidator, AsyncFormatValidator, CodeKeywordDefinition, + KeywordErrorDefinition, } from "../../types" import type KeywordCxt from "../../compile/context" import {_, str, nil, or, Code, getProperty} from "../../compile/codegen" @@ -16,11 +17,17 @@ type FormatValidate = | RegExp | string +const error: KeywordErrorDefinition = { + message: ({schemaCode}) => str`should match format "${schemaCode}"`, + params: ({schemaCode}) => _`{format: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: "format", type: ["number", "string"], schemaType: "string", $data: true, + error, code(cxt: KeywordCxt, ruleType?: string) { const {gen, data, $data, schema, schemaCode, it} = cxt const {opts, errSchemaPath, schemaEnv, self} = it @@ -107,10 +114,6 @@ const def: CodeKeywordDefinition = { } } }, - error: { - message: ({schemaCode}) => str`should match format "${schemaCode}"`, - params: ({schemaCode}) => _`{format: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/format/index.ts b/lib/vocabularies/format/index.ts index 9763393d6..bca2f5b3d 100644 --- a/lib/vocabularies/format/index.ts +++ b/lib/vocabularies/format/index.ts @@ -1,5 +1,6 @@ import type {Vocabulary} from "../../types" +import formatKeyword from "./format" -const format: Vocabulary = [require("./format")] +const format: Vocabulary = [formatKeyword] export default format diff --git a/lib/vocabularies/validation/const.ts b/lib/vocabularies/validation/const.ts index 648ae97a6..f44d7b9b1 100644 --- a/lib/vocabularies/validation/const.ts +++ b/lib/vocabularies/validation/const.ts @@ -1,11 +1,17 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_} from "../../compile/codegen" import equal from "fast-deep-equal" +const error: KeywordErrorDefinition = { + message: "should be equal to constant", + params: ({schemaCode}) => _`{allowedValue: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: "const", $data: true, + error, code(cxt: KeywordCxt) { const eql = cxt.gen.scopeValue("func", { ref: equal, @@ -13,10 +19,6 @@ const def: CodeKeywordDefinition = { }) cxt.fail$data(_`!${eql}(${cxt.data}, ${cxt.schemaCode})`) }, - error: { - message: "should be equal to constant", - params: ({schemaCode}) => _`{allowedValue: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/enum.ts b/lib/vocabularies/validation/enum.ts index 84e973e85..f2bfb302e 100644 --- a/lib/vocabularies/validation/enum.ts +++ b/lib/vocabularies/validation/enum.ts @@ -1,12 +1,18 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, or, Name, Code} from "../../compile/codegen" import equal from "fast-deep-equal" +const error: KeywordErrorDefinition = { + message: "should be equal to one of the allowed values", + params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: "enum", schemaType: "array", $data: true, + error, code(cxt: KeywordCxt) { const {gen, data, $data, schema, schemaCode, it} = cxt if (!$data && schema.length === 0) throw new Error("enum must have non-empty array") @@ -41,10 +47,6 @@ const def: CodeKeywordDefinition = { return _`${data} === ${sch}` } }, - error: { - message: "should be equal to one of the allowed values", - params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/index.ts b/lib/vocabularies/validation/index.ts index 116ce1759..1c555ec41 100644 --- a/lib/vocabularies/validation/index.ts +++ b/lib/vocabularies/validation/index.ts @@ -1,22 +1,32 @@ import type {Vocabulary} from "../../types" +import limit from "./limit" +import multipleOf from "./multipleOf" +import limitLength from "./limitLength" +import pattern from "./pattern" +import limitProperties from "./limitProperties" +import required from "./required" +import limitItems from "./limitItems" +import uniqueItems from "./uniqueItems" +import constKeyword from "./const" +import enumKeyword from "./enum" const validation: Vocabulary = [ // number - require("./limit"), - require("./multipleOf"), + limit, + multipleOf, // string - require("./limitLength"), - require("./pattern"), + limitLength, + pattern, // object - require("./limitProperties"), - require("./required"), + limitProperties, + required, // array - require("./limitItems"), - require("./uniqueItems"), + limitItems, + uniqueItems, // any {keyword: "nullable", schemaType: "boolean"}, - require("./const"), - require("./enum"), + constKeyword, + enumKeyword, ] export default validation diff --git a/lib/vocabularies/validation/limit.ts b/lib/vocabularies/validation/limit.ts index a5bf82de2..22086f1eb 100644 --- a/lib/vocabularies/validation/limit.ts +++ b/lib/vocabularies/validation/limit.ts @@ -1,4 +1,4 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str, operators, Code} from "../../compile/codegen" @@ -11,20 +11,22 @@ const OPS: {[index: string]: {fail: Code; ok: Code; okStr: string}} = { exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE}, } +const error: KeywordErrorDefinition = { + message: ({keyword, schemaCode}) => str`should be ${OPS[keyword].okStr} ${schemaCode}`, + params: ({keyword, schemaCode}) => _`{comparison: ${OPS[keyword].okStr}, limit: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: ["maximum", "minimum", "exclusiveMaximum", "exclusiveMinimum"], type: "number", schemaType: "number", $data: true, + error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode} = cxt // const bdt = bad$DataType(schemaCode, def.schemaType, $data) cxt.fail$data(_`(${data} ${OPS[keyword].fail} ${schemaCode} || isNaN(${data}))`) }, - error: { - message: ({keyword, schemaCode}) => str`should be ${OPS[keyword].okStr} ${schemaCode}`, - params: ({keyword, schemaCode}) => _`{comparison: ${OPS[keyword].okStr}, limit: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/limitItems.ts b/lib/vocabularies/validation/limitItems.ts index e7615fb3a..9bf865eb6 100644 --- a/lib/vocabularies/validation/limitItems.ts +++ b/lib/vocabularies/validation/limitItems.ts @@ -1,24 +1,26 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str, operators} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message({keyword, schemaCode}) { + const comp = keyword === "maxItems" ? "more" : "fewer" + return str`should NOT have ${comp} than ${schemaCode} items` + }, + params: ({schemaCode}) => _`{limit: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: ["maxItems", "minItems"], type: "array", schemaType: "number", $data: true, + error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode} = cxt const op = keyword === "maxItems" ? operators.GT : operators.LT cxt.fail$data(_`${data}.length ${op} ${schemaCode}`) }, - error: { - message({keyword, schemaCode}) { - const comp = keyword === "maxItems" ? "more" : "fewer" - return str`should NOT have ${comp} than ${schemaCode} items` - }, - params: ({schemaCode}) => _`{limit: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/limitLength.ts b/lib/vocabularies/validation/limitLength.ts index 448b63dc1..04bf9f734 100644 --- a/lib/vocabularies/validation/limitLength.ts +++ b/lib/vocabularies/validation/limitLength.ts @@ -1,13 +1,22 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str, operators} from "../../compile/codegen" import ucs2length from "../../compile/ucs2length" +const error: KeywordErrorDefinition = { + message({keyword, schemaCode}) { + const comp = keyword === "maxLength" ? "more" : "fewer" + return str`should NOT have ${comp} than ${schemaCode} items` + }, + params: ({schemaCode}) => _`{limit: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: ["maxLength", "minLength"], type: "string", schemaType: "number", $data: true, + error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode, it} = cxt const op = keyword === "maxLength" ? operators.GT : operators.LT @@ -23,13 +32,6 @@ const def: CodeKeywordDefinition = { } cxt.fail$data(_`${len} ${op} ${schemaCode}`) }, - error: { - message({keyword, schemaCode}) { - const comp = keyword === "maxLength" ? "more" : "fewer" - return str`should NOT have ${comp} than ${schemaCode} items` - }, - params: ({schemaCode}) => _`{limit: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/limitProperties.ts b/lib/vocabularies/validation/limitProperties.ts index 04e7e8ba8..ec5943deb 100644 --- a/lib/vocabularies/validation/limitProperties.ts +++ b/lib/vocabularies/validation/limitProperties.ts @@ -1,24 +1,26 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str, operators} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message({keyword, schemaCode}) { + const comp = keyword === "maxProperties" ? "more" : "fewer" + return str`should NOT have ${comp} than ${schemaCode} items` + }, + params: ({schemaCode}) => _`{limit: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: ["maxProperties", "minProperties"], type: "object", schemaType: "number", $data: true, + error, code(cxt: KeywordCxt) { const {keyword, data, schemaCode} = cxt const op = keyword === "maxProperties" ? operators.GT : operators.LT cxt.fail$data(_`Object.keys(${data}).length ${op} ${schemaCode}`) }, - error: { - message({keyword, schemaCode}) { - const comp = keyword === "maxProperties" ? "more" : "fewer" - return str`should NOT have ${comp} than ${schemaCode} items` - }, - params: ({schemaCode}) => _`{limit: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/multipleOf.ts b/lib/vocabularies/validation/multipleOf.ts index c4298dfc7..d1a763e7a 100644 --- a/lib/vocabularies/validation/multipleOf.ts +++ b/lib/vocabularies/validation/multipleOf.ts @@ -1,12 +1,18 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: ({schemaCode}) => str`should be multiple of ${schemaCode}`, + params: ({schemaCode}) => _`{multipleOf: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: "multipleOf", type: "number", schemaType: "number", $data: true, + error, code(cxt: KeywordCxt) { const {gen, data, schemaCode, it} = cxt // const bdt = bad$DataType(schemaCode, def.schemaType, $data) @@ -17,10 +23,6 @@ const def: CodeKeywordDefinition = { : _`${res} !== parseInt(${res})` cxt.fail$data(_`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`) }, - error: { - message: ({schemaCode}) => str`should be multiple of ${schemaCode}`, - params: ({schemaCode}) => _`{multipleOf: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index e4ceefce9..0d41aab58 100644 --- a/lib/vocabularies/validation/pattern.ts +++ b/lib/vocabularies/validation/pattern.ts @@ -1,22 +1,24 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {usePattern} from "../util" import {_, str} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: ({schemaCode}) => str`should match pattern "${schemaCode}"`, + params: ({schemaCode}) => _`{pattern: ${schemaCode}}`, +} + const def: CodeKeywordDefinition = { keyword: "pattern", type: "string", schemaType: "string", $data: true, + error, code(cxt: KeywordCxt) { const {gen, data, $data, schema, schemaCode} = cxt const regExp = $data ? _`(new RegExp(${schemaCode}, "u"))` : usePattern(gen, schema) // TODO regexp should be wrapped in try/catch cxt.fail$data(_`!${regExp}.test(${data})`) }, - error: { - message: ({schemaCode}) => str`should match pattern "${schemaCode}"`, - params: ({schemaCode}) => _`{pattern: ${schemaCode}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/required.ts b/lib/vocabularies/validation/required.ts index 85a3b8528..dc66ff15b 100644 --- a/lib/vocabularies/validation/required.ts +++ b/lib/vocabularies/validation/required.ts @@ -1,14 +1,20 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {propertyInData, noPropertyInData} from "../util" import {checkReportMissingProp, checkMissingProp, reportMissingProp} from "../missing" import {_, str, nil, Name} from "../../compile/codegen" +const error: KeywordErrorDefinition = { + message: ({params: {missingProperty}}) => str`should have required property '${missingProperty}'`, + params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`, +} + const def: CodeKeywordDefinition = { keyword: "required", type: "object", schemaType: "array", $data: true, + error, code(cxt: KeywordCxt) { const {gen, schema, schemaCode, data, $data, it} = cxt if (!$data && schema.length === 0) return @@ -62,11 +68,6 @@ const def: CodeKeywordDefinition = { ) } }, - error: { - message: ({params: {missingProperty}}) => - str`should have required property '${missingProperty}'`, - params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`, - }, } -module.exports = def +export default def diff --git a/lib/vocabularies/validation/uniqueItems.ts b/lib/vocabularies/validation/uniqueItems.ts index 02da60860..53f065efe 100644 --- a/lib/vocabularies/validation/uniqueItems.ts +++ b/lib/vocabularies/validation/uniqueItems.ts @@ -1,15 +1,22 @@ -import type {CodeKeywordDefinition} from "../../types" +import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {getSchemaTypes} from "../../compile/validate/dataType" import {checkDataTypes, DataType} from "../../compile/util" import {_, str, Name} from "../../compile/codegen" import equal from "fast-deep-equal" +const error: KeywordErrorDefinition = { + message: ({params: {i, j}}) => + str`should NOT have duplicate items (items ## ${j} and ${i} are identical)`, + params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`, +} + const def: CodeKeywordDefinition = { keyword: "uniqueItems", type: "array", schemaType: "boolean", $data: true, + error, code(cxt: KeywordCxt) { const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt if (!$data && !schema) return @@ -64,11 +71,6 @@ const def: CodeKeywordDefinition = { ) } }, - error: { - message: ({params: {i, j}}) => - str`should NOT have duplicate items (items ## ${j} and ${i} are identical)`, - params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`, - }, } -module.exports = def +export default def From defca2ce21dcd98fbedffde2dd693c6672ee3b13 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 16 Sep 2020 20:18:22 +0100 Subject: [PATCH 2/8] feat: DefinedError as discriminated union with different parameter types, closes #843, closes #1090 --- lib/compile/validate/dataType.ts | 4 ++ lib/types/index.ts | 8 ++- .../applicator/additionalItems.ts | 4 ++ .../applicator/additionalProperties.ts | 4 ++ lib/vocabularies/applicator/dependencies.ts | 9 ++- lib/vocabularies/applicator/if.ts | 4 ++ lib/vocabularies/applicator/oneOf.ts | 4 ++ lib/vocabularies/applicator/propertyNames.ts | 4 ++ lib/vocabularies/core/ref.ts | 4 ++ lib/vocabularies/errors.ts | 69 +++++++++++++++++++ lib/vocabularies/format/format.ts | 4 ++ lib/vocabularies/validation/const.ts | 4 ++ lib/vocabularies/validation/enum.ts | 4 ++ lib/vocabularies/validation/limit.ts | 9 +++ lib/vocabularies/validation/multipleOf.ts | 4 ++ lib/vocabularies/validation/pattern.ts | 4 ++ lib/vocabularies/validation/required.ts | 4 ++ lib/vocabularies/validation/uniqueItems.ts | 5 ++ spec/types/async-validate.spec.ts | 3 +- spec/types/error-parameters.spec.ts | 31 +++++++++ 20 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 lib/vocabularies/errors.ts create mode 100644 spec/types/error-parameters.spec.ts diff --git a/lib/compile/validate/dataType.ts b/lib/compile/validate/dataType.ts index f45206fd0..b3354e22b 100644 --- a/lib/compile/validate/dataType.ts +++ b/lib/compile/validate/dataType.ts @@ -134,6 +134,10 @@ function assignParentData({gen, parentData, parentDataProperty}: SchemaObjCxt, e ) } +export interface TypeErrorParams { + type: string +} + const typeError: KeywordErrorDefinition = { message: ({schema}) => str`should be ${schema}`, params: ({schema, schemaValue}) => diff --git a/lib/types/index.ts b/lib/types/index.ts index 581f82dae..c07f058ba 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -3,6 +3,8 @@ import type {SchemaEnv} from "../compile" import type KeywordCxt from "../compile/context" import type Ajv from "../ajv" +export {DefinedError} from "../vocabularies/errors" + interface _SchemaObject { $id?: string $schema?: string @@ -141,11 +143,11 @@ export interface AsyncValidateFunction extends ValidateFunction { export type AnyValidateFunction = ValidateFunction | AsyncValidateFunction -export interface ErrorObject { - keyword: T +export interface ErrorObject> { + keyword: K dataPath: string schemaPath: string - params: Record // TODO add interface + params: P // Added to validation errors of "propertyNames" keyword schema propertyName?: string // Excluded if option `messages` set to false. diff --git a/lib/vocabularies/applicator/additionalItems.ts b/lib/vocabularies/applicator/additionalItems.ts index 92c7c423c..df9d5643c 100644 --- a/lib/vocabularies/applicator/additionalItems.ts +++ b/lib/vocabularies/applicator/additionalItems.ts @@ -4,6 +4,10 @@ import {alwaysValidSchema, checkStrictMode} from "../util" import {applySubschema, Type} from "../../compile/subschema" import {_, Name, str} from "../../compile/codegen" +export interface AdditionalItemsErrorParams { + limit: number +} + const error: KeywordErrorDefinition = { message: ({params: {len}}) => str`should NOT have more than ${len} items`, params: ({params: {len}}) => _`{limit: ${len}}`, diff --git a/lib/vocabularies/applicator/additionalProperties.ts b/lib/vocabularies/applicator/additionalProperties.ts index 7e4f55a91..e54712eeb 100644 --- a/lib/vocabularies/applicator/additionalProperties.ts +++ b/lib/vocabularies/applicator/additionalProperties.ts @@ -4,6 +4,10 @@ import {applySubschema, SubschemaApplication, Type} from "../../compile/subschem import {_, nil, or, Code, Name} from "../../compile/codegen" import N from "../../compile/names" +export interface AdditionalPropsErrorParams { + additionalProperty: string +} + const error: KeywordErrorDefinition = { message: "should NOT have additional properties", params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`, diff --git a/lib/vocabularies/applicator/dependencies.ts b/lib/vocabularies/applicator/dependencies.ts index da279861b..c5ccbe547 100644 --- a/lib/vocabularies/applicator/dependencies.ts +++ b/lib/vocabularies/applicator/dependencies.ts @@ -11,6 +11,13 @@ interface PropertyDependencies { type SchemaDependencies = SchemaMap +export interface DependenciesErrorParams { + property: string + missingProperty: string + depsCount: number + deps: string // TODO change to string[] +} + const error: KeywordErrorDefinition = { message: ({params: {property, depsCount, deps}}) => { const property_ies = depsCount === 1 ? "property" : "properties" @@ -20,7 +27,7 @@ const error: KeywordErrorDefinition = { _`{property: ${property}, missingProperty: ${missingProperty}, depsCount: ${depsCount}, - deps: ${deps}}`, // TODO change to reference? + deps: ${deps}}`, // TODO change to reference } const def: CodeKeywordDefinition = { diff --git a/lib/vocabularies/applicator/if.ts b/lib/vocabularies/applicator/if.ts index 4bcd3654c..714e38de1 100644 --- a/lib/vocabularies/applicator/if.ts +++ b/lib/vocabularies/applicator/if.ts @@ -4,6 +4,10 @@ import {alwaysValidSchema, checkStrictMode} from "../util" import {applySubschema} from "../../compile/subschema" import {_, str, Name} from "../../compile/codegen" +export interface IfErrorParams { + failingKeyword: string +} + const error: KeywordErrorDefinition = { message: ({params}) => str`should match "${params.ifClause}" schema`, params: ({params}) => _`{failingKeyword: ${params.ifClause}}`, diff --git a/lib/vocabularies/applicator/oneOf.ts b/lib/vocabularies/applicator/oneOf.ts index cc4c74d54..37f3bfd1e 100644 --- a/lib/vocabularies/applicator/oneOf.ts +++ b/lib/vocabularies/applicator/oneOf.ts @@ -4,6 +4,10 @@ import {alwaysValidSchema} from "../util" import {applySubschema} from "../../compile/subschema" import {_} from "../../compile/codegen" +export interface OneOfErrorParams { + passingSchemas: [number, number] +} + const error: KeywordErrorDefinition = { message: "should match exactly one schema in oneOf", params: ({params}) => _`{passingSchemas: ${params.passing}}`, diff --git a/lib/vocabularies/applicator/propertyNames.ts b/lib/vocabularies/applicator/propertyNames.ts index 72d67c4f0..5e775291d 100644 --- a/lib/vocabularies/applicator/propertyNames.ts +++ b/lib/vocabularies/applicator/propertyNames.ts @@ -4,6 +4,10 @@ import {alwaysValidSchema} from "../util" import {applySubschema} from "../../compile/subschema" import {_, str} from "../../compile/codegen" +export interface PropertyNamesErrorParams { + propertyName: string +} + const error: KeywordErrorDefinition = { message: ({params}) => str`property name '${params.propertyName}' is invalid`, // TODO double quotes? params: ({params}) => _`{propertyName: ${params.propertyName}}`, diff --git a/lib/vocabularies/core/ref.ts b/lib/vocabularies/core/ref.ts index ea316a0d0..af536da5b 100644 --- a/lib/vocabularies/core/ref.ts +++ b/lib/vocabularies/core/ref.ts @@ -7,6 +7,10 @@ import {_, str, nil, Code, Name} from "../../compile/codegen" import N from "../../compile/names" import {SchemaEnv, resolveRef} from "../../compile" +export interface RefErrorParams { + ref: string +} + const error: KeywordErrorDefinition = { message: ({schema}) => str`can't resolve reference ${schema}`, params: ({schema}) => _`{ref: ${schema}}`, diff --git a/lib/vocabularies/errors.ts b/lib/vocabularies/errors.ts new file mode 100644 index 000000000..ea36eabee --- /dev/null +++ b/lib/vocabularies/errors.ts @@ -0,0 +1,69 @@ +import type {ErrorObject} from "../types" +import type {RefErrorParams} from "./core/ref" +import type {TypeErrorParams} from "../compile/validate/dataType" +import type {AdditionalItemsErrorParams} from "./applicator/additionalItems" +import type {AdditionalPropsErrorParams} from "./applicator/additionalProperties" +import type {DependenciesErrorParams} from "./applicator/dependencies" +import type {IfErrorParams} from "./applicator/if" +import type {OneOfErrorParams} from "./applicator/oneOf" +import type {PropertyNamesErrorParams} from "./applicator/propertyNames" +import type {LimitErrorParams, LimitNumberErrorParams} from "./validation/limit" +import type {MultipleOfErrorParams} from "./validation/multipleOf" +import type {PatternErrorParams} from "./validation/pattern" +import type {RequiredErrorParams} from "./validation/required" +import type {UniqueItemsErrorParams} from "./validation/uniqueItems" +import type {ConstErrorParams} from "./validation/const" +import type {EnumErrorParams} from "./validation/enum" +import type {FormatErrorParams} from "./format/format" + +type LimitKeyword = + | "maxItems" + | "minItems" + | "minProperties" + | "maxProperties" + | "minLength" + | "maxLength" + +type LimitNumberKeyword = "maximum" | "minimum" | "exclusiveMaximum" | "exclusiveMinimum" + +type RefError = ErrorObject<"ref", RefErrorParams> +type TypeError = ErrorObject<"type", TypeErrorParams> +type ErrorWithoutParams = ErrorObject< + "anyOf" | "contains" | "not" | "false schema", + Record +> +type AdditionalItemsError = ErrorObject<"additionalItems", AdditionalItemsErrorParams> +type AdditionalPropsError = ErrorObject<"additionalProperties", AdditionalPropsErrorParams> +type DependenciesError = ErrorObject<"dependencies", DependenciesErrorParams> +type IfKeywordError = ErrorObject<"if", IfErrorParams> +type OneOfError = ErrorObject<"oneOf", OneOfErrorParams> +type PropertyNamesError = ErrorObject<"propertyNames", PropertyNamesErrorParams> +type LimitError = ErrorObject +type LimitNumberError = ErrorObject +type MultipleOfError = ErrorObject<"multipleOf", MultipleOfErrorParams> +type PatternError = ErrorObject<"pattern", PatternErrorParams> +type RequiredError = ErrorObject<"required", RequiredErrorParams> +type UniqueItemsError = ErrorObject<"uniqueItems", UniqueItemsErrorParams> +type ConstError = ErrorObject<"const", ConstErrorParams> +type EnumError = ErrorObject<"enum", EnumErrorParams> +type FormatError = ErrorObject<"format", FormatErrorParams> + +export type DefinedError = + | RefError + | TypeError + | ErrorWithoutParams + | AdditionalItemsError + | AdditionalPropsError + | DependenciesError + | IfKeywordError + | OneOfError + | PropertyNamesError + | LimitNumberError + | MultipleOfError + | LimitError + | PatternError + | RequiredError + | UniqueItemsError + | ConstError + | EnumError + | FormatError diff --git a/lib/vocabularies/format/format.ts b/lib/vocabularies/format/format.ts index 437af8477..281bc47f7 100644 --- a/lib/vocabularies/format/format.ts +++ b/lib/vocabularies/format/format.ts @@ -17,6 +17,10 @@ type FormatValidate = | RegExp | string +export interface FormatErrorParams { + format: string +} + const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`should match format "${schemaCode}"`, params: ({schemaCode}) => _`{format: ${schemaCode}}`, diff --git a/lib/vocabularies/validation/const.ts b/lib/vocabularies/validation/const.ts index f44d7b9b1..ad9352c90 100644 --- a/lib/vocabularies/validation/const.ts +++ b/lib/vocabularies/validation/const.ts @@ -3,6 +3,10 @@ import type KeywordCxt from "../../compile/context" import {_} from "../../compile/codegen" import equal from "fast-deep-equal" +export interface ConstErrorParams { + allowedValue: any +} + const error: KeywordErrorDefinition = { message: "should be equal to constant", params: ({schemaCode}) => _`{allowedValue: ${schemaCode}}`, diff --git a/lib/vocabularies/validation/enum.ts b/lib/vocabularies/validation/enum.ts index f2bfb302e..de041b9ca 100644 --- a/lib/vocabularies/validation/enum.ts +++ b/lib/vocabularies/validation/enum.ts @@ -3,6 +3,10 @@ import type KeywordCxt from "../../compile/context" import {_, or, Name, Code} from "../../compile/codegen" import equal from "fast-deep-equal" +export interface EnumErrorParams { + allowedValues: any[] +} + const error: KeywordErrorDefinition = { message: "should be equal to one of the allowed values", params: ({schemaCode}) => _`{allowedValues: ${schemaCode}}`, diff --git a/lib/vocabularies/validation/limit.ts b/lib/vocabularies/validation/limit.ts index 22086f1eb..790894746 100644 --- a/lib/vocabularies/validation/limit.ts +++ b/lib/vocabularies/validation/limit.ts @@ -11,6 +11,15 @@ const OPS: {[index: string]: {fail: Code; ok: Code; okStr: string}} = { exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE}, } +export interface LimitErrorParams { + limit: number +} + +export interface LimitNumberErrorParams { + limit: number + comparison: "<=" | ">=" | "<" | ">" +} + const error: KeywordErrorDefinition = { message: ({keyword, schemaCode}) => str`should be ${OPS[keyword].okStr} ${schemaCode}`, params: ({keyword, schemaCode}) => _`{comparison: ${OPS[keyword].okStr}, limit: ${schemaCode}}`, diff --git a/lib/vocabularies/validation/multipleOf.ts b/lib/vocabularies/validation/multipleOf.ts index d1a763e7a..11d4de480 100644 --- a/lib/vocabularies/validation/multipleOf.ts +++ b/lib/vocabularies/validation/multipleOf.ts @@ -2,6 +2,10 @@ import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str} from "../../compile/codegen" +export interface MultipleOfErrorParams { + multipleOf: number +} + const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`should be multiple of ${schemaCode}`, params: ({schemaCode}) => _`{multipleOf: ${schemaCode}}`, diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index 0d41aab58..d51a46268 100644 --- a/lib/vocabularies/validation/pattern.ts +++ b/lib/vocabularies/validation/pattern.ts @@ -3,6 +3,10 @@ import type KeywordCxt from "../../compile/context" import {usePattern} from "../util" import {_, str} from "../../compile/codegen" +export interface PatternErrorParams { + pattern: string +} + const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`should match pattern "${schemaCode}"`, params: ({schemaCode}) => _`{pattern: ${schemaCode}}`, diff --git a/lib/vocabularies/validation/required.ts b/lib/vocabularies/validation/required.ts index dc66ff15b..a14de9e77 100644 --- a/lib/vocabularies/validation/required.ts +++ b/lib/vocabularies/validation/required.ts @@ -4,6 +4,10 @@ import {propertyInData, noPropertyInData} from "../util" import {checkReportMissingProp, checkMissingProp, reportMissingProp} from "../missing" import {_, str, nil, Name} from "../../compile/codegen" +export interface RequiredErrorParams { + missingProperty: string +} + const error: KeywordErrorDefinition = { message: ({params: {missingProperty}}) => str`should have required property '${missingProperty}'`, params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`, diff --git a/lib/vocabularies/validation/uniqueItems.ts b/lib/vocabularies/validation/uniqueItems.ts index 53f065efe..f89f0371c 100644 --- a/lib/vocabularies/validation/uniqueItems.ts +++ b/lib/vocabularies/validation/uniqueItems.ts @@ -5,6 +5,11 @@ import {checkDataTypes, DataType} from "../../compile/util" import {_, str, Name} from "../../compile/codegen" import equal from "fast-deep-equal" +export interface UniqueItemsErrorParams { + i: number + j: number +} + const error: KeywordErrorDefinition = { message: ({params: {i, j}}) => str`should NOT have duplicate items (items ## ${j} and ${i} are identical)`, diff --git a/spec/types/async-validate.spec.ts b/spec/types/async-validate.spec.ts index 82fa02f4c..f8eba8d8b 100644 --- a/spec/types/async-validate.spec.ts +++ b/spec/types/async-validate.spec.ts @@ -1,6 +1,7 @@ import type {AnySchemaObject, SchemaObject, AsyncSchema} from "../../dist/types" import _Ajv from "../ajv" -const should = require("../chai").should() +import chai from "../chai" +const should = chai.should() interface Foo { foo: number diff --git a/spec/types/error-parameters.spec.ts b/spec/types/error-parameters.spec.ts new file mode 100644 index 000000000..638b5fadc --- /dev/null +++ b/spec/types/error-parameters.spec.ts @@ -0,0 +1,31 @@ +import {DefinedError} from "../../dist/types" +import _Ajv from "../ajv" +import chai from "../chai" +const should = chai.should() + +describe("error object parameters type", () => { + const ajv = new _Ajv({allErrors: true}) + + it("should be determined by the keyword", () => { + const validate = ajv.compile({minimum: 0, multipleOf: 2}) + const valid = validate(-1) + valid.should.equal(false) + const errs = validate.errors + if (errs) { + errs.length.should.equal(2) + for (const err of errs as DefinedError[]) { + switch (err.keyword) { + case "minimum": + err.params.limit.should.equal(0) + err.params.comparison.should.equal(">=") + break + case "multipleOf": + err.params.multipleOf.should.equal(2) + break + default: + should.fail() + } + } + } + }) +}) From f7a5f9dc967932637d221ab2fd264aa48d434bd8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 17 Sep 2020 08:26:30 +0100 Subject: [PATCH 3/8] refactor: keyword validation errors --- lib/ajv.ts | 2 + lib/compile/validate/dataType.ts | 5 +- lib/types/index.ts | 2 - .../applicator/additionalItems.ts | 8 +-- .../applicator/additionalProperties.ts | 9 +-- lib/vocabularies/applicator/dependencies.ts | 23 ++++-- lib/vocabularies/applicator/if.ts | 11 +-- lib/vocabularies/applicator/index.ts | 45 ++++++++++-- lib/vocabularies/applicator/oneOf.ts | 11 +-- lib/vocabularies/applicator/propertyNames.ts | 6 +- lib/vocabularies/core/ref.ts | 11 +-- lib/vocabularies/errors.ts | 71 ++----------------- lib/vocabularies/format/format.ts | 5 +- lib/vocabularies/validation/const.ts | 6 +- lib/vocabularies/validation/enum.ts | 6 +- lib/vocabularies/validation/index.ts | 33 ++++++--- lib/vocabularies/validation/limit.ts | 41 ----------- lib/vocabularies/validation/limitNumber.ts | 39 ++++++++++ lib/vocabularies/validation/multipleOf.ts | 6 +- lib/vocabularies/validation/pattern.ts | 6 +- lib/vocabularies/validation/required.ts | 6 +- lib/vocabularies/validation/uniqueItems.ts | 7 +- spec/types/error-parameters.spec.ts | 2 +- 23 files changed, 174 insertions(+), 187 deletions(-) delete mode 100644 lib/vocabularies/validation/limit.ts create mode 100644 lib/vocabularies/validation/limitNumber.ts diff --git a/lib/ajv.ts b/lib/ajv.ts index 4b001b783..58755a825 100644 --- a/lib/ajv.ts +++ b/lib/ajv.ts @@ -13,6 +13,7 @@ export { Options, ValidateFunction, AsyncValidateFunction, + ErrorObject, CacheInterface, Logger, } from "./types" @@ -23,6 +24,7 @@ export interface Plugin { import KeywordCxt from "./compile/context" export {KeywordCxt} +export {DefinedError} from "./vocabularies/errors" import type { Schema, diff --git a/lib/compile/validate/dataType.ts b/lib/compile/validate/dataType.ts index b3354e22b..d9642069f 100644 --- a/lib/compile/validate/dataType.ts +++ b/lib/compile/validate/dataType.ts @@ -2,6 +2,7 @@ import type { SchemaObjCxt, KeywordErrorDefinition, KeywordErrorCxt, + ErrorObject, AnySchemaObject, } from "../../types" import type {ValidationRules} from "../rules" @@ -134,9 +135,7 @@ function assignParentData({gen, parentData, parentDataProperty}: SchemaObjCxt, e ) } -export interface TypeErrorParams { - type: string -} +export type TypeError = ErrorObject<"type", {type: string}> const typeError: KeywordErrorDefinition = { message: ({schema}) => str`should be ${schema}`, diff --git a/lib/types/index.ts b/lib/types/index.ts index c07f058ba..c6c659240 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -3,8 +3,6 @@ import type {SchemaEnv} from "../compile" import type KeywordCxt from "../compile/context" import type Ajv from "../ajv" -export {DefinedError} from "../vocabularies/errors" - interface _SchemaObject { $id?: string $schema?: string diff --git a/lib/vocabularies/applicator/additionalItems.ts b/lib/vocabularies/applicator/additionalItems.ts index df9d5643c..88d1ae8c6 100644 --- a/lib/vocabularies/applicator/additionalItems.ts +++ b/lib/vocabularies/applicator/additionalItems.ts @@ -1,12 +1,10 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema, checkStrictMode} from "../util" import {applySubschema, Type} from "../../compile/subschema" import {_, Name, str} from "../../compile/codegen" -export interface AdditionalItemsErrorParams { - limit: number -} +export type AdditionalItemsError = ErrorObject<"additionalItems", {limit: number}> const error: KeywordErrorDefinition = { message: ({params: {len}}) => str`should NOT have more than ${len} items`, @@ -14,7 +12,7 @@ const error: KeywordErrorDefinition = { } const def: CodeKeywordDefinition = { - keyword: "additionalItems", + keyword: "additionalItems" as const, type: "array", schemaType: ["boolean", "object"], before: "uniqueItems", diff --git a/lib/vocabularies/applicator/additionalProperties.ts b/lib/vocabularies/applicator/additionalProperties.ts index e54712eeb..c2ab3d255 100644 --- a/lib/vocabularies/applicator/additionalProperties.ts +++ b/lib/vocabularies/applicator/additionalProperties.ts @@ -1,12 +1,13 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import {allSchemaProperties, schemaRefOrVal, alwaysValidSchema, usePattern} from "../util" import {applySubschema, SubschemaApplication, Type} from "../../compile/subschema" import {_, nil, or, Code, Name} from "../../compile/codegen" import N from "../../compile/names" -export interface AdditionalPropsErrorParams { - additionalProperty: string -} +export type AdditionalPropertiesError = ErrorObject< + "additionalProperties", + {additionalProperty: string} +> const error: KeywordErrorDefinition = { message: "should NOT have additional properties", diff --git a/lib/vocabularies/applicator/dependencies.ts b/lib/vocabularies/applicator/dependencies.ts index c5ccbe547..f6437eb73 100644 --- a/lib/vocabularies/applicator/dependencies.ts +++ b/lib/vocabularies/applicator/dependencies.ts @@ -1,4 +1,10 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition, SchemaMap, AnySchema} from "../../types" +import type { + CodeKeywordDefinition, + ErrorObject, + KeywordErrorDefinition, + SchemaMap, + AnySchema, +} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema, propertyInData} from "../util" import {applySubschema} from "../../compile/subschema" @@ -11,12 +17,15 @@ interface PropertyDependencies { type SchemaDependencies = SchemaMap -export interface DependenciesErrorParams { - property: string - missingProperty: string - depsCount: number - deps: string // TODO change to string[] -} +export type DependenciesError = ErrorObject< + "dependencies", + { + property: string + missingProperty: string + depsCount: number + deps: string // TODO change to string[] + } +> const error: KeywordErrorDefinition = { message: ({params: {property, depsCount, deps}}) => { diff --git a/lib/vocabularies/applicator/if.ts b/lib/vocabularies/applicator/if.ts index 714e38de1..8ff19f257 100644 --- a/lib/vocabularies/applicator/if.ts +++ b/lib/vocabularies/applicator/if.ts @@ -1,12 +1,15 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition, SchemaObjCxt} from "../../types" +import type { + CodeKeywordDefinition, + ErrorObject, + KeywordErrorDefinition, + SchemaObjCxt, +} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema, checkStrictMode} from "../util" import {applySubschema} from "../../compile/subschema" import {_, str, Name} from "../../compile/codegen" -export interface IfErrorParams { - failingKeyword: string -} +export type IfKeywordError = ErrorObject<"if", {failingKeyword: string}> const error: KeywordErrorDefinition = { message: ({params}) => str`should match "${params.ifClause}" schema`, diff --git a/lib/vocabularies/applicator/index.ts b/lib/vocabularies/applicator/index.ts index 15abf1fdf..ca3c16958 100644 --- a/lib/vocabularies/applicator/index.ts +++ b/lib/vocabularies/applicator/index.ts @@ -1,19 +1,36 @@ -import type {Vocabulary} from "../../types" -import additionalItems from "./additionalItems" +import type {ErrorObject, Vocabulary} from "../../types" +import additionalItems, {AdditionalItemsError} from "./additionalItems" import items from "./items" import contains from "./contains" -import dependencies from "./dependencies" -import propertyNames from "./propertyNames" -import additionalProperties from "./additionalProperties" +import dependencies, {DependenciesError} from "./dependencies" +import propertyNames, {PropertyNamesError} from "./propertyNames" +import additionalProperties, {AdditionalPropertiesError} from "./additionalProperties" import properties from "./properties" import patternProperties from "./patternProperties" import notKeyword from "./not" import anyOf from "./anyOf" -import oneOf from "./oneOf" +import oneOf, {OneOfError} from "./oneOf" import allOf from "./allOf" -import ifKeyword from "./if" +import ifKeyword, {IfKeywordError} from "./if" import thenElse from "./thenElse" +export type ApplicatorKeyword = + | "additionalItems" + | "items" + | "contains" + | "dependencies" + | "propertyNames" + | "additionalProperties" + | "properties" + | "patternProperties" + | "not" + | "anyOf" + | "oneOf" + | "allOf" + | "if" + | "then" + | "else" + const applicator: Vocabulary = [ // array additionalItems, @@ -35,3 +52,17 @@ const applicator: Vocabulary = [ ] export default applicator + +export type ApplicatorKeywordError = + | ErrorWithoutParams + | AdditionalItemsError + | AdditionalPropertiesError + | DependenciesError + | IfKeywordError + | OneOfError + | PropertyNamesError + +export type ErrorWithoutParams = ErrorObject< + "anyOf" | "contains" | "not" | "false schema", + Record +> diff --git a/lib/vocabularies/applicator/oneOf.ts b/lib/vocabularies/applicator/oneOf.ts index 37f3bfd1e..bcd554085 100644 --- a/lib/vocabularies/applicator/oneOf.ts +++ b/lib/vocabularies/applicator/oneOf.ts @@ -1,12 +1,15 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition, AnySchema} from "../../types" +import type { + CodeKeywordDefinition, + ErrorObject, + KeywordErrorDefinition, + AnySchema, +} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema} from "../util" import {applySubschema} from "../../compile/subschema" import {_} from "../../compile/codegen" -export interface OneOfErrorParams { - passingSchemas: [number, number] -} +export type OneOfError = ErrorObject<"oneOf", {passingSchemas: [number, number]}> const error: KeywordErrorDefinition = { message: "should match exactly one schema in oneOf", diff --git a/lib/vocabularies/applicator/propertyNames.ts b/lib/vocabularies/applicator/propertyNames.ts index 5e775291d..5c65a1d92 100644 --- a/lib/vocabularies/applicator/propertyNames.ts +++ b/lib/vocabularies/applicator/propertyNames.ts @@ -1,12 +1,10 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {alwaysValidSchema} from "../util" import {applySubschema} from "../../compile/subschema" import {_, str} from "../../compile/codegen" -export interface PropertyNamesErrorParams { - propertyName: string -} +export type PropertyNamesError = ErrorObject<"propertyNames", {propertyName: string}> const error: KeywordErrorDefinition = { message: ({params}) => str`property name '${params.propertyName}' is invalid`, // TODO double quotes? diff --git a/lib/vocabularies/core/ref.ts b/lib/vocabularies/core/ref.ts index af536da5b..aeb3e0af3 100644 --- a/lib/vocabularies/core/ref.ts +++ b/lib/vocabularies/core/ref.ts @@ -1,4 +1,9 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition, AnySchema} from "../../types" +import type { + CodeKeywordDefinition, + ErrorObject, + KeywordErrorDefinition, + AnySchema, +} from "../../types" import type KeywordCxt from "../../compile/context" import {MissingRefError} from "../../compile/error_classes" import {applySubschema} from "../../compile/subschema" @@ -7,9 +12,7 @@ import {_, str, nil, Code, Name} from "../../compile/codegen" import N from "../../compile/names" import {SchemaEnv, resolveRef} from "../../compile" -export interface RefErrorParams { - ref: string -} +export type RefError = ErrorObject<"ref", {ref: string}> const error: KeywordErrorDefinition = { message: ({schema}) => str`can't resolve reference ${schema}`, diff --git a/lib/vocabularies/errors.ts b/lib/vocabularies/errors.ts index ea36eabee..f501fd229 100644 --- a/lib/vocabularies/errors.ts +++ b/lib/vocabularies/errors.ts @@ -1,69 +1,12 @@ -import type {ErrorObject} from "../types" -import type {RefErrorParams} from "./core/ref" -import type {TypeErrorParams} from "../compile/validate/dataType" -import type {AdditionalItemsErrorParams} from "./applicator/additionalItems" -import type {AdditionalPropsErrorParams} from "./applicator/additionalProperties" -import type {DependenciesErrorParams} from "./applicator/dependencies" -import type {IfErrorParams} from "./applicator/if" -import type {OneOfErrorParams} from "./applicator/oneOf" -import type {PropertyNamesErrorParams} from "./applicator/propertyNames" -import type {LimitErrorParams, LimitNumberErrorParams} from "./validation/limit" -import type {MultipleOfErrorParams} from "./validation/multipleOf" -import type {PatternErrorParams} from "./validation/pattern" -import type {RequiredErrorParams} from "./validation/required" -import type {UniqueItemsErrorParams} from "./validation/uniqueItems" -import type {ConstErrorParams} from "./validation/const" -import type {EnumErrorParams} from "./validation/enum" -import type {FormatErrorParams} from "./format/format" - -type LimitKeyword = - | "maxItems" - | "minItems" - | "minProperties" - | "maxProperties" - | "minLength" - | "maxLength" - -type LimitNumberKeyword = "maximum" | "minimum" | "exclusiveMaximum" | "exclusiveMinimum" - -type RefError = ErrorObject<"ref", RefErrorParams> -type TypeError = ErrorObject<"type", TypeErrorParams> -type ErrorWithoutParams = ErrorObject< - "anyOf" | "contains" | "not" | "false schema", - Record -> -type AdditionalItemsError = ErrorObject<"additionalItems", AdditionalItemsErrorParams> -type AdditionalPropsError = ErrorObject<"additionalProperties", AdditionalPropsErrorParams> -type DependenciesError = ErrorObject<"dependencies", DependenciesErrorParams> -type IfKeywordError = ErrorObject<"if", IfErrorParams> -type OneOfError = ErrorObject<"oneOf", OneOfErrorParams> -type PropertyNamesError = ErrorObject<"propertyNames", PropertyNamesErrorParams> -type LimitError = ErrorObject -type LimitNumberError = ErrorObject -type MultipleOfError = ErrorObject<"multipleOf", MultipleOfErrorParams> -type PatternError = ErrorObject<"pattern", PatternErrorParams> -type RequiredError = ErrorObject<"required", RequiredErrorParams> -type UniqueItemsError = ErrorObject<"uniqueItems", UniqueItemsErrorParams> -type ConstError = ErrorObject<"const", ConstErrorParams> -type EnumError = ErrorObject<"enum", EnumErrorParams> -type FormatError = ErrorObject<"format", FormatErrorParams> +import type {RefError} from "./core/ref" +import type {TypeError} from "../compile/validate/dataType" +import type {ApplicatorKeywordError} from "./applicator" +import type {ValidationKeywordError} from "./validation" +import type {FormatError} from "./format/format" export type DefinedError = | RefError | TypeError - | ErrorWithoutParams - | AdditionalItemsError - | AdditionalPropsError - | DependenciesError - | IfKeywordError - | OneOfError - | PropertyNamesError - | LimitNumberError - | MultipleOfError - | LimitError - | PatternError - | RequiredError - | UniqueItemsError - | ConstError - | EnumError + | ApplicatorKeywordError + | ValidationKeywordError | FormatError diff --git a/lib/vocabularies/format/format.ts b/lib/vocabularies/format/format.ts index 281bc47f7..74ce00d72 100644 --- a/lib/vocabularies/format/format.ts +++ b/lib/vocabularies/format/format.ts @@ -4,6 +4,7 @@ import type { AsyncFormatValidator, CodeKeywordDefinition, KeywordErrorDefinition, + ErrorObject, } from "../../types" import type KeywordCxt from "../../compile/context" import {_, str, nil, or, Code, getProperty} from "../../compile/codegen" @@ -17,9 +18,7 @@ type FormatValidate = | RegExp | string -export interface FormatErrorParams { - format: string -} +export type FormatError = ErrorObject<"format", {format: string}> const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`should match format "${schemaCode}"`, diff --git a/lib/vocabularies/validation/const.ts b/lib/vocabularies/validation/const.ts index ad9352c90..fc122737f 100644 --- a/lib/vocabularies/validation/const.ts +++ b/lib/vocabularies/validation/const.ts @@ -1,11 +1,9 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_} from "../../compile/codegen" import equal from "fast-deep-equal" -export interface ConstErrorParams { - allowedValue: any -} +export type ConstError = ErrorObject<"const", {allowedValue: any}> const error: KeywordErrorDefinition = { message: "should be equal to constant", diff --git a/lib/vocabularies/validation/enum.ts b/lib/vocabularies/validation/enum.ts index de041b9ca..d6fbdb108 100644 --- a/lib/vocabularies/validation/enum.ts +++ b/lib/vocabularies/validation/enum.ts @@ -1,11 +1,9 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, or, Name, Code} from "../../compile/codegen" import equal from "fast-deep-equal" -export interface EnumErrorParams { - allowedValues: any[] -} +export type EnumError = ErrorObject<"enum", {allowedValues: any[]}> const error: KeywordErrorDefinition = { message: "should be equal to one of the allowed values", diff --git a/lib/vocabularies/validation/index.ts b/lib/vocabularies/validation/index.ts index 1c555ec41..ab27a5ad1 100644 --- a/lib/vocabularies/validation/index.ts +++ b/lib/vocabularies/validation/index.ts @@ -1,18 +1,18 @@ -import type {Vocabulary} from "../../types" -import limit from "./limit" -import multipleOf from "./multipleOf" +import type {ErrorObject, Vocabulary} from "../../types" +import limitNumber, {LimitNumberError} from "./limitNumber" +import multipleOf, {MultipleOfError} from "./multipleOf" import limitLength from "./limitLength" -import pattern from "./pattern" +import pattern, {PatternError} from "./pattern" import limitProperties from "./limitProperties" -import required from "./required" +import required, {RequiredError} from "./required" import limitItems from "./limitItems" -import uniqueItems from "./uniqueItems" -import constKeyword from "./const" -import enumKeyword from "./enum" +import uniqueItems, {UniqueItemsError} from "./uniqueItems" +import constKeyword, {ConstError} from "./const" +import enumKeyword, {EnumError} from "./enum" const validation: Vocabulary = [ // number - limit, + limitNumber, multipleOf, // string limitLength, @@ -30,3 +30,18 @@ const validation: Vocabulary = [ ] export default validation + +type LimitError = ErrorObject< + "maxItems" | "minItems" | "minProperties" | "maxProperties" | "minLength" | "maxLength", + {limit: number} +> + +export type ValidationKeywordError = + | LimitError + | LimitNumberError + | MultipleOfError + | PatternError + | RequiredError + | UniqueItemsError + | ConstError + | EnumError diff --git a/lib/vocabularies/validation/limit.ts b/lib/vocabularies/validation/limit.ts deleted file mode 100644 index 790894746..000000000 --- a/lib/vocabularies/validation/limit.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" -import type KeywordCxt from "../../compile/context" -import {_, str, operators, Code} from "../../compile/codegen" - -const ops = operators - -const OPS: {[index: string]: {fail: Code; ok: Code; okStr: string}} = { - maximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT}, - minimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT}, - exclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE}, - exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE}, -} - -export interface LimitErrorParams { - limit: number -} - -export interface LimitNumberErrorParams { - limit: number - comparison: "<=" | ">=" | "<" | ">" -} - -const error: KeywordErrorDefinition = { - message: ({keyword, schemaCode}) => str`should be ${OPS[keyword].okStr} ${schemaCode}`, - params: ({keyword, schemaCode}) => _`{comparison: ${OPS[keyword].okStr}, limit: ${schemaCode}}`, -} - -const def: CodeKeywordDefinition = { - keyword: ["maximum", "minimum", "exclusiveMaximum", "exclusiveMinimum"], - type: "number", - schemaType: "number", - $data: true, - error, - code(cxt: KeywordCxt) { - const {keyword, data, schemaCode} = cxt - // const bdt = bad$DataType(schemaCode, def.schemaType, $data) - cxt.fail$data(_`(${data} ${OPS[keyword].fail} ${schemaCode} || isNaN(${data}))`) - }, -} - -export default def diff --git a/lib/vocabularies/validation/limitNumber.ts b/lib/vocabularies/validation/limitNumber.ts new file mode 100644 index 000000000..66e30f7dd --- /dev/null +++ b/lib/vocabularies/validation/limitNumber.ts @@ -0,0 +1,39 @@ +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" +import type KeywordCxt from "../../compile/context" +import {_, str, operators, Code} from "../../compile/codegen" + +const ops = operators + +type Kwd = "maximum" | "minimum" | "exclusiveMaximum" | "exclusiveMinimum" + +type Comparison = "<=" | ">=" | "<" | ">" + +const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = { + maximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT}, + minimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT}, + exclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE}, + exclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE}, +} + +export type LimitNumberError = ErrorObject + +const error: KeywordErrorDefinition = { + message: ({keyword, schemaCode}) => str`should be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`, + params: ({keyword, schemaCode}) => + _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`, +} + +const def: CodeKeywordDefinition = { + keyword: Object.keys(KWDs), + type: "number", + schemaType: "number", + $data: true, + error, + code(cxt: KeywordCxt) { + const {keyword, data, schemaCode} = cxt + // const bdt = bad$DataType(schemaCode, def.schemaType, $data) + cxt.fail$data(_`(${data} ${KWDs[keyword as Kwd].fail} ${schemaCode} || isNaN(${data}))`) + }, +} + +export default def diff --git a/lib/vocabularies/validation/multipleOf.ts b/lib/vocabularies/validation/multipleOf.ts index 11d4de480..ef703769c 100644 --- a/lib/vocabularies/validation/multipleOf.ts +++ b/lib/vocabularies/validation/multipleOf.ts @@ -1,10 +1,8 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {_, str} from "../../compile/codegen" -export interface MultipleOfErrorParams { - multipleOf: number -} +export type MultipleOfError = ErrorObject<"multipleOf", {multipleOf: number}> const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`should be multiple of ${schemaCode}`, diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index d51a46268..b23aa1c2c 100644 --- a/lib/vocabularies/validation/pattern.ts +++ b/lib/vocabularies/validation/pattern.ts @@ -1,11 +1,9 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {usePattern} from "../util" import {_, str} from "../../compile/codegen" -export interface PatternErrorParams { - pattern: string -} +export type PatternError = ErrorObject<"pattern", {pattern: string}> const error: KeywordErrorDefinition = { message: ({schemaCode}) => str`should match pattern "${schemaCode}"`, diff --git a/lib/vocabularies/validation/required.ts b/lib/vocabularies/validation/required.ts index a14de9e77..702b04fef 100644 --- a/lib/vocabularies/validation/required.ts +++ b/lib/vocabularies/validation/required.ts @@ -1,12 +1,10 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {propertyInData, noPropertyInData} from "../util" import {checkReportMissingProp, checkMissingProp, reportMissingProp} from "../missing" import {_, str, nil, Name} from "../../compile/codegen" -export interface RequiredErrorParams { - missingProperty: string -} +export type RequiredError = ErrorObject<"required", {missingProperty: string}> const error: KeywordErrorDefinition = { message: ({params: {missingProperty}}) => str`should have required property '${missingProperty}'`, diff --git a/lib/vocabularies/validation/uniqueItems.ts b/lib/vocabularies/validation/uniqueItems.ts index f89f0371c..9c0533dc6 100644 --- a/lib/vocabularies/validation/uniqueItems.ts +++ b/lib/vocabularies/validation/uniqueItems.ts @@ -1,14 +1,11 @@ -import type {CodeKeywordDefinition, KeywordErrorDefinition} from "../../types" +import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types" import type KeywordCxt from "../../compile/context" import {getSchemaTypes} from "../../compile/validate/dataType" import {checkDataTypes, DataType} from "../../compile/util" import {_, str, Name} from "../../compile/codegen" import equal from "fast-deep-equal" -export interface UniqueItemsErrorParams { - i: number - j: number -} +export type UniqueItemsError = ErrorObject<"uniqueItems", {i: number; j: number}> const error: KeywordErrorDefinition = { message: ({params: {i, j}}) => diff --git a/spec/types/error-parameters.spec.ts b/spec/types/error-parameters.spec.ts index 638b5fadc..16358e69f 100644 --- a/spec/types/error-parameters.spec.ts +++ b/spec/types/error-parameters.spec.ts @@ -1,4 +1,4 @@ -import {DefinedError} from "../../dist/types" +import {DefinedError} from "../../dist/ajv" import _Ajv from "../ajv" import chai from "../chai" const should = chai.should() From 4fb63c7e8123b340336cce3a21f9c8b650fdb60f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 17 Sep 2020 12:52:38 +0100 Subject: [PATCH 4/8] remove coveralls from dependencies --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 2ae1e0dcb..64655ef1d 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "ajv-formats": "^0.3.2", "browserify": "^16.2.0", "chai": "^4.0.1", - "coveralls": "^3.0.1", "cross-env": "^7.0.2", "eslint": "^7.8.1", "eslint-config-prettier": "^6.11.0", From 9180f4c626818a596bdcc7a508fc0b97fc45a4fc Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 17 Sep 2020 13:01:57 +0100 Subject: [PATCH 5/8] style: update files to match prettier config --- .github/ISSUE_TEMPLATE.md | 4 ++-- .github/ISSUE_TEMPLATE/bug-or-error-report.md | 5 ++--- .github/ISSUE_TEMPLATE/typescript.md | 2 +- .prettierignore | 2 ++ lib/refs/json-schema-draft-06.json | 10 +--------- lib/refs/json-schema-draft-07.json | 10 +--------- package.json | 2 +- spec/tests/issues/226_json_with_control_chars.json | 9 +-------- spec/tests/rules/items.json | 11 ++--------- spec/tests/schemas/advanced.json | 6 +----- spec/tests/schemas/basic.json | 4 +--- spec/tests/schemas/complex2.json | 5 +---- spec/tests/schemas/cosmicrealms.json | 7 +------ 13 files changed, 17 insertions(+), 60 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 46d6f970f..2d5317e14 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -17,6 +17,7 @@ This template is for bug or error reports. For other issues please use: ```javascript + ``` **JSON Schema** @@ -25,7 +26,6 @@ This template is for bug or error reports. For other issues please use: ```json - ``` **Sample data** @@ -34,7 +34,6 @@ This template is for bug or error reports. For other issues please use: ```json - ``` **Your code** @@ -51,6 +50,7 @@ Thank you! --> ```javascript + ``` **Validation result, data AFTER validation, error messages** diff --git a/.github/ISSUE_TEMPLATE/bug-or-error-report.md b/.github/ISSUE_TEMPLATE/bug-or-error-report.md index 9f4aeed90..897166f41 100644 --- a/.github/ISSUE_TEMPLATE/bug-or-error-report.md +++ b/.github/ISSUE_TEMPLATE/bug-or-error-report.md @@ -21,6 +21,7 @@ For other issues please see https://github.com/ajv-validator/ajv/blob/master/CON ```javascript + ``` **JSON Schema** @@ -29,7 +30,6 @@ For other issues please see https://github.com/ajv-validator/ajv/blob/master/CON ```json - ``` **Sample data** @@ -38,7 +38,6 @@ For other issues please see https://github.com/ajv-validator/ajv/blob/master/CON ```json - ``` **Your code** @@ -55,13 +54,13 @@ Thank you! --> ```javascript + ``` **Validation result, data AFTER validation, error messages** ``` - ``` **What results did you expect?** diff --git a/.github/ISSUE_TEMPLATE/typescript.md b/.github/ISSUE_TEMPLATE/typescript.md index 26bf33ed3..05ad1a7a2 100644 --- a/.github/ISSUE_TEMPLATE/typescript.md +++ b/.github/ISSUE_TEMPLATE/typescript.md @@ -22,13 +22,13 @@ Please make it as small as posssible to reproduce the issue --> ```typescript + ``` **Typescript compiler error messages** ``` - ``` **Describe the change that should be made to address the issue?** diff --git a/.prettierignore b/.prettierignore index 8d0387cf3..c61c4f88f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,6 @@ spec/JSON-Schema-Test-Suite .browser coverage dist +bundle .nyc_output +spec/_json diff --git a/lib/refs/json-schema-draft-06.json b/lib/refs/json-schema-draft-06.json index cbaabcedd..5410064ba 100644 --- a/lib/refs/json-schema-draft-06.json +++ b/lib/refs/json-schema-draft-06.json @@ -16,15 +16,7 @@ "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] }, "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] + "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", diff --git a/lib/refs/json-schema-draft-07.json b/lib/refs/json-schema-draft-07.json index 69174b843..6a7485104 100644 --- a/lib/refs/json-schema-draft-07.json +++ b/lib/refs/json-schema-draft-07.json @@ -16,15 +16,7 @@ "allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}] }, "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] + "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] }, "stringArray": { "type": "array", diff --git a/package.json b/package.json index 64655ef1d..5748eb114 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test-browser": "rm -rf .browser && npm run bundle && scripts/prepare-tests && karma start", "test-all": "npm run test-cov && if-node-version 12 npm run test-browser", "test": "npm run build && npm run json-tests && npm run eslint && npm run test-cov", - "test-ci": "AJV_FULL_TEST=true npm test", + "test-ci": "AJV_FULL_TEST=true npm test && npm run prettier:check", "prepublish": "npm run build", "watch": "watch \"npm run build\" ./lib" }, diff --git a/spec/tests/issues/226_json_with_control_chars.json b/spec/tests/issues/226_json_with_control_chars.json index 3e56dd8b2..de00a012f 100644 --- a/spec/tests/issues/226_json_with_control_chars.json +++ b/spec/tests/issues/226_json_with_control_chars.json @@ -41,14 +41,7 @@ { "description": "JSON with control characters - 'required' (#226)", "schema": { - "required": [ - "foo\nbar", - "foo\"bar", - "foo\\bar", - "foo\rbar", - "foo\tbar", - "foo\fbar" - ] + "required": ["foo\nbar", "foo\"bar", "foo\\bar", "foo\rbar", "foo\tbar", "foo\fbar"] }, "tests": [ { diff --git a/spec/tests/rules/items.json b/spec/tests/rules/items.json index b1726fa6f..ca2171767 100644 --- a/spec/tests/rules/items.json +++ b/spec/tests/rules/items.json @@ -25,10 +25,7 @@ "child": { "type": "array", "additionalItems": false, - "items": [ - {"$ref": "#/definitions/sub-child"}, - {"$ref": "#/definitions/sub-child"} - ] + "items": [{"$ref": "#/definitions/sub-child"}, {"$ref": "#/definitions/sub-child"}] }, "sub-child": { "type": "object", @@ -76,11 +73,7 @@ }, { "description": "wrong child", - "data": [ - {"foo": null}, - [{"foo": null}, {"foo": null}], - [{"foo": null}, {"foo": null}] - ], + "data": [{"foo": null}, [{"foo": null}, {"foo": null}], [{"foo": null}, {"foo": null}]], "valid": false }, { diff --git a/spec/tests/schemas/advanced.json b/spec/tests/schemas/advanced.json index 7a8b11536..cd5162778 100644 --- a/spec/tests/schemas/advanced.json +++ b/spec/tests/schemas/advanced.json @@ -71,11 +71,7 @@ }, "server": { "type": "string", - "anyOf": [ - {"format": "hostname"}, - {"format": "ipv4"}, - {"format": "ipv6"} - ] + "anyOf": [{"format": "hostname"}, {"format": "ipv4"}, {"format": "ipv6"}] } }, "required": ["type", "server", "remotePath"], diff --git a/spec/tests/schemas/basic.json b/spec/tests/schemas/basic.json index 45800a228..553855cf7 100644 --- a/spec/tests/schemas/basic.json +++ b/spec/tests/schemas/basic.json @@ -117,9 +117,7 @@ }, { "description": "valid product with tag", - "data": [ - {"tags": ["product"], "id": 1, "name": "product", "price": 100} - ], + "data": [{"tags": ["product"], "id": 1, "name": "product", "price": 100}], "valid": true }, { diff --git a/spec/tests/schemas/complex2.json b/spec/tests/schemas/complex2.json index 0dcf32ff3..759af9afd 100644 --- a/spec/tests/schemas/complex2.json +++ b/spec/tests/schemas/complex2.json @@ -149,10 +149,7 @@ "minProperties": 1, "maxProperties": 3, "additionalProperties": { - "anyOf": [ - {"$ref": "#/definitions/base58"}, - {"$ref": "#/definitions/hex"} - ] + "anyOf": [{"$ref": "#/definitions/base58"}, {"$ref": "#/definitions/hex"}] } } } diff --git a/spec/tests/schemas/cosmicrealms.json b/spec/tests/schemas/cosmicrealms.json index 1b506608c..79ea7efe0 100644 --- a/spec/tests/schemas/cosmicrealms.json +++ b/spec/tests/schemas/cosmicrealms.json @@ -84,12 +84,7 @@ "zip": 65794, "topThreeFavoriteColors": ["blue", "black", "yellow"], "favoriteSingleDigitWholeNumbers": [2, 1, 3, 9], - "ipAddresses": [ - "225.234.40.3", - "96.216.243.54", - "18.126.145.83", - "196.17.191.239" - ] + "ipAddresses": ["225.234.40.3", "96.216.243.54", "18.126.145.83", "196.17.191.239"] }, "valid": true }, From 52cfe8be1046dec664de59f711c5dd6dcfef958f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 17 Sep 2020 18:20:00 +0100 Subject: [PATCH 6/8] docs: formats in ajv-formats --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index af77c176d..03b13bbf2 100644 --- a/README.md +++ b/README.md @@ -343,13 +343,16 @@ JSON Schema specification defines several annotation keywords that describe sche ## Formats -From version 7 Ajv does not include formats defined by JSON Schema specification - these and several others formats are provided by [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin. +From version 7 Ajv does not include formats defined by JSON Schema specification - these and several other formats are provided by [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin. To add all formats from this plugin: ```javascript +import Ajv from "ajv" +import addFormats from "ajv-formats" + const ajv = new Ajv() -require("ajv-formats")(ajv) +addFormats(ajv) ``` See ajv-formats documentation for further details. @@ -362,7 +365,7 @@ The following formats are defined in [ajv-formats](https://github.com/ajv-valida - _date_: full-date according to [RFC3339](http://tools.ietf.org/html/rfc3339#section-5.6). - _time_: time with optional time-zone. -- _date-time_: date-time from the same source (time-zone is mandatory). `date`, `time` and `date-time` validate ranges in `full` mode and only regexp in `fast` mode (see [options](#options)). +- _date-time_: date-time from the same source (time-zone is mandatory). - _uri_: full URI. - _uri-reference_: URI reference, including full and relative URIs. - _uri-template_: URI template according to [RFC6570](https://tools.ietf.org/html/rfc6570) @@ -378,12 +381,10 @@ The following formats are defined in [ajv-formats](https://github.com/ajv-valida **Please note**: JSON Schema draft-07 also defines formats `iri`, `iri-reference`, `idn-hostname` and `idn-email` for URLs, hostnames and emails with international characters. These formats are available in [ajv-formats-draft2019](https://github.com/luzlab/ajv-formats-draft2019) plugin. -You can add (and replace) any formats using [addFormat](#api-addformat) method. +You can add and replace any formats using [addFormat](#api-addformat) method. The option `unknownFormats` allows changing the default behaviour when an unknown format is encountered. In this case Ajv can either fail schema compilation (default) or ignore it (default in versions before 5.0.0). You also can allow specific format(s) that will be ignored. See [Options](#options) for details. -You can find regular expressions used for format validation and the sources that were used in [formats.js](https://github.com/ajv-validator/ajv/blob/master/lib/compile/formats.js). - ## Combining schemas with \$ref You can structure your validation logic across multiple schema files and have schemas reference each other using `$ref` keyword. From 5fbf421d271ddee696a4c7bc782352c85f389891 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 17 Sep 2020 18:25:04 +0100 Subject: [PATCH 7/8] remove prettier check from CI --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5748eb114..64655ef1d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test-browser": "rm -rf .browser && npm run bundle && scripts/prepare-tests && karma start", "test-all": "npm run test-cov && if-node-version 12 npm run test-browser", "test": "npm run build && npm run json-tests && npm run eslint && npm run test-cov", - "test-ci": "AJV_FULL_TEST=true npm test && npm run prettier:check", + "test-ci": "AJV_FULL_TEST=true npm test", "prepublish": "npm run build", "watch": "watch \"npm run build\" ./lib" }, From bb342fd02b90f3199c885790e4a33c09970fff50 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 17 Sep 2020 18:28:50 +0100 Subject: [PATCH 8/8] remove unused keyword name union type --- lib/vocabularies/applicator/index.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/vocabularies/applicator/index.ts b/lib/vocabularies/applicator/index.ts index ca3c16958..e46a17e69 100644 --- a/lib/vocabularies/applicator/index.ts +++ b/lib/vocabularies/applicator/index.ts @@ -14,23 +14,6 @@ import allOf from "./allOf" import ifKeyword, {IfKeywordError} from "./if" import thenElse from "./thenElse" -export type ApplicatorKeyword = - | "additionalItems" - | "items" - | "contains" - | "dependencies" - | "propertyNames" - | "additionalProperties" - | "properties" - | "patternProperties" - | "not" - | "anyOf" - | "oneOf" - | "allOf" - | "if" - | "then" - | "else" - const applicator: Vocabulary = [ // array additionalItems,