Skip to content

Commit

Permalink
feat: DefinedError as discriminated union with different parameter ty…
Browse files Browse the repository at this point in the history
…pes, closes #843, closes #1090
  • Loading branch information
epoberezkin committed Sep 16, 2020
1 parent 61e7520 commit defca2c
Show file tree
Hide file tree
Showing 20 changed files with 181 additions and 5 deletions.
4 changes: 4 additions & 0 deletions lib/compile/validate/dataType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}) =>
Expand Down
8 changes: 5 additions & 3 deletions lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -141,11 +143,11 @@ export interface AsyncValidateFunction<T = any> extends ValidateFunction<T> {

export type AnyValidateFunction<T = any> = ValidateFunction<T> | AsyncValidateFunction<T>

export interface ErrorObject<T = string> {
keyword: T
export interface ErrorObject<K = string, P = Record<string, any>> {
keyword: K
dataPath: string
schemaPath: string
params: Record<string, unknown> // TODO add interface
params: P
// Added to validation errors of "propertyNames" keyword schema
propertyName?: string
// Excluded if option `messages` set to false.
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/applicator/additionalItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/applicator/additionalProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
9 changes: 8 additions & 1 deletion lib/vocabularies/applicator/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 = {
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/applicator/if.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/applicator/oneOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/applicator/propertyNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/core/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
69 changes: 69 additions & 0 deletions lib/vocabularies/errors.ts
Original file line number Diff line number Diff line change
@@ -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<string, never>
>
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<LimitKeyword, LimitErrorParams>
type LimitNumberError = ErrorObject<LimitNumberKeyword, LimitNumberErrorParams>
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
4 changes: 4 additions & 0 deletions lib/vocabularies/format/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/validation/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/validation/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
9 changes: 9 additions & 0 deletions lib/vocabularies/validation/limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/validation/multipleOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/validation/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
4 changes: 4 additions & 0 deletions lib/vocabularies/validation/required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand Down
5 changes: 5 additions & 0 deletions lib/vocabularies/validation/uniqueItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)`,
Expand Down
3 changes: 2 additions & 1 deletion spec/types/async-validate.spec.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
31 changes: 31 additions & 0 deletions spec/types/error-parameters.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
})
})

0 comments on commit defca2c

Please sign in to comment.