Skip to content

Commit

Permalink
refactor: KeywordContext
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Aug 27, 2020
1 parent 9d1f18a commit e710856
Show file tree
Hide file tree
Showing 33 changed files with 292 additions and 259 deletions.
115 changes: 115 additions & 0 deletions lib/compile/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
KeywordDefinition,
KeywordErrorContext,
KeywordContextParams,
CompilationContext,
} from "../types"
import {schemaRefOrVal} from "../vocabularies/util"
import {getData} from "./util"
import {reportError, keywordError} from "./errors"
import CodeGen, {_, Name, Expression} from "./codegen"

export default class KeywordContext implements KeywordErrorContext {
gen: CodeGen
allErrors: boolean
keyword: string
data: Name
$data?: string | false
schema: any
schemaCode: Expression | number | boolean
schemaValue: Expression | number | boolean
parentSchema: any
params: KeywordContextParams
it: CompilationContext
def: KeywordDefinition

constructor(it: CompilationContext, keyword: string, def: KeywordDefinition) {
const schema = it.schema[keyword]
const {schemaType, $data: $defData} = def
validateKeywordSchema(it, keyword, def)
// TODO
// if (!code) throw new Error('"code" and "error" must be defined')
const $data = $defData && it.opts.$data && schema && schema.$data
const schemaValue = schemaRefOrVal(it, schema, keyword, $data)
this.gen = it.gen
this.allErrors = it.allErrors
this.keyword = keyword
this.data = it.data
this.$data = $data
this.schema = schema
this.schemaCode = $data ? it.gen.name("schema") : schemaValue // reference to resolved schema value
this.schemaValue = schemaValue // actual schema reference or value for primitive values
this.parentSchema = it.schema
this.params = {}
this.it = it
this.def = def

if ($data) {
it.gen.const(<Name>this.schemaCode, `${getData($data, it)}`)
} else if (schemaType && !validSchemaType(schema, schemaType)) {
throw new Error(`${keyword} must be ${JSON.stringify(schemaType)}`)
}
}

fail(cond?: Expression, failAction?: () => void, context?: KeywordErrorContext): void {
if (cond) {
this.gen.if(cond)
failAction ? failAction() : this._reportError(context)
if (this.allErrors) this.gen.endIf()
else this.gen.else()
} else {
failAction ? failAction() : this._reportError(context)
if (!this.allErrors) this.gen.if("false")
}
}

_reportError(context?: KeywordErrorContext) {
reportError(context || this, this.def.error || keywordError)
}

pass(cond: Expression, failAction?: () => void, context?: KeywordErrorContext): void {
cond = cond instanceof Name ? cond : `(${cond})`
this.fail(`!${cond}`, failAction, context)
}

ok(cond: Expression): void {
if (!this.allErrors) this.gen.if(cond)
}

errorParams(obj: KeywordContextParams, assign?: true) {
if (assign) Object.assign(this.params, obj)
else this.params = obj
}
}

function validSchemaType(schema: any, schemaType: string | string[]): boolean {
// TODO add tests
if (Array.isArray(schemaType)) {
return schemaType.some((st) => validSchemaType(schema, st))
}
return schemaType === "array"
? Array.isArray(schema)
: schemaType === "object"
? schema && typeof schema == "object" && !Array.isArray(schema)
: typeof schema == schemaType
}

function validateKeywordSchema(
it: CompilationContext,
keyword: string,
def: KeywordDefinition
): void {
const deps = def.dependencies
if (deps?.some((kwd) => !Object.prototype.hasOwnProperty.call(it.schema, kwd))) {
throw new Error(`parent schema must have dependencies of ${keyword}: ${deps.join(",")}`)
}

if (def.validateSchema) {
const valid = def.validateSchema(it.schema[keyword])
if (!valid) {
const msg = "keyword schema is invalid: " + it.self.errorsText(def.validateSchema.errors)
if (it.opts.validateSchema === "log") it.logger.error(msg)
else throw new Error(msg)
}
}
}
15 changes: 10 additions & 5 deletions lib/compile/errors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import {KeywordContext, KeywordErrorDefinition} from "../types"
import {KeywordErrorContext, KeywordErrorDefinition} from "../types"
import {quotedString} from "../vocabularies/util"
import CodeGen, {_, Name, Expression} from "./codegen"
import N from "./names"

export const keywordError: KeywordErrorDefinition = {
message: ({keyword}) => `'should pass "${keyword}" keyword validation'`,
params: ({keyword}) => `{keyword: "${keyword}"}`, // TODO possibly remove it as keyword is reported in the object
}

