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, () => (0, boolSchema_1.topBoolOrEmptySchema)(it));
27 exports.validateFunctionCode = validateFunctionCode;
28 function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) {
30 gen.func(validateName, (0, codegen_1._) `${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => {
31 gen.code((0, codegen_1._) `"use strict"; ${funcSourceUrl(schema, opts)}`);
32 destructureValCxtES5(gen, opts);
37 gen.func(validateName, (0, codegen_1._) `${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body));
40 function destructureValCxt(opts) {
41 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}}={}`;
43 function destructureValCxtES5(gen, opts) {
44 gen.if(names_1.default.valCxt, () => {
45 gen.var(names_1.default.instancePath, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.instancePath}`);
46 gen.var(names_1.default.parentData, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.parentData}`);
47 gen.var(names_1.default.parentDataProperty, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.parentDataProperty}`);
48 gen.var(names_1.default.rootData, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.rootData}`);
50 gen.var(names_1.default.dynamicAnchors, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.dynamicAnchors}`);
52 gen.var(names_1.default.instancePath, (0, codegen_1._) `""`);
53 gen.var(names_1.default.parentData, (0, codegen_1._) `undefined`);
54 gen.var(names_1.default.parentDataProperty, (0, codegen_1._) `undefined`);
55 gen.var(names_1.default.rootData, names_1.default.data);
57 gen.var(names_1.default.dynamicAnchors, (0, 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", (0, codegen_1._) `${validateName}.evaluated`);
79 gen.if((0, codegen_1._) `${it.evaluated}.dynamicProps`, () => gen.assign((0, codegen_1._) `${it.evaluated}.props`, (0, codegen_1._) `undefined`));
80 gen.if((0, codegen_1._) `${it.evaluated}.dynamicItems`, () => gen.assign((0, codegen_1._) `${it.evaluated}.items`, (0, codegen_1._) `undefined`));
82 function funcSourceUrl(schema, opts) {
83 const schId = typeof schema == "object" && schema[opts.schemaId];
84 return schId && (opts.code.source || opts.code.process) ? (0, codegen_1._) `/*# sourceURL=${schId} */` : codegen_1.nil;
86 // schema compilation - this function is used recursively to generate code for sub-schemas
87 function subschemaCode(it, valid) {
88 if (isSchemaObj(it)) {
90 if (schemaCxtHasRules(it)) {
91 subSchemaObjCode(it, valid);
95 (0, boolSchema_1.boolOrEmptySchema)(it, valid);
97 function schemaCxtHasRules({ schema, self }) {
98 if (typeof schema == "boolean")
100 for (const key in schema)
101 if (self.RULES.all[key])
105 function isSchemaObj(it) {
106 return typeof it.schema != "boolean";
108 function subSchemaObjCode(it, valid) {
109 const { schema, gen, opts } = it;
110 if (opts.$comment && schema.$comment)
113 checkAsyncSchema(it);
114 const errsCount = gen.const("_errs", names_1.default.errors);
115 typeAndKeywords(it, errsCount);
117 gen.var(valid, (0, codegen_1._) `${errsCount} === ${names_1.default.errors}`);
119 function checkKeywords(it) {
120 (0, util_1.checkUnknownRules)(it);
121 checkRefsAndKeywords(it);
123 function typeAndKeywords(it, errsCount) {
125 return schemaKeywords(it, [], false, errsCount);
126 const types = (0, dataType_1.getSchemaTypes)(it.schema);
127 const checkedTypes = (0, dataType_1.coerceAndCheckDataType)(it, types);
128 schemaKeywords(it, types, !checkedTypes, errsCount);
130 function checkRefsAndKeywords(it) {
131 const { schema, errSchemaPath, opts, self } = it;
132 if (schema.$ref && opts.ignoreKeywordsWithRef && (0, util_1.schemaHasRulesButRef)(schema, self.RULES)) {
133 self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`);
136 function checkNoDefault(it) {
137 const { schema, opts } = it;
138 if (schema.default !== undefined && opts.useDefaults && opts.strictSchema) {
139 (0, util_1.checkStrictMode)(it, "default is ignored in the schema root");
142 function updateContext(it) {
143 const schId = it.schema[it.opts.schemaId];
145 it.baseId = (0, resolve_1.resolveUrl)(it.baseId, schId);
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((0, codegen_1._) `${names_1.default.self}.logger.log(${msg})`);
156 else if (typeof opts.$comment == "function") {
157 const schemaPath = (0, codegen_1.str) `${errSchemaPath}/$comment`;
158 const rootName = gen.scopeValue("root", { ref: schemaEnv.root });
159 gen.code((0, 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((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})`));
169 gen.assign((0, codegen_1._) `${validateName}.errors`, names_1.default.vErrors);
170 if (opts.unevaluated)
172 gen.return((0, codegen_1._) `${names_1.default.errors} === 0`);
175 function assignEvaluated({ gen, evaluated, props, items }) {
176 if (props instanceof codegen_1.Name)
177 gen.assign((0, codegen_1._) `${evaluated}.props`, props);
178 if (items instanceof codegen_1.Name)
179 gen.assign((0, 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 || !(0, 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 (!(0, applicability_1.shouldUseGroup)(schema, group))
199 gen.if((0, dataType_2.checkDataType)(group.type, data, opts.strictNumbers));
200 iterateKeywords(it, group);
201 if (types.length === 1 && types[0] === group.type && typeErrors) {
203 (0, dataType_2.reportTypeError)(it);
208 iterateKeywords(it, group);
210 // TODO make it "ok" call?
212 gen.if((0, codegen_1._) `${names_1.default.errors} === ${errsCount || 0}`);
215 function iterateKeywords(it, group) {
216 const { gen, schema, opts: { useDefaults }, } = it;
218 (0, defaults_1.assignDefaults)(it, group.type);
220 for (const rule of group.rules) {
221 if ((0, 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" && (0, 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 (0, util_1.checkStrictMode)(it, msg, it.opts.strictTypes);
278 constructor(it, def, keyword) {
279 (0, 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 = (0, 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 (!(0, 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.failResult((0, codegen_1.not)(condition), successAction, failAction);
308 failResult(condition, successAction, failAction) {
309 this.gen.if(condition);
327 pass(condition, failAction) {
328 this.failResult((0, codegen_1.not)(condition), undefined, failAction);
331 if (condition === undefined) {
334 this.gen.if(false); // this branch will be removed by gen.optimize
337 this.gen.if(condition);
344 fail$data(condition) {
346 return this.fail(condition);
347 const { schemaCode } = this;
348 this.fail((0, codegen_1._) `${schemaCode} !== undefined && (${(0, codegen_1.or)(this.invalid$data(), condition)})`);
350 error(append, errorParams, errorPaths) {
352 this.setParams(errorParams);
353 this._error(append, errorPaths);
357 this._error(append, errorPaths);
359 _error(append, errorPaths) {
361 (append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths);
364 (0, errors_1.reportError)(this, this.def.$dataError || errors_1.keyword$DataError);
367 if (this.errsCount === undefined)
368 throw new Error('add "trackErrors" to keyword definition');
369 (0, errors_1.resetErrorsCount)(this.gen, this.errsCount);
375 setParams(obj, assign) {
377 Object.assign(this.params, obj);
381 block$data(valid, codeBlock, $dataValid = codegen_1.nil) {
382 this.gen.block(() => {
383 this.check$data(valid, $dataValid);
387 check$data(valid = codegen_1.nil, $dataValid = codegen_1.nil) {
390 const { gen, schemaCode, schemaType, def } = this;
391 gen.if((0, codegen_1.or)((0, codegen_1._) `${schemaCode} === undefined`, $dataValid));
392 if (valid !== codegen_1.nil)
393 gen.assign(valid, true);
394 if (schemaType.length || def.validateSchema) {
395 gen.elseIf(this.invalid$data());
397 if (valid !== codegen_1.nil)
398 gen.assign(valid, false);
403 const { gen, schemaCode, schemaType, def, it } = this;
404 return (0, codegen_1.or)(wrong$DataType(), invalid$DataSchema());
405 function wrong$DataType() {
406 if (schemaType.length) {
407 /* istanbul ignore if */
408 if (!(schemaCode instanceof codegen_1.Name))
409 throw new Error("ajv implementation error");
410 const st = Array.isArray(schemaType) ? schemaType : [schemaType];
411 return (0, codegen_1._) `${(0, dataType_2.checkDataTypes)(st, schemaCode, it.opts.strictNumbers, dataType_2.DataType.Wrong)}`;
413 return codegen_1.nil;
415 function invalid$DataSchema() {
416 if (def.validateSchema) {
417 const validateSchemaRef = gen.scopeValue("validate$data", { ref: def.validateSchema }); // TODO value.code for standalone
418 return (0, codegen_1._) `!${validateSchemaRef}(${schemaCode})`;
420 return codegen_1.nil;
423 subschema(appl, valid) {
424 const subschema = (0, subschema_1.getSubschema)(this.it, appl);
425 (0, subschema_1.extendSubschemaData)(subschema, this.it, appl);
426 (0, subschema_1.extendSubschemaMode)(subschema, appl);
427 const nextContext = { ...this.it, ...subschema, items: undefined, props: undefined };
428 subschemaCode(nextContext, valid);
431 mergeEvaluated(schemaCxt, toName) {
432 const { it, gen } = this;
433 if (!it.opts.unevaluated)
435 if (it.props !== true && schemaCxt.props !== undefined) {
436 it.props = util_1.mergeEvaluated.props(gen, schemaCxt.props, it.props, toName);
438 if (it.items !== true && schemaCxt.items !== undefined) {
439 it.items = util_1.mergeEvaluated.items(gen, schemaCxt.items, it.items, toName);
442 mergeValidEvaluated(schemaCxt, valid) {
443 const { it, gen } = this;
444 if (it.opts.unevaluated && (it.props !== true || it.items !== true)) {
445 gen.if(valid, () => this.mergeEvaluated(schemaCxt, codegen_1.Name));
450 exports.KeywordCxt = KeywordCxt;
451 function keywordCode(it, keyword, def, ruleType) {
452 const cxt = new KeywordCxt(it, def, keyword);
454 def.code(cxt, ruleType);
456 else if (cxt.$data && def.validate) {
457 (0, keyword_1.funcKeywordCode)(cxt, def);
459 else if ("macro" in def) {
460 (0, keyword_1.macroKeywordCode)(cxt, def);
462 else if (def.compile || def.validate) {
463 (0, keyword_1.funcKeywordCode)(cxt, def);
466 const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
467 const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
468 function getData($data, { dataLevel, dataNames, dataPathArr }) {
472 return names_1.default.rootData;
473 if ($data[0] === "/") {
474 if (!JSON_POINTER.test($data))
475 throw new Error(`Invalid JSON-pointer: ${$data}`);
477 data = names_1.default.rootData;
480 const matches = RELATIVE_JSON_POINTER.exec($data);
482 throw new Error(`Invalid JSON-pointer: ${$data}`);
483 const up = +matches[1];
484 jsonPointer = matches[2];
485 if (jsonPointer === "#") {
487 throw new Error(errorMsg("property/index", up));
488 return dataPathArr[dataLevel - up];
491 throw new Error(errorMsg("data", up));
492 data = dataNames[dataLevel - up];
497 const segments = jsonPointer.split("/");
498 for (const segment of segments) {
500 data = (0, codegen_1._) `${data}${(0, codegen_1.getProperty)((0, util_1.unescapeJsonPointer)(segment))}`;
501 expr = (0, codegen_1._) `${expr} && ${data}`;
505 function errorMsg(pointerType, up) {
506 return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
509 exports.getData = getData;
510 //# sourceMappingURL=index.js.map