| "use strict"; |
| 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.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(",")}"`); |
| } |
| }); |
| it.dataTypes = it.dataTypes.filter((t) => includesType(types, t)); |
| } |
| 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 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 |