-
-
Notifications
You must be signed in to change notification settings - Fork 868
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: validate to typescript (WIP)
- Loading branch information
1 parent
3337b28
commit b2b257b
Showing
20 changed files
with
762 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export default class CodeGen { | ||
#names: {[key: string]: number} = {} | ||
// TODO make private. Possibly stack? | ||
_out = "" | ||
|
||
name(prefix: string): string { | ||
if (!this.#names[prefix]) this.#names[prefix] = 0 | ||
const num = this.#names[prefix]++ | ||
return `${prefix}_${num}` | ||
} | ||
|
||
code(str: string): CodeGen { | ||
// TODO optionally strip whitespace | ||
this._out += str + "\n" | ||
return this | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import {KeywordContext, KeywordErrorDefinition} from "../types" | ||
import {toQuotedString} from "./util" | ||
|
||
export function reportError( | ||
cxt: KeywordContext, | ||
error: KeywordErrorDefinition, | ||
allErrors?: boolean | ||
): void { | ||
const {gen, compositeRule, opts, async} = cxt.it | ||
const errObj = errorObjectCode(cxt, error) | ||
if (allErrors ?? (compositeRule || opts.allErrors)) { | ||
gen.code( | ||
`const err = ${errObj}; | ||
if (vErrors === null) vErrors = [err]; | ||
else vErrors.push(err); | ||
errors++;` | ||
) | ||
} else { | ||
gen.code( | ||
async | ||
? `throw new ValidationError([${errObj}]);` | ||
: `validate.errors = [${errObj}]; | ||
return false;` | ||
) | ||
} | ||
} | ||
|
||
function errorObjectCode(cxt: KeywordContext, error: KeywordErrorDefinition): string { | ||
const { | ||
keyword, | ||
data, | ||
schemaValue, | ||
it: {createErrors, schemaPath, errorPath, errSchemaPath, opts}, | ||
} = cxt | ||
if (createErrors === false) return "{}" | ||
if (!error) throw new Error('keyword definition must have "error" property') | ||
// TODO trim whitespace | ||
let out = `{ | ||
keyword: "${keyword}", | ||
dataPath: (dataPath || "") + ${errorPath}, | ||
schemaPath: ${toQuotedString(errSchemaPath + "/" + keyword)}, | ||
params: ${error.params(cxt)},` | ||
if (opts.messages !== false) out += `message: ${error.message(cxt)},` | ||
if (opts.verbose) { | ||
// TODO trim whitespace | ||
out += ` | ||
schema: ${schemaValue}, | ||
parentSchema: validate.schema${schemaPath}, | ||
data: ${data},` | ||
} | ||
return out + "}" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {CompilationContext} from "../../types" | ||
|
||
export function schemaHasRulesForType({RULES, schema}: CompilationContext, ty: string) { | ||
const group = RULES.types[ty] | ||
return group && group !== true && shouldUseGroup(schema, group) | ||
} | ||
|
||
function shouldUseGroup(schema, rulesGroup): boolean { | ||
return rulesGroup.some((rule) => shouldUseRule(schema, rule)) | ||
} | ||
|
||
function shouldUseRule(schema, rule): boolean { | ||
return schema[rule.keyword] !== undefined || ruleImplementsSomeKeyword(schema, rule) | ||
} | ||
|
||
function ruleImplementsSomeKeyword(schema, rule): boolean { | ||
return rule.implements && rule.implements.some((kwd) => schema[kwd] !== undefined) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import {KeywordErrorDefinition, CompilationContext, KeywordContext} from "../../types" | ||
import {reportError} from "../errors" | ||
|
||
const boolError: KeywordErrorDefinition = { | ||
message: () => '"boolean schema is false"', | ||
params: () => "{}", | ||
} | ||
|
||
export function booleanOrEmptySchema(it: CompilationContext): void { | ||
const {gen, isTop, schema, level} = it | ||
if (isTop) { | ||
if (schema === false) { | ||
falseSchemaError(it, false) | ||
} else if (schema.$async === true) { | ||
gen.code("return data;") | ||
} else { | ||
gen.code("validate.errors = null; return true;") | ||
} | ||
gen.code( | ||
`}; | ||
return validate;` | ||
) | ||
} else { | ||
if (schema === false) { | ||
gen.code(`var valid${level} = false;`) // TODO level, var | ||
falseSchemaError(it) | ||
} else { | ||
gen.code(`var valid${level} = true;`) // TODO level, var | ||
} | ||
} | ||
|
||
// if (schema === false) { | ||
// if (!isTop) { | ||
// gen.code(`var valid${level} = false;`) // TODO level, var | ||
// } | ||
// // TODO probably some other interface should be used for non-keyword validation errors... | ||
// falseSchemaError(it, !isTop) | ||
// } else { | ||
// if (isTop) { | ||
// gen.code(schema.$async === true ? `return data;` : `validate.errors = null; return true;`) | ||
// } else { | ||
// gen.code(`var valid${level} = true;`) // TODO level, var | ||
// } | ||
// } | ||
// if (isTop) { | ||
// gen.code( | ||
// `}; | ||
// return validate;` | ||
// ) | ||
// } | ||
} | ||
|
||
function falseSchemaError(it: CompilationContext, allErrors?: boolean) { | ||
const {gen, dataLevel} = it | ||
// TODO maybe some other interface should be used for non-keyword validation errors... | ||
const cxt: KeywordContext = { | ||
gen, | ||
fail: exception, | ||
ok: exception, | ||
errorParams: exception, | ||
keyword: "false schema", | ||
data: "data" + (dataLevel || ""), | ||
$data: false, | ||
schema: false, | ||
schemaCode: false, | ||
schemaValue: false, | ||
parentSchema: false, | ||
it, | ||
} | ||
reportError(cxt, boolError, allErrors) | ||
} | ||
|
||
function exception() { | ||
throw new Error("this function can only be used in keyword") | ||
} | ||
|
||
// {{ var $keyword = 'false schema'; }} | ||
// {{# def.setupKeyword }} | ||
// {{? it.schema === false}} | ||
// {{? it.isTop}} | ||
// {{ $breakOnError = true; }} | ||
// {{??}} | ||
// var {{=$valid}} = false; | ||
// {{?}} | ||
// {{# def.error:'false schema' }} | ||
// {{??}} | ||
// {{? it.isTop}} | ||
// {{? $async }} | ||
// return data; | ||
// {{??}} | ||
// validate.errors = null; | ||
// return true; | ||
// {{?}} | ||
// {{??}} | ||
// var {{=$valid}} = true; | ||
// {{?}} | ||
// {{?}} | ||
|
||
// {{? it.isTop}} | ||
// }; | ||
// return validate; | ||
// {{?}} | ||
|
||
// {{ return out; }} | ||
// {{?}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import {CompilationContext} from "../../types" | ||
import {toHash, checkDataTypes} from "../util" | ||
import {schemaHasRulesForType} from "./applicability" | ||
|
||
export function getSchemaTypes({schema, opts}: CompilationContext): string[] { | ||
const t = schema.type | ||
const types: string[] = Array.isArray(t) ? t : t || [] | ||
types.forEach(checkType) | ||
if (opts.nullable) { | ||
const hasNull = types.includes("null") | ||
if (hasNull && schema.nullable === false) { | ||
throw new Error('{"type": "null"} contradicts {"nullable": "false"}') | ||
} else if (!hasNull && schema.nullable === true) { | ||
types.push("null") | ||
} | ||
} | ||
return types | ||
|
||
function checkType(t: string): void { | ||
// TODO check that type is allowed | ||
if (typeof t != "string") throw new Error('"type" keyword must be string or string[]') | ||
} | ||
} | ||
|
||
export function coerceAndCheckDataType(it: CompilationContext, types: string[]): void { | ||
const { | ||
gen, | ||
dataLevel, | ||
opts: {coerceTypes, strictNumbers}, | ||
} = it | ||
let coerceTo = coerceToTypes(types, coerceTypes) | ||
if (coerceTo.length || types.length > 1 || !schemaHasRulesForType(it, types[0])) { | ||
const wrongType = checkDataTypes(types, `data${dataLevel || ""}`, strictNumbers, true) | ||
gen.code(`if (${wrongType}) {`) | ||
if (coerceTo.length) coerceType(it) | ||
else reportTypeError(it) | ||
gen.code("}") | ||
} | ||
} | ||
|
||
function coerceType(_: CompilationContext) {} | ||
|
||
function reportTypeError(_: CompilationContext) {} | ||
|
||
const COERCIBLE = toHash(["string", "number", "integer", "boolean", "null"]) | ||
function coerceToTypes(types: string[], coerceTypes?: boolean | "array"): string[] { | ||
return coerceTypes | ||
? types.filter((t) => COERCIBLE[t] || (coerceTypes === "array" && t === "array")) | ||
: [] | ||
} |
Oops, something went wrong.