Skip to content

Commit

Permalink
refactor: keywordCode
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Aug 27, 2020
1 parent 47d172c commit 9d1f18a
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 243 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ overrides:
"@typescript-eslint/no-empty-function": off
"@typescript-eslint/no-this-alias": off
"@typescript-eslint/no-implied-eval": off
no-invalid-this: off
rules:
block-scoped-var: error
callback-return: error
Expand Down
12 changes: 5 additions & 7 deletions lib/compile/rules.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {toHash} from "./util"
import {CompilationContext, KeywordDefinition} from "../types"
import {KeywordDefinition} from "../types"

export interface ValidationRules {
rules: RuleGroup[]
all: {[key: string]: boolean | Rule}
keywords: {[key: string]: boolean}
all: {[key: string]: boolean | Rule} // rules that have to be validated
keywords: {[key: string]: boolean} // all known keywords (superset of "all")
types: {[key: string]: boolean | RuleGroup}
custom: {[key: string]: Rule}
}
Expand All @@ -14,12 +14,10 @@ export interface RuleGroup {
rules: Rule[]
}

// This interface wraps KeywordDefinition because definition can have multiple keywords
export interface Rule {
keyword: string
code: (it: CompilationContext, keyword: string, ruleType?: string) => void
implements?: string[]
definition?: KeywordDefinition
custom?: true
definition: KeywordDefinition
}

