Spaces:
Running
Running
; | |
Object.defineProperty(exports, "__esModule", { value: true }); | |
exports.getData = exports.KeywordCxt = exports.validateFunctionCode = void 0; | |
const boolSchema_1 = require("./boolSchema"); | |
const dataType_1 = require("./dataType"); | |
const applicability_1 = require("./applicability"); | |
const dataType_2 = require("./dataType"); | |
const defaults_1 = require("./defaults"); | |
const keyword_1 = require("./keyword"); | |
const subschema_1 = require("./subschema"); | |
const codegen_1 = require("../codegen"); | |
const names_1 = require("../names"); | |
const resolve_1 = require("../resolve"); | |
const util_1 = require("../util"); | |
const errors_1 = require("../errors"); | |
// schema compilation - generates validation function, subschemaCode (below) is used for subschemas | |
function validateFunctionCode(it) { | |
if (isSchemaObj(it)) { | |
checkKeywords(it); | |
if (schemaCxtHasRules(it)) { | |
topSchemaObjCode(it); | |
return; | |
} | |
} | |
validateFunction(it, () => (0, boolSchema_1.topBoolOrEmptySchema)(it)); | |
} | |
exports.validateFunctionCode = validateFunctionCode; | |
function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) { | |
if (opts.code.es5) { | |
gen.func(validateName, (0, codegen_1._) `${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => { | |
gen.code((0, codegen_1._) `"use strict"; ${funcSourceUrl(schema, opts)}`); | |
destructureValCxtES5(gen, opts); | |
gen.code(body); | |
}); | |
} | |
else { | |
gen.func(validateName, (0, codegen_1._) `${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body)); | |
} | |
} | |
function destructureValCxt(opts) { | |
return (0, codegen_1._) `{${names_1.default.instancePath}="", ${names_1.default.parentData}, ${names_1.default.parentDataProperty}, ${names_1.default.rootData}=${names_1.default.data}${opts.dynamicRef ? (0, codegen_1._) `, ${names_1.default.dynamicAnchors}={}` : codegen_1.nil}}={}`; | |
} | |
function destructureValCxtES5(gen, opts) { | |
gen.if(names_1.default.valCxt, () => { | |
gen.var(names_1.default.instancePath, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.instancePath}`); | |
gen.var(names_1.default.parentData, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.parentData}`); | |
gen.var(names_1.default.parentDataProperty, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.parentDataProperty}`); | |
gen.var(names_1.default.rootData, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.rootData}`); | |
if (opts.dynamicRef) | |
gen.var(names_1.default.dynamicAnchors, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.dynamicAnchors}`); | |
}, () => { | |
gen.var(names_1.default.instancePath, (0, codegen_1._) `""`); | |
gen.var(names_1.default.parentData, (0, codegen_1._) `undefined`); | |
gen.var(names_1.default.parentDataProperty, (0, codegen_1._) `undefined`); | |
gen.var(names_1.default.rootData, names_1.default.data); | |
if (opts.dynamicRef) | |
gen.var(names_1.default.dynamicAnchors, (0, codegen_1._) `{}`); | |
}); | |
} | |
function topSchemaObjCode(it) { | |
const { schema, opts, gen } = it; | |
validateFunction(it, () => { | |
if (opts.$comment && schema.$comment) | |
commentKeyword(it); | |
checkNoDefault(it); | |
gen.let(names_1.default.vErrors, null); | |
gen.let(names_1.default.errors, 0); | |
if (opts.unevaluated) | |
resetEvaluated(it); | |
typeAndKeywords(it); | |
returnResults(it); | |
}); | |
return; | |
} | |
function resetEvaluated(it) { | |
// TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated | |
const { gen, validateName } = it; | |
it.evaluated = gen.const("evaluated", (0, codegen_1._) `${validateName}.evaluated`); | |
gen.if((0, codegen_1._) `${it.evaluated}.dynamicProps`, () => gen.assign((0, codegen_1._) `${it.evaluated}.props`, (0, codegen_1._) `undefined`)); | |
gen.if((0, codegen_1._) `${it.evaluated}.dynamicItems`, () => gen.assign((0, codegen_1._) `${it.evaluated}.items`, (0, codegen_1._) `undefined`)); | |
} | |
function funcSourceUrl(schema, opts) { | |
const schId = typeof schema == "object" && schema[opts.schemaId]; | |
return schId && (opts.code.source || opts.code.process) ? (0, codegen_1._) `/*# sourceURL=${schId} */` : codegen_1.nil; | |
} | |
// schema compilation - this function is used recursively to generate code for sub-schemas | |
function subschemaCode(it, valid) { | |
if (isSchemaObj(it)) { | |
checkKeywords(it); | |
if (schemaCxtHasRules(it)) { | |
subSchemaObjCode(it, valid); | |
return; | |
} | |
} | |
(0, boolSchema_1.boolOrEmptySchema)(it, valid); | |
} | |
function schemaCxtHasRules({ schema, self }) { | |
if (typeof schema == "boolean") | |
return !schema; | |
for (const key in schema) | |
if (self.RULES.all[key]) | |
return true; | |
return false; | |
} | |
function isSchemaObj(it) { | |
return typeof it.schema != "boolean"; | |
} | |
function subSchemaObjCode(it, valid) { | |
const { schema, gen, opts } = it; | |
if (opts.$comment && schema.$comment) | |
commentKeyword(it); | |
updateContext(it); | |
checkAsyncSchema(it); | |
const errsCount = gen.const("_errs", names_1.default.errors); | |
typeAndKeywords(it, errsCount); | |
// TODO var | |
gen.var(valid, (0, codegen_1._) `${errsCount} === ${names_1.default.errors}`); | |
} | |
function checkKeywords(it) { | |
(0, util_1.checkUnknownRules)(it); | |
checkRefsAndKeywords(it); | |
} | |
function typeAndKeywords(it, errsCount) { | |
if (it.opts.jtd) | |
return schemaKeywords(it, [], false, errsCount); | |
const types = (0, dataType_1.getSchemaTypes)(it.schema); | |
const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it, types); | |
schemaKeywords(it, types, !checkedTypes, errsCount); | |
} | |
function checkRefsAndKeywords(it) { | |
const { schema, errSchemaPath, opts, self } = it; | |
if (schema.$ref && opts.ignoreKeywordsWithRef && (0, util_1.schemaHasRulesButRef)(schema, self.RULES)) { | |
self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`); | |
} | |
} | |
function checkNoDefault(it) { | |
const { schema, opts } = it; | |
if (schema.default !== undefined && opts.useDefaults && opts.strictSchema) { | |
(0, util_1.checkStrictMode)(it, "default is ignored in the schema root"); | |
} | |
} | |
function updateContext(it) { | |
const schId = it.schema[it.opts.schemaId]; | |
if (schId) | |
it.baseId = (0, resolve_1.resolveUrl)(it.opts.uriResolver, it.baseId, schId); | |
} | |
function checkAsyncSchema(it) { | |
if (it.schema.$async && !it.schemaEnv.$async) | |
throw new Error("async schema in sync schema"); | |
} | |
function commentKeyword({ gen, schemaEnv, schema, errSchemaPath, opts }) { | |
const msg = schema.$comment; | |
if (opts.$comment === true) { | |
gen.code((0, codegen_1._) `${names_1.default.self}.logger.log(${msg})`); | |
} | |
else if (typeof opts.$comment == "function") { | |
const schemaPath = (0, codegen_1.str) `${errSchemaPath}/$comment`; | |
const rootName = gen.scopeValue("root", { ref: schemaEnv.root }); | |
gen.code((0, codegen_1._) `${names_1.default.self}.opts.$comment(${msg}, ${schemaPath}, ${rootName}.schema)`); | |
} | |
} | |
function returnResults(it) { | |
const { gen, schemaEnv, validateName, ValidationError, opts } = it; | |
if (schemaEnv.$async) { | |
// TODO assign unevaluated | |
gen.if((0, codegen_1._) `${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._) `new ${ValidationError}(${names_1.default.vErrors})`)); | |
} | |
else { | |
gen.assign((0, codegen_1._) `${validateName}.errors`, names_1.default.vErrors); | |
if (opts.unevaluated) | |
assignEvaluated(it); | |
gen.return((0, codegen_1._) `${names_1.default.errors} === 0`); | |
} | |
} | |
function assignEvaluated({ gen, evaluated, props, items }) { | |
if (props instanceof codegen_1.Name) | |
gen.assign((0, codegen_1._) `${evaluated}.props`, props); | |
if (items instanceof codegen_1.Name) | |
gen.assign((0, codegen_1._) `${evaluated}.items`, items); | |
} | |
function schemaKeywords(it, types, typeErrors, errsCount) { | |
const { gen, schema, data, allErrors, opts, self } = it; | |
const { RULES } = self; | |
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema, RULES))) { | |
gen.block(() => keywordCode(it, "$ref", RULES.all.$ref.definition)); // TODO typecast | |
return; | |
} | |
if (!opts.jtd) | |
checkStrictTypes(it, types); | |
gen.block(() => { | |
for (const group of RULES.rules) | |
groupKeywords(group); | |
groupKeywords(RULES.post); | |
}); | |
function groupKeywords(group) { | |
if (!(0, applicability_1.shouldUseGroup)(schema, group)) | |
return; | |
if (group.type) { | |
gen.if((0, dataType_2.checkDataType)(group.type, data, opts.strictNumbers)); | |
iterateKeywords(it, group); | |
if (types.length === 1 && types[0] === group.type && typeErrors) { | |
gen.else(); | |
(0, dataType_2.reportTypeError)(it); | |
} | |
gen.endIf(); | |
} | |
else { | |
iterateKeywords(it, group); | |
} | |
// TODO make it "ok" call? | |
if (!allErrors) | |
gen.if((0, codegen_1._) `${names_1.default.errors} === ${errsCount || 0}`); | |
} | |
} | |
function iterateKeywords(it, group) { | |
const { gen, schema, opts: { useDefaults }, } = it; | |
if (useDefaults) | |
(0, defaults_1.assignDefaults)(it, group.type); | |
gen.block(() => { | |
for (const rule of group.rules) { | |
if ((0, applicability_1.shouldUseRule)(schema, rule)) { | |
keywordCode(it, rule.keyword, rule.definition, group.type); | |
} | |
} | |
}); | |
} | |
function checkStrictTypes(it, types) { | |
if (it.schemaEnv.meta || !it.opts.strictTypes) | |
return; | |
checkContextTypes(it, types); | |
if (!it.opts.allowUnionTypes) | |
checkMultipleTypes(it, types); | |
checkKeywordTypes(it, it.dataTypes); | |
} | |
function checkContextTypes(it, types) { | |
if (!types.length) | |
return; | |
if (!it.dataTypes.length) { | |
it.dataTypes = types; | |
return; | |
} | |
types.forEach((t) => { | |
if (!includesType(it.dataTypes, t)) { | |
strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`); | |
} | |
}); | |
narrowSchemaTypes(it, types); | |
} | |
function checkMultipleTypes(it, ts) { | |
if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) { | |
strictTypesError(it, "use allowUnionTypes to allow union type keyword"); | |
} | |
} | |
function checkKeywordTypes(it, ts) { | |
const rules = it.self.RULES.all; | |
for (const keyword in rules) { | |
const rule = rules[keyword]; | |
if (typeof rule == "object" && (0, applicability_1.shouldUseRule)(it.schema, rule)) { | |
const { type } = rule.definition; | |
if (type.length && !type.some((t) => hasApplicableType(ts, t))) { | |
strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`); | |
} | |
} | |
} | |
} | |
function hasApplicableType(schTs, kwdT) { | |
return schTs.includes(kwdT) || (kwdT === "number" && schTs.includes("integer")); | |
} | |
function includesType(ts, t) { | |
return ts.includes(t) || (t === "integer" && ts.includes("number")); | |
} | |
function narrowSchemaTypes(it, withTypes) { | |
const ts = []; | |
for (const t of it.dataTypes) { | |
if (includesType(withTypes, t)) | |
ts.push(t); | |
else if (withTypes.includes("integer") && t === "number") | |
ts.push("integer"); | |
} | |
it.dataTypes = ts; | |
} | |
function strictTypesError(it, msg) { | |
const schemaPath = it.schemaEnv.baseId + it.errSchemaPath; | |
msg += ` at "${schemaPath}" (strictTypes)`; | |
(0, util_1.checkStrictMode)(it, msg, it.opts.strictTypes); | |
} | |
class KeywordCxt { | |
constructor(it, def, keyword) { | |
(0, keyword_1.validateKeywordUsage)(it, def, keyword); | |
this.gen = it.gen; | |
this.allErrors = it.allErrors; | |
this.keyword = keyword; | |
this.data = it.data; | |
this.schema = it.schema[keyword]; | |
this.$data = def.$data && it.opts.$data && this.schema && this.schema.$data; | |
this.schemaValue = (0, util_1.schemaRefOrVal)(it, this.schema, keyword, this.$data); | |
this.schemaType = def.schemaType; | |
this.parentSchema = it.schema; | |
this.params = {}; | |
this.it = it; | |
this.def = def; | |
if (this.$data) { | |
this.schemaCode = it.gen.const("vSchema", getData(this.$data, it)); | |
} | |
else { | |
this.schemaCode = this.schemaValue; | |
if (!(0, keyword_1.validSchemaType)(this.schema, def.schemaType, def.allowUndefined)) { | |
throw new Error(`${keyword} value must be ${JSON.stringify(def.schemaType)}`); | |
} | |
} | |
if ("code" in def ? def.trackErrors : def.errors !== false) { | |
this.errsCount = it.gen.const("_errs", names_1.default.errors); | |
} | |
} | |
result(condition, successAction, failAction) { | |
this.failResult((0, codegen_1.not)(condition), successAction, failAction); | |
} | |
failResult(condition, successAction, failAction) { | |
this.gen.if(condition); | |
if (failAction) | |
failAction(); | |
else | |
this.error(); | |
if (successAction) { | |
this.gen.else(); | |
successAction(); | |
if (this.allErrors) | |
this.gen.endIf(); | |
} | |
else { | |
if (this.allErrors) | |
this.gen.endIf(); | |
else | |
this.gen.else(); | |
} | |
} | |
pass(condition, failAction) { | |
this.failResult((0, codegen_1.not)(condition), undefined, failAction); | |
} | |
fail(condition) { | |
if (condition === undefined) { | |
this.error(); | |
if (!this.allErrors) | |
this.gen.if(false); // this branch will be removed by gen.optimize | |
return; | |
} | |
this.gen.if(condition); | |
this.error(); | |
if (this.allErrors) | |
this.gen.endIf(); | |
else | |
this.gen.else(); | |
} | |
fail$data(condition) { | |
if (!this.$data) | |
return this.fail(condition); | |
const { schemaCode } = this; | |
this.fail((0, codegen_1._) `${schemaCode} !== undefined && (${(0, codegen_1.or)(this.invalid$data(), condition)})`); | |
} | |
error(append, errorParams, errorPaths) { | |
if (errorParams) { | |
this.setParams(errorParams); | |
this._error(append, errorPaths); | |
this.setParams({}); | |
return; | |
} | |
this._error(append, errorPaths); | |
} | |
_error(append, errorPaths) { | |
; | |
(append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths); | |
} | |
$dataError() { | |
(0, errors_1.reportError)(this, this.def.$dataError || errors_1.keyword$DataError); | |
} | |
reset() { | |
if (this.errsCount === undefined) | |
throw new Error('add "trackErrors" to keyword definition'); | |
(0, errors_1.resetErrorsCount)(this.gen, this.errsCount); | |
} | |
ok(cond) { | |
if (!this.allErrors) | |
this.gen.if(cond); | |
} | |
setParams(obj, assign) { | |
if (assign) | |
Object.assign(this.params, obj); | |
else | |
this.params = obj; | |
} | |
block$data(valid, codeBlock, $dataValid = codegen_1.nil) { | |
this.gen.block(() => { | |
this.check$data(valid, $dataValid); | |
codeBlock(); | |
}); | |
} | |
check$data(valid = codegen_1.nil, $dataValid = codegen_1.nil) { | |
if (!this.$data) | |
return; | |
const { gen, schemaCode, schemaType, def } = this; | |
gen.if((0, codegen_1.or)((0, codegen_1._) `${schemaCode} === undefined`, $dataValid)); | |
if (valid !== codegen_1.nil) | |
gen.assign(valid, true); | |
if (schemaType.length || def.validateSchema) { | |
gen.elseIf(this.invalid$data()); | |
this.$dataError(); | |
if (valid !== codegen_1.nil) | |
gen.assign(valid, false); | |
} | |
gen.else(); | |
} | |
invalid$data() { | |
const { gen, schemaCode, schemaType, def, it } = this; | |
return (0, codegen_1.or)(wrong$DataType(), invalid$DataSchema()); | |
function wrong$DataType() { | |
if (schemaType.length) { | |
/* istanbul ignore if */ | |
if (!(schemaCode instanceof codegen_1.Name)) | |
throw new Error("ajv implementation error"); | |
const st = Array.isArray(schemaType) ? schemaType : [schemaType]; | |
return (0, codegen_1._) `${(0, dataType_2.checkDataTypes)(st, schemaCode, it.opts.strictNumbers, dataType_2.DataType.Wrong)}`; | |
} | |
return codegen_1.nil; | |
} | |
function invalid$DataSchema() { | |
if (def.validateSchema) { | |
const validateSchemaRef = gen.scopeValue("validate$data", { ref: def.validateSchema }); // TODO value.code for standalone | |
return (0, codegen_1._) `!${validateSchemaRef}(${schemaCode})`; | |
} | |
return codegen_1.nil; | |
} | |
} | |
subschema(appl, valid) { | |
const subschema = (0, subschema_1.getSubschema)(this.it, appl); | |
(0, subschema_1.extendSubschemaData)(subschema, this.it, appl); | |
(0, subschema_1.extendSubschemaMode)(subschema, appl); | |
const nextContext = { ...this.it, ...subschema, items: undefined, props: undefined }; | |
subschemaCode(nextContext, valid); | |
return nextContext; | |
} | |
mergeEvaluated(schemaCxt, toName) { | |
const { it, gen } = this; | |
if (!it.opts.unevaluated) | |
return; | |
if (it.props !== true && schemaCxt.props !== undefined) { | |
it.props = util_1.mergeEvaluated.props(gen, schemaCxt.props, it.props, toName); | |
} | |
if (it.items !== true && schemaCxt.items !== undefined) { | |
it.items = util_1.mergeEvaluated.items(gen, schemaCxt.items, it.items, toName); | |
} | |
} | |
mergeValidEvaluated(schemaCxt, valid) { | |
const { it, gen } = this; | |
if (it.opts.unevaluated && (it.props !== true || it.items !== true)) { | |
gen.if(valid, () => this.mergeEvaluated(schemaCxt, codegen_1.Name)); | |
return true; | |
} | |
} | |
} | |
exports.KeywordCxt = KeywordCxt; | |
function keywordCode(it, keyword, def, ruleType) { | |
const cxt = new KeywordCxt(it, def, keyword); | |
if ("code" in def) { | |
def.code(cxt, ruleType); | |
} | |
else if (cxt.$data && def.validate) { | |
(0, keyword_1.funcKeywordCode)(cxt, def); | |
} | |
else if ("macro" in def) { | |
(0, keyword_1.macroKeywordCode)(cxt, def); | |
} | |
else if (def.compile || def.validate) { | |
(0, keyword_1.funcKeywordCode)(cxt, def); | |
} | |
} | |
const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; | |
const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; | |
function getData($data, { dataLevel, dataNames, dataPathArr }) { | |
let jsonPointer; | |
let data; | |
if ($data === "") | |
return names_1.default.rootData; | |
if ($data[0] === "/") { | |
if (!JSON_POINTER.test($data)) | |
throw new Error(`Invalid JSON-pointer: ${$data}`); | |
jsonPointer = $data; | |
data = names_1.default.rootData; | |
} | |
else { | |
const matches = RELATIVE_JSON_POINTER.exec($data); | |
if (!matches) | |
throw new Error(`Invalid JSON-pointer: ${$data}`); | |
const up = +matches[1]; | |
jsonPointer = matches[2]; | |
if (jsonPointer === "#") { | |
if (up >= dataLevel) | |
throw new Error(errorMsg("property/index", up)); | |
return dataPathArr[dataLevel - up]; | |
} | |
if (up > dataLevel) | |
throw new Error(errorMsg("data", up)); | |
data = dataNames[dataLevel - up]; | |
if (!jsonPointer) | |
return data; | |
} | |
let expr = data; | |
const segments = jsonPointer.split("/"); | |
for (const segment of segments) { | |
if (segment) { | |
data = (0, codegen_1._) `${data}${(0, codegen_1.getProperty)((0, util_1.unescapeJsonPointer)(segment))}`; | |
expr = (0, codegen_1._) `${expr} && ${data}`; | |
} | |
} | |
return expr; | |
function errorMsg(pointerType, up) { | |
return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`; | |
} | |
} | |
exports.getData = getData; | |
//# sourceMappingURL=index.js.map |