| "use strict"; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.resolveSchema = exports.getCompilingSchema = exports.resolveRef = exports.compileSchema = exports.SchemaEnv = void 0; |
| const codegen_1 = require("./codegen"); |
| const validation_error_1 = require("../runtime/validation_error"); |
| const names_1 = require("./names"); |
| const resolve_1 = require("./resolve"); |
| const util_1 = require("./util"); |
| const validate_1 = require("./validate"); |
| const URI = require("uri-js"); |
| class SchemaEnv { |
| constructor(env) { |
| var _a; |
| this.refs = {}; |
| this.dynamicAnchors = {}; |
| let schema; |
| if (typeof env.schema == "object") |
| schema = env.schema; |
| this.schema = env.schema; |
| this.schemaId = env.schemaId; |
| this.root = env.root || this; |
| this.baseId = (_a = env.baseId) !== null && _a !== void 0 ? _a : (0, resolve_1.normalizeId)(schema === null || schema === void 0 ? void 0 : schema[env.schemaId || "$id"]); |
| this.schemaPath = env.schemaPath; |
| this.localRefs = env.localRefs; |
| this.meta = env.meta; |
| this.$async = schema === null || schema === void 0 ? void 0 : schema.$async; |
| this.refs = {}; |
| } |
| } |
| exports.SchemaEnv = SchemaEnv; |
| // let codeSize = 0 |
| // let nodeCount = 0 |
| // Compiles schema in SchemaEnv |
| function compileSchema(sch) { |
| // TODO refactor - remove compilations |
| const _sch = getCompilingSchema.call(this, sch); |
| if (_sch) |
| return _sch; |
| const rootId = (0, resolve_1.getFullPath)(sch.root.baseId); // TODO if getFullPath removed 1 tests fails |
| const { es5, lines } = this.opts.code; |
| const { ownProperties } = this.opts; |
| const gen = new codegen_1.CodeGen(this.scope, { es5, lines, ownProperties }); |
| let _ValidationError; |
| if (sch.$async) { |
| _ValidationError = gen.scopeValue("Error", { |
| ref: validation_error_1.default, |
| code: (0, codegen_1._) `require("ajv/dist/runtime/validation_error").default`, |
| }); |
| } |
| const validateName = gen.scopeName("validate"); |
| sch.validateName = validateName; |
| const schemaCxt = { |
| gen, |
| allErrors: this.opts.allErrors, |
| data: names_1.default.data, |
| parentData: names_1.default.parentData, |
| parentDataProperty: names_1.default.parentDataProperty, |
| dataNames: [names_1.default.data], |
| dataPathArr: [codegen_1.nil], |
| dataLevel: 0, |
| dataTypes: [], |
| definedProperties: new Set(), |
| topSchemaRef: gen.scopeValue("schema", this.opts.code.source === true |
| ? { ref: sch.schema, code: (0, codegen_1.stringify)(sch.schema) } |
| : { ref: sch.schema }), |
| validateName, |
| ValidationError: _ValidationError, |
| schema: sch.schema, |
| schemaEnv: sch, |
| rootId, |
| baseId: sch.baseId || rootId, |
| schemaPath: codegen_1.nil, |
| errSchemaPath: sch.schemaPath || (this.opts.jtd ? "" : "#"), |
| errorPath: (0, codegen_1._) `""`, |
| opts: this.opts, |
| self: this, |
| }; |
| let sourceCode; |
| try { |
| this._compilations.add(sch); |
| (0, validate_1.validateFunctionCode)(schemaCxt); |
| gen.optimize(this.opts.code.optimize); |
| // gen.optimize(1) |
| const validateCode = gen.toString(); |
| sourceCode = `${gen.scopeRefs(names_1.default.scope)}return ${validateCode}`; |
| // console.log((codeSize += sourceCode.length), (nodeCount += gen.nodeCount)) |
| if (this.opts.code.process) |
| sourceCode = this.opts.code.process(sourceCode, sch); |
| // console.log("\n\n\n *** \n", sourceCode) |
| const makeValidate = new Function(`${names_1.default.self}`, `${names_1.default.scope}`, sourceCode); |
| const validate = makeValidate(this, this.scope.get()); |
| this.scope.value(validateName, { ref: validate }); |
| validate.errors = null; |
| validate.schema = sch.schema; |
| validate.schemaEnv = sch; |
| if (sch.$async) |
| validate.$async = true; |
| if (this.opts.code.source === true) { |
| validate.source = { validateName, validateCode, scopeValues: gen._values }; |
| } |
| if (this.opts.unevaluated) { |
| const { props, items } = schemaCxt; |
| validate.evaluated = { |
| props: props instanceof codegen_1.Name ? undefined : props, |
| items: items instanceof codegen_1.Name ? undefined : items, |
| dynamicProps: props instanceof codegen_1.Name, |
| dynamicItems: items instanceof codegen_1.Name, |
| }; |
| if (validate.source) |
| validate.source.evaluated = (0, codegen_1.stringify)(validate.evaluated); |
| } |
| sch.validate = validate; |
| return sch; |
| } |
| catch (e) { |
| delete sch.validate; |
| delete sch.validateName; |
| if (sourceCode) |
| this.logger.error("Error compiling schema, function code:", sourceCode); |
| // console.log("\n\n\n *** \n", sourceCode, this.opts) |
| throw e; |
| } |
| finally { |
| this._compilations.delete(sch); |
| } |
| } |
| exports.compileSchema = compileSchema; |
| function resolveRef(root, baseId, ref) { |
| var _a; |
| ref = (0, resolve_1.resolveUrl)(baseId, ref); |
| const schOrFunc = root.refs[ref]; |
| if (schOrFunc) |
| return schOrFunc; |
| let _sch = resolve.call(this, root, ref); |
| if (_sch === undefined) { |
| const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref]; // TODO maybe localRefs should hold SchemaEnv |
| const { schemaId } = this.opts; |
| if (schema) |
| _sch = new SchemaEnv({ schema, schemaId, root, baseId }); |
| } |
| if (_sch === undefined) |
| return; |
| return (root.refs[ref] = inlineOrCompile.call(this, _sch)); |
| } |
| exports.resolveRef = resolveRef; |
| function inlineOrCompile(sch) { |
| if ((0, resolve_1.inlineRef)(sch.schema, this.opts.inlineRefs)) |
| return sch.schema; |
| return sch.validate ? sch : compileSchema.call(this, sch); |
| } |
| // Index of schema compilation in the currently compiled list |
| function getCompilingSchema(schEnv) { |
| for (const sch of this._compilations) { |
| if (sameSchemaEnv(sch, schEnv)) |
| return sch; |
| } |
| } |
| exports.getCompilingSchema = getCompilingSchema; |
| function sameSchemaEnv(s1, s2) { |
| return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId; |
| } |
| // resolve and compile the references ($ref) |
| // TODO returns AnySchemaObject (if the schema can be inlined) or validation function |
| function resolve(root, // information about the root schema for the current schema |
| ref // reference to resolve |
| ) { |
| let sch; |
| while (typeof (sch = this.refs[ref]) == "string") |
| ref = sch; |
| return sch || this.schemas[ref] || resolveSchema.call(this, root, ref); |
| } |
| // Resolve schema, its root and baseId |
| function resolveSchema(root, // root object with properties schema, refs TODO below SchemaEnv is assigned to it |
| ref // reference to resolve |
| ) { |
| const p = URI.parse(ref); |
| const refPath = (0, resolve_1._getFullPath)(p); |
| let baseId = (0, resolve_1.getFullPath)(root.baseId); |
| // TODO `Object.keys(root.schema).length > 0` should not be needed - but removing breaks 2 tests |
| if (Object.keys(root.schema).length > 0 && refPath === baseId) { |
| return getJsonPointer.call(this, p, root); |
| } |
| const id = (0, resolve_1.normalizeId)(refPath); |
| const schOrRef = this.refs[id] || this.schemas[id]; |
| if (typeof schOrRef == "string") { |
| const sch = resolveSchema.call(this, root, schOrRef); |
| if (typeof (sch === null || sch === void 0 ? void 0 : sch.schema) !== "object") |
| return; |
| return getJsonPointer.call(this, p, sch); |
| } |
| if (typeof (schOrRef === null || schOrRef === void 0 ? void 0 : schOrRef.schema) !== "object") |
| return; |
| if (!schOrRef.validate) |
| compileSchema.call(this, schOrRef); |
| if (id === (0, resolve_1.normalizeId)(ref)) { |
| const { schema } = schOrRef; |
| const { schemaId } = this.opts; |
| const schId = schema[schemaId]; |
| if (schId) |
| baseId = (0, resolve_1.resolveUrl)(baseId, schId); |
| return new SchemaEnv({ schema, schemaId, root, baseId }); |
| } |
| return getJsonPointer.call(this, p, schOrRef); |
| } |
| exports.resolveSchema = resolveSchema; |
| const PREVENT_SCOPE_CHANGE = new Set([ |
| "properties", |
| "patternProperties", |
| "enum", |
| "dependencies", |
| "definitions", |
| ]); |
| function getJsonPointer(parsedRef, { baseId, schema, root }) { |
| var _a; |
| if (((_a = parsedRef.fragment) === null || _a === void 0 ? void 0 : _a[0]) !== "/") |
| return; |
| for (const part of parsedRef.fragment.slice(1).split("/")) { |
| if (typeof schema === "boolean") |
| return; |
| const partSchema = schema[(0, util_1.unescapeFragment)(part)]; |
| if (partSchema === undefined) |
| return; |
| schema = partSchema; |
| // TODO PREVENT_SCOPE_CHANGE could be defined in keyword def? |
| const schId = typeof schema === "object" && schema[this.opts.schemaId]; |
| if (!PREVENT_SCOPE_CHANGE.has(part) && schId) { |
| baseId = (0, resolve_1.resolveUrl)(baseId, schId); |
| } |
| } |
| let env; |
| if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) { |
| const $ref = (0, resolve_1.resolveUrl)(baseId, schema.$ref); |
| env = resolveSchema.call(this, root, $ref); |
| } |
| // even though resolution failed we need to return SchemaEnv to throw exception |
| // so that compileAsync loads missing schema. |
| const { schemaId } = this.opts; |
| env = env || new SchemaEnv({ schema, schemaId, root, baseId }); |
| if (env.schema !== env.root.schema) |
| return env; |
| return undefined; |
| } |
| //# sourceMappingURL=index.js.map |