export default function rules(): ValidationRules {
Expand Down
4 changes: 2 additions & 2 deletions lib/compile/subschema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface SubschemaContext {
schema: object | boolean
schemaPath: string
errSchemaPath: string
topSchemaRef?: Code
topSchemaRef?: Expression
errorPath?: string
dataLevel?: number
data?: Name
Expand Down Expand Up @@ -37,7 +37,7 @@ interface SubschemaApplicationParams {
schema: object | boolean
schemaPath: string
errSchemaPath: string
topSchemaRef: Code
topSchemaRef: Expression
data: Name | Code
dataProp: Expression | number
propertyName: Name
Expand Down
2 changes: 1 addition & 1 deletion lib/compile/validate/applicability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export function shouldUseRule(schema: object, rule: Rule): boolean | undefined {
}

function ruleImplementsSomeKeyword(schema: object, rule: Rule): boolean | undefined {
return rule.implements?.some((kwd) => schema[kwd] !== undefined)
return rule.definition.implements?.some((kwd) => schema[kwd] !== undefined)
}
2 changes: 1 addition & 1 deletion lib/compile/validate/dataType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {CompilationContext, KeywordErrorDefinition} from "../../types"
import {toHash, checkDataType, checkDataTypes} from "../util"
import {schemaHasRulesForType} from "./applicability"
import {reportError} from "../errors"
import {getKeywordContext} from "../../keyword"
import {getKeywordContext} from "./keyword"
import {_, str, Name} from "../codegen"

export function getSchemaTypes({schema, opts}: CompilationContext): string[] {
Expand Down
8 changes: 4 additions & 4 deletions lib/compile/validate/iterate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {CompilationContext} from "../../types"
import {shouldUseGroup, shouldUseRule} from "./applicability"
import {checkDataType, schemaHasRulesExcept} from "../util"
import {keywordCode} from "./keyword"
import {assignDefaults} from "./defaults"
import {reportTypeError} from "./dataType"
import {RuleGroup, Rule} from "../rules"
import {Rule, RuleGroup} from "../rules"
import {Name} from "../codegen"
import N from "../names"

Expand All @@ -18,8 +19,7 @@ export function schemaKeywords(
schema.$ref &&
!(opts.extendRefs === true && schemaHasRulesExcept(schema, RULES.all, "$ref"))
) {
// TODO remove Rule type cast
gen.block(() => (RULES.all.$ref as Rule).code(it, "$ref"))
gen.block(() => keywordCode(it, "$ref", (<Rule>RULES.all.$ref).definition)) // TODO typecast
return
}
gen.block(() => {
Expand Down Expand Up @@ -58,7 +58,7 @@ function iterateKeywords(it: CompilationContext, group: RuleGroup) {
gen.block(() => {
for (const rule of group.rules) {
if (shouldUseRule(schema, rule)) {
rule.code(it, rule.keyword, group.type)
keywordCode(it, rule.keyword, rule.definition, group.type)
}
}
})
Expand Down
124 changes: 116 additions & 8 deletions lib/compile/validate/keyword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import {
KeywordDefinition,
KeywordErrorDefinition,
KeywordContext,
KeywordContextParams,
MacroKeywordDefinition,
FuncKeywordDefinition,
CompilationContext,
KeywordCompilationResult,
} from "../../types"
import {applySubschema} from "../subschema"
import {reportError, reportExtraError, extendErrors} from "../errors"
import {callValidate} from "../../vocabularies/util"
import {callValidate, schemaRefOrVal} from "../../vocabularies/util"
import {getData} from "../util"
import {_, Name, Expression} from "../codegen"
import N from "../names"

Expand All @@ -18,14 +20,16 @@ export const keywordError: KeywordErrorDefinition = {
params: ({keyword}) => `{keyword: "${keyword}"}`, // TODO possibly remove it as keyword is reported in the object
}

export default function keywordCode(
cxt: KeywordContext,
_ruleType: string,
def: KeywordDefinition
export function keywordCode(
it: CompilationContext,
keyword: string,
def: KeywordDefinition,
ruleType?: string
): void {
// TODO "code" keyword
// TODO refactor
if (cxt.$data && "validate" in def) {
const cxt = _getKeywordContext(it, keyword, def)
if ("code" in def) {
def.code(cxt, ruleType)
} else if (cxt.$data && "validate" in def) {
funcKeywordCode(cxt, def as FuncKeywordDefinition)
} else if ("macro" in def) {
macroKeywordCode(cxt, def)
Expand All @@ -34,6 +38,110 @@ export default function keywordCode(
}
}

function _getKeywordContext(
it: CompilationContext,
keyword: string,
def: KeywordDefinition
): KeywordContext {
const schema = it.schema[keyword]
const {schemaType, $data: $defData} = def
validateKeywordSchema(it, keyword, def)
const {gen, data, opts, allErrors} = it
// TODO
// if (!code) throw new Error('"code" and "error" must be defined')
const $data = $defData && opts.$data && schema && schema.$data
const schemaValue = schemaRefOrVal(it, schema, keyword, $data)
const cxt: KeywordContext = {
gen,
ok,
pass,
fail,
errorParams,
keyword,
data,
$data,
schema,
schemaCode: $data ? gen.name("schema") : schemaValue, // reference to resolved schema value
schemaValue, // actual schema reference or value for primitive values
parentSchema: it.schema,
params: {},
it,
}
if ($data) {
gen.const(<Name>cxt.schemaCode, `${getData($data, it)}`)
} else if (schemaType && !validSchemaType(schema, schemaType)) {
throw new Error(`${keyword} must be ${JSON.stringify(schemaType)}`)
}
return cxt

function fail(cond?: Expression, failAction?: () => void, context?: KeywordContext): void {
const action = failAction || _reportError
if (cond) {
gen.if(cond)
action()
if (allErrors) gen.endIf()
else gen.else()
} else {
action()
if (!allErrors) gen.if("false")
}

function _reportError() {
reportError(context || cxt, def.error || keywordError)
}
}

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

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

function errorParams(obj: KeywordContextParams, assign?: true) {
if (assign) Object.assign(cxt.params, obj)
else cxt.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
}

export function getKeywordContext(it: CompilationContext, keyword: string): KeywordContext {
const {gen, data, schema} = it
const schemaCode = schemaRefOrVal(it, schema, keyword)
return {
gen,
ok: exception,
pass: exception,
fail: exception,
errorParams: exception,
keyword,
data,
schema: schema[keyword],
schemaCode,
schemaValue: schemaCode,
parentSchema: schema,
params: {},
it,
}
}

function exception() {
throw new Error("this function can only be used in keyword")
}

function macroKeywordCode(cxt: KeywordContext, def: MacroKeywordDefinition) {
const {gen, fail, keyword, schema, parentSchema, it} = cxt
const macroSchema = def.macro.call(it.self, schema, parentSchema, it)
Expand Down
Loading

0 comments on commit 9d1f18a

Please sign in to comment.