export function reportError(
cxt: KeywordContext,
cxt: KeywordErrorContext,
error: KeywordErrorDefinition,
overrideAllErrors?: boolean
): void {
Expand All @@ -17,7 +22,7 @@ export function reportError(
}
}

export function reportExtraError(cxt: KeywordContext, error: KeywordErrorDefinition): void {
export function reportExtraError(cxt: KeywordErrorContext, error: KeywordErrorDefinition): void {
const {gen, compositeRule, allErrors, async} = cxt.it
const errObj = errorObjectCode(cxt, error)
addError(gen, errObj)
Expand All @@ -34,7 +39,7 @@ export function resetErrorsCount(gen: CodeGen, errsCount: Name): void {
}

export function extendErrors(
{gen, keyword, schemaValue, data, it}: KeywordContext,
{gen, keyword, schemaValue, data, it}: KeywordErrorContext,
errsCount: Name
): void {
const err = gen.name("err")
Expand Down Expand Up @@ -69,7 +74,7 @@ function returnErrors(gen: CodeGen, async: boolean, errs: Expression): void {
}
}

function errorObjectCode(cxt: KeywordContext, error: KeywordErrorDefinition): string {
function errorObjectCode(cxt: KeywordErrorContext, error: KeywordErrorDefinition): string {
const {
keyword,
data,
Expand Down
2 changes: 0 additions & 2 deletions lib/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import CodeGen, {_, nil, Expression} from "./codegen"
import {toQuotedString} from "./util"
import {quotedString} from "../vocabularies/util"
import {validateFunctionCode} from "./validate"
import {validateKeywordSchema} from "./validate/keyword"
import {ErrorObject, KeywordCompilationResult} from "../types"
import N from "./names"

Expand Down Expand Up @@ -133,7 +132,6 @@ function compile(schema, root, localRefs, baseId) {
resolveRef, // TODO remove to imports
usePattern, // TODO remove to imports
useDefault, // TODO remove to imports
validateKeywordSchema, // TODO remove
customRules, // TODO add to types
opts,
formats,
Expand Down
13 changes: 2 additions & 11 deletions lib/compile/validate/boolSchema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {KeywordErrorDefinition, CompilationContext, KeywordContext} from "../../types"
import {KeywordErrorDefinition, CompilationContext, KeywordErrorContext} from "../../types"
import {reportError} from "../errors"
import {_, Name} from "../codegen"
import N from "../names"
Expand Down Expand Up @@ -32,12 +32,8 @@ export function boolOrEmptySchema(it: CompilationContext, valid: Name): void {
function falseSchemaError(it: CompilationContext, overrideAllErrors?: boolean) {
const {gen, data} = it
// TODO maybe some other interface should be used for non-keyword validation errors...
const cxt: KeywordContext = {
const cxt: KeywordErrorContext = {
gen,
ok: exception,
pass: exception,
fail: exception,
errorParams: exception,
keyword: "false schema",
data,
schema: false,
Expand All @@ -49,8 +45,3 @@ function falseSchemaError(it: CompilationContext, overrideAllErrors?: boolean) {
}
reportError(cxt, boolError, overrideAllErrors)
}

// TODO combine with exception from dataType
function exception() {
throw new Error("this function can only be used in keyword")
}
22 changes: 19 additions & 3 deletions lib/compile/validate/dataType.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {CompilationContext, KeywordErrorDefinition} from "../../types"
import {CompilationContext, KeywordErrorDefinition, KeywordErrorContext} from "../../types"
import {toHash, checkDataType, checkDataTypes} from "../util"
import {schemaRefOrVal} from "../../vocabularies/util"
import {schemaHasRulesForType} from "./applicability"
import {reportError} from "../errors"
import {getKeywordContext} from "./keyword"
import {_, str, Name} from "../codegen"

export function getSchemaTypes({schema, opts}: CompilationContext): string[] {
Expand Down Expand Up @@ -140,6 +140,22 @@ const typeError: KeywordErrorDefinition = {
}

export function reportTypeError(it: CompilationContext): void {
const cxt = getKeywordContext(it, "type")
const cxt = getErrorContext(it, "type")
reportError(cxt, typeError)
}

function getErrorContext(it: CompilationContext, keyword: string): KeywordErrorContext {
const {gen, data, schema} = it
const schemaCode = schemaRefOrVal(it, schema, keyword)
return {
gen,
keyword,
data,
schema: schema[keyword],
schemaCode,
schemaValue: schemaCode,
parentSchema: schema,
params: {},
it,
}
}
Loading

0 comments on commit e710856

Please sign in to comment.