2 Object.defineProperty(exports, "__esModule", { value: true });
3 exports.getData = exports.KeywordCxt = exports.validateFunctionCode = void 0;
4 const boolSchema_1 = require("./boolSchema");
5 const dataType_1 = require("./dataType");
6 const applicability_1 = require("./applicability");
7 const dataType_2 = require("./dataType");
8 const defaults_1 = require("./defaults");
9 const keyword_1 = require("./keyword");
10 const subschema_1 = require("./subschema");
11 const codegen_1 = require("../codegen");
12 const names_1 = require("../names");
13 const resolve_1 = require("../resolve");
14 const util_1 = require("../util");
15 const errors_1 = require("../errors");
16 // schema compilation - generates validation function, subschemaCode (below) is used for subschemas
17 function validateFunctionCode(it) {
18 if (isSchemaObj(it)) {
20 if (schemaCxtHasRules(it)) {
25 validateFunction(it, () => boolSchema_1.topBoolOrEmptySchema(it));
27 exports.validateFunctionCode = validateFunctionCode;
28 function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) {
30 gen.func(validateName, codegen_1._ `${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => {
31 gen.code(codegen_1._ `"use strict"; ${funcSourceUrl(schema, opts)}`);
32 destructureValCxtES5(gen, opts);
37 gen.func(validateName, codegen_1._ `${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body));
40 function destructureValCxt(opts) {
41 return codegen_1._ `{${names_1.default.instancePath}="", ${names_1.default.parentData}, ${names_1.default.parentDataProperty}, ${names_1.default.rootData}=${names_1.default.data}${opts.dynamicRef ? codegen_1._ `, ${names_1.default.dynamicAnchors}={}` : codegen_1.nil}}={}`;
43 function destructureValCxtES5(gen, opts) {
44 gen.if(names_1.default.valCxt, () => {
45 gen.var(names_1.default.instancePath, codegen_1._ `${names_1.default.valCxt}.${names_1.default.instancePath}`);
46 gen.var(names_1.default.parentData, codegen_1._ `${names_1.default.valCxt}.${names_1.default.parentData}`);
47 gen.var(names_1.default.parentDataProperty, codegen_1._ `${names_1.default.valCxt}.${names_1.default.parentDataProperty}`);
48 gen.var(names_1.default.rootData, codegen_1._ `${names_1.default.valCxt}.${names_1.default.rootData}`);
50 gen.var(names_1.default.dynamicAnchors, codegen_1._ `${names_1.default.valCxt}.${names_1.default.dynamicAnchors}`);
52 gen.var(names_1.default.instancePath, codegen_1._ `""`);
53 gen.var(names_1.default.parentData, codegen_1._ `undefined`);
54 gen.var(names_1.default.parentDataProperty, codegen_1._ `undefined`);
55 gen.var(names_1.default.rootData, names_1.default.data);
57 gen.var(names_1.default.dynamicAnchors, codegen_1._ `{}`);
60 function topSchemaObjCode(it) {
61 const { schema, opts, gen } = it;
62 validateFunction(it, () => {
63 if (opts.$comment && schema.$comment)
66 gen.let(names_1.default.vErrors, null);
67 gen.let(names_1.default.errors, 0);
75 function resetEvaluated(it) {
76 // TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated
77 const { gen, validateName } = it;
78 it.evaluated = gen.const("evaluated", codegen_1._ `${validateName}.evaluated`);
79 gen.if(codegen_1._ `${it.evaluated}.dynamicProps`, () => gen.assign(codegen_1._ `${it.evaluated}.props`, codegen_1._ `undefined`));
80 gen.if(codegen_1._ `${it.evaluated}.dynamicItems`, () => gen.assign(codegen_1._ `${it.evaluated}.items`, codegen_1._ `undefined`));
82 function funcSourceUrl(schema, opts) {
83 return typeof schema == "object" && schema.$id && (opts.code.source || opts.code.process)
84 ? codegen_1._ `/*# sourceURL=${schema.$id} */`
87 // schema compilation - this function is used recursively to generate code for sub-schemas
88 function subschemaCode(it, valid) {
89 if (isSchemaObj(it)) {
91 if (schemaCxtHasRules(it)) {
92 subSchemaObjCode(it, valid);
96 boolSchema_1.boolOrEmptySchema(it, valid);
98 function schemaCxtHasRules({ schema, self }) {
99 if (typeof schema == "boolean")
101 for (const key in schema)
102 if (self.RULES.all[key])
106 function isSchemaObj(it) {
107 return typeof it.schema != "boolean";
109 function subSchemaObjCode(it, valid) {
110 const { schema, gen, opts } = it;
111 if (opts.$comment && schema.$comment)
114 checkAsyncSchema(it);
115 const errsCount = gen.const("_errs", names_1.default.errors);
116 typeAndKeywords(it, errsCount);
118 gen.var(valid, codegen_1._ `${errsCount} === ${names_1.default.errors}`);
120 function checkKeywords(it) {
121 util_1.checkUnknownRules(it);
122 checkRefsAndKeywords(it);
124 function typeAndKeywords(it, errsCount) {
126 return schemaKeywords(it, [], false, errsCount);
127 const types = dataType_1.getSchemaTypes(it.schema);
128 const checkedTypes = dataType_1.coerceAndCheckDataType(it, types);
129 schemaKeywords(it, types, !checkedTypes, errsCount);
131 function checkRefsAndKeywords(it) {
132 const { schema, errSchemaPath, opts, self } = it;
133 if (schema.$ref && opts.ignoreKeywordsWithRef && util_1.schemaHasRulesButRef(schema, self.RULES)) {
134 self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`);
137 function checkNoDefault(it) {
138 const { schema, opts } = it;
139 if (schema.default !== undefined && opts.useDefaults && opts.strictSchema) {
140 util_1.checkStrictMode(it, "default is ignored in the schema root");
143 function updateContext(it) {
145 it.baseId = resolve_1.resolveUrl(it.baseId, it.schema.$id);
147 function checkAsyncSchema(it) {
148 if (it.schema.$async && !it.schemaEnv.$async)
149 throw new Error("async schema in sync schema");
151 function commentKeyword({ gen, schemaEnv, schema, errSchemaPath, opts }) {
152 const msg = schema.$comment;
153 if (opts.$comment === true) {
154 gen.code(codegen_1._ `${names_1.default.self}.logger.log(${msg})`);
156 else if (typeof opts.$comment == "function") {
157 const schemaPath = codegen_1.str `${errSchemaPath}/$comment`;
158 const rootName = gen.scopeValue("root", { ref: schemaEnv.root });
159 gen.code(codegen_1._ `${names_1.default.self}.opts.$comment(${msg}, ${schemaPath}, ${rootName}.schema)`);
162 function returnResults(it) {
163 const { gen, schemaEnv, validateName, ValidationError, opts } = it;
164 if (schemaEnv.$async) {
165 // TODO assign unevaluated
166 gen.if(codegen_1._ `${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw(codegen_1._ `new ${ValidationError}(${names_1.default.vErrors})`));
169 gen.assign(codegen_1._ `${validateName}.errors`, names_1.default.vErrors);
170 if (opts.unevaluated)
172 gen.return(codegen_1._ `${names_1.default.errors} === 0`);
175 function assignEvaluated({ gen, evaluated, props, items }) {
176 if (props instanceof codegen_1.Name)
177 gen.assign(codegen_1._ `${evaluated}.props`, props);
178 if (items instanceof codegen_1.Name)
179 gen.assign(codegen_1._ `${evaluated}.items`, items);
181 function schemaKeywords(it, types, typeErrors, errsCount) {
182 const { gen, schema, data, allErrors, opts, self } = it;
183 const { RULES } = self;
184 if (schema.$ref && (opts.ignoreKeywordsWithRef || !util_1.schemaHasRulesButRef(schema, RULES))) {
185 gen.block(() => keywordCode(it, "$ref", RULES.all.$ref.definition)); // TODO typecast
189 checkStrictTypes(it, types);
191 for (const group of RULES.rules)
192 groupKeywords(group);
193 groupKeywords(RULES.post);
195 function groupKeywords(group) {
196 if (!applicability_1.shouldUseGroup(schema, group))
199 gen.if(dataType_2.checkDataType(group.type, data, opts.strictNumbers));
200 iterateKeywords(it, group);
201 if (types.length === 1 && types[0] === group.type && typeErrors) {
203 dataType_2.reportTypeError(it);
208 iterateKeywords(it, group);
210 // TODO make it "ok" call?
212 gen.if(codegen_1._ `${names_1.default.errors} === ${errsCount || 0}`);
215 function iterateKeywords(it, group) {
216 const { gen, schema, opts: { useDefaults }, } = it;
218 defaults_1.assignDefaults(it, group.type);
220 for (const rule of group.rules) {
221 if (applicability_1.shouldUseRule(schema, rule)) {
222 keywordCode(it, rule.keyword, rule.definition, group.type);
227 function checkStrictTypes(it, types) {
228 if (it.schemaEnv.meta || !it.opts.strictTypes)
230 checkContextTypes(it, types);
231 if (!it.opts.allowUnionTypes)
232 checkMultipleTypes(it, types);
233 checkKeywordTypes(it, it.dataTypes);
235 function checkContextTypes(it, types) {
238 if (!it.dataTypes.length) {
239 it.dataTypes = types;
242 types.forEach((t) => {
243 if (!includesType(it.dataTypes, t)) {
244 strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`);
247 it.dataTypes = it.dataTypes.filter((t) => includesType(types, t));
249 function checkMultipleTypes(it, ts) {
250 if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) {
251 strictTypesError(it, "use allowUnionTypes to allow union type keyword");
254 function checkKeywordTypes(it, ts) {
255 const rules = it.self.RULES.all;
256 for (const keyword in rules) {
257 const rule = rules[keyword];
258 if (typeof rule == "object" && applicability_1.shouldUseRule(it.schema, rule)) {
259 const { type } = rule.definition;
260 if (type.length && !type.some((t) => hasApplicableType(ts, t))) {
261 strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`);
266 function hasApplicableType(schTs, kwdT) {
267 return schTs.includes(kwdT) || (kwdT === "number" && schTs.includes("integer"));
269 function includesType(ts, t) {
270 return ts.includes(t) || (t === "integer" && ts.includes("number"));
272 function strictTypesError(it, msg) {
273 const schemaPath = it.schemaEnv.baseId + it.errSchemaPath;
274 msg += ` at "${schemaPath}" (strictTypes)`;
275 util_1.checkStrictMode(it, msg, it.opts.strictTypes);
278 constructor(it, def, keyword) {
279 keyword_1.validateKeywordUsage(it, def, keyword);
281 this.allErrors = it.allErrors;
282 this.keyword = keyword;
284 this.schema = it.schema[keyword];
285 this.$data = def.$data && it.opts.$data && this.schema && this.schema.$data;
286 this.schemaValue = util_1.schemaRefOrVal(it, this.schema, keyword, this.$data);
287 this.schemaType = def.schemaType;
288 this.parentSchema = it.schema;
293 this.schemaCode = it.gen.const("vSchema", getData(this.$data, it));
296 this.schemaCode = this.schemaValue;
297 if (!keyword_1.validSchemaType(this.schema, def.schemaType, def.allowUndefined)) {
298 throw new Error(`${keyword} value must be ${JSON.stringify(def.schemaType)}`);
301 if ("code" in def ? def.trackErrors : def.errors !== false) {
302 this.errsCount = it.gen.const("_errs", names_1.default.errors);
305 result(condition, successAction, failAction) {
306 this.gen.if(codegen_1.not(condition));
324 pass(condition, failAction) {
325 this.result(condition, undefined, failAction);
328 if (condition === undefined) {
331 this.gen.if(false); // this branch will be removed by gen.optimize
334 this.gen.if(condition);
341 fail$data(condition) {
343 return this.fail(condition);
344 const { schemaCode } = this;
345 this.fail(codegen_1._ `${schemaCode} !== undefined && (${codegen_1.or(this.invalid$data(), condition)})`);
347 error(append, errorParams, errorPaths) {
349 this.setParams(errorParams);
350 this._error(append, errorPaths);
354 this._error(append, errorPaths);
356 _error(append, errorPaths) {
358 (append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths);
361 errors_1.reportError(this, this.def.$dataError || errors_1.keyword$DataError);
364 if (this.errsCount === undefined)
365 throw new Error('add "trackErrors" to keyword definition');
366 errors_1.resetErrorsCount(this.gen, this.errsCount);
372 setParams(obj, assign) {
374 Object.assign(this.params, obj);
378 block$data(valid, codeBlock, $dataValid = codegen_1.nil) {
379 this.gen.block(() => {
380 this.check$data(valid, $dataValid);
384 check$data(valid = codegen_1.nil, $dataValid = codegen_1.nil) {
387 const { gen, schemaCode, schemaType, def } = this;
388 gen.if(codegen_1.or(codegen_1._ `${schemaCode} === undefined`, $dataValid));
389 if (valid !== codegen_1.nil)
390 gen.assign(valid, true);
391 if (schemaType.length || def.validateSchema) {
392 gen.elseIf(this.invalid$data());
394 if (valid !== codegen_1.nil)
395 gen.assign(valid, false);
400 const { gen, schemaCode, schemaType, def, it } = this;
401 return codegen_1.or(wrong$DataType(), invalid$DataSchema());
402 function wrong$DataType() {
403 if (schemaType.length) {
404 /* istanbul ignore if */
405 if (!(schemaCode instanceof codegen_1.Name))
406 throw new Error("ajv implementation error");
407 const st = Array.isArray(schemaType) ? schemaType : [schemaType];
408 return codegen_1._ `${dataType_2.checkDataTypes(st, schemaCode, it.opts.strictNumbers, dataType_2.DataType.Wrong)}`;
410 return codegen_1.nil;
412 function invalid$DataSchema() {
413 if (def.validateSchema) {
414 const validateSchemaRef = gen.scopeValue("validate$data", { ref: def.validateSchema }); // TODO value.code for standalone
415 return codegen_1._ `!${validateSchemaRef}(${schemaCode})`;
417 return codegen_1.nil;
420 subschema(appl, valid) {
421 const subschema = subschema_1.getSubschema(this.it, appl);
422 subschema_1.extendSubschemaData(subschema, this.it, appl);
423 subschema_1.extendSubschemaMode(subschema, appl);
424 const nextContext = { ...this.it, ...subschema, items: undefined, props: undefined };
425 subschemaCode(nextContext, valid);
428 mergeEvaluated(schemaCxt, toName) {
429 const { it, gen } = this;
430 if (!it.opts.unevaluated)
432 if (it.props !== true && schemaCxt.props !== undefined) {
433 it.props = util_1.mergeEvaluated.props(gen, schemaCxt.props, it.props, toName);
435 if (it.items !== true && schemaCxt.items !== undefined) {
436 it.items = util_1.mergeEvaluated.items(gen, schemaCxt.items, it.items, toName);
439 mergeValidEvaluated(schemaCxt, valid) {
440 const { it, gen } = this;
441 if (it.opts.unevaluated && (it.props !== true || it.items !== true)) {
442 gen.if(valid, () => this.mergeEvaluated(schemaCxt, codegen_1.Name));
447 exports.KeywordCxt = KeywordCxt;
448 function keywordCode(it, keyword, def, ruleType) {
449 const cxt = new KeywordCxt(it, def, keyword);
451 def.code(cxt, ruleType);
453 else if (cxt.$data && def.validate) {
454 keyword_1.funcKeywordCode(cxt, def);
456 else if ("macro" in def) {
457 keyword_1.macroKeywordCode(cxt, def);
459 else if (def.compile || def.validate) {
460 keyword_1.funcKeywordCode(cxt, def);
463 const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
464 const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
465 function getData($data, { dataLevel, dataNames, dataPathArr }) {
469 return names_1.default.rootData;
470 if ($data[0] === "/") {
471 if (!JSON_POINTER.test($data))
472 throw new Error(`Invalid JSON-pointer: ${$data}`);
474 data = names_1.default.rootData;
477 const matches = RELATIVE_JSON_POINTER.exec($data);
479 throw new Error(`Invalid JSON-pointer: ${$data}`);
480 const up = +matches[1];
481 jsonPointer = matches[2];
482 if (jsonPointer === "#") {
484 throw new Error(errorMsg("property/index", up));
485 return dataPathArr[dataLevel - up];
488 throw new Error(errorMsg("data", up));
489 data = dataNames[dataLevel - up];
494 const segments = jsonPointer.split("/");
495 for (const segment of segments) {
497 data = codegen_1._ `${data}${codegen_1.getProperty(util_1.unescapeJsonPointer(segment))}`;
498 expr = codegen_1._ `${expr} && ${data}`;
502 function errorMsg(pointerType, up) {
503 return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
506 exports.getData = getData;
507 //# sourceMappingURL=index.js.map