UNPKG

20.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getData = exports.KeywordCxt = exports.validateFunctionCode = void 0;
4const boolSchema_1 = require("./boolSchema");
5const dataType_1 = require("./dataType");
6const applicability_1 = require("./applicability");
7const dataType_2 = require("./dataType");
8const defaults_1 = require("./defaults");
9const keyword_1 = require("./keyword");
10const subschema_1 = require("./subschema");
11const codegen_1 = require("../codegen");
12const names_1 = require("../names");
13const resolve_1 = require("../resolve");
14const util_1 = require("../util");
15const errors_1 = require("../errors");
16// schema compilation - generates validation function, subschemaCode (below) is used for subschemas
17function validateFunctionCode(it) {
18 if (isSchemaObj(it)) {
19 checkKeywords(it);
20 if (schemaCxtHasRules(it)) {
21 topSchemaObjCode(it);
22 return;
23 }
24 }
25 validateFunction(it, () => (0, boolSchema_1.topBoolOrEmptySchema)(it));
26}
27exports.validateFunctionCode = validateFunctionCode;
28function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) {
29 if (opts.code.es5) {
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);
33 gen.code(body);
34 });
35 }
36 else {
37 gen.func(validateName, (0, codegen_1._) `${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body));
38 }
39}
40function 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}}={}`;
42}
43function 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}`);
49 if (opts.dynamicRef)
50 gen.var(names_1.default.dynamicAnchors, (0, codegen_1._) `${names_1.default.valCxt}.${names_1.default.dynamicAnchors}`);
51 }, () => {
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);
56 if (opts.dynamicRef)
57 gen.var(names_1.default.dynamicAnchors, (0, codegen_1._) `{}`);
58 });
59}
60function topSchemaObjCode(it) {
61 const { schema, opts, gen } = it;
62 validateFunction(it, () => {
63 if (opts.$comment && schema.$comment)
64 commentKeyword(it);
65 checkNoDefault(it);
66 gen.let(names_1.default.vErrors, null);
67 gen.let(names_1.default.errors, 0);
68 if (opts.unevaluated)
69 resetEvaluated(it);
70 typeAndKeywords(it);
71 returnResults(it);
72 });
73 return;
74}
75function 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`));
81}
82function 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;
85}
86// schema compilation - this function is used recursively to generate code for sub-schemas
87function subschemaCode(it, valid) {
88 if (isSchemaObj(it)) {
89 checkKeywords(it);
90 if (schemaCxtHasRules(it)) {
91 subSchemaObjCode(it, valid);
92 return;
93 }
94 }
95 (0, boolSchema_1.boolOrEmptySchema)(it, valid);
96}
97function schemaCxtHasRules({ schema, self }) {
98 if (typeof schema == "boolean")
99 return !schema;
100 for (const key in schema)
101 if (self.RULES.all[key])
102 return true;
103 return false;
104}
105function isSchemaObj(it) {
106 return typeof it.schema != "boolean";
107}
108function subSchemaObjCode(it, valid) {
109 const { schema, gen, opts } = it;
110 if (opts.$comment && schema.$comment)
111 commentKeyword(it);
112 updateContext(it);
113 checkAsyncSchema(it);
114 const errsCount = gen.const("_errs", names_1.default.errors);
115 typeAndKeywords(it, errsCount);
116 // TODO var
117 gen.var(valid, (0, codegen_1._) `${errsCount} === ${names_1.default.errors}`);
118}
119function checkKeywords(it) {
120 (0, util_1.checkUnknownRules)(it);
121 checkRefsAndKeywords(it);
122}
123function typeAndKeywords(it, errsCount) {
124 if (it.opts.jtd)
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);
129}
130function 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}"`);
134 }
135}
136function 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");
140 }
141}
142function updateContext(it) {
143 const schId = it.schema[it.opts.schemaId];
144 if (schId)
145 it.baseId = (0, resolve_1.resolveUrl)(it.baseId, schId);
146}
147function checkAsyncSchema(it) {
148 if (it.schema.$async && !it.schemaEnv.$async)
149 throw new Error("async schema in sync schema");
150}
151function 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})`);
155 }
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)`);
160 }
161}
162function 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})`));
167 }
168 else {
169 gen.assign((0, codegen_1._) `${validateName}.errors`, names_1.default.vErrors);
170 if (opts.unevaluated)
171 assignEvaluated(it);
172 gen.return((0, codegen_1._) `${names_1.default.errors} === 0`);
173 }
174}
175function 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);
180}
181function 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
186 return;
187 }
188 if (!opts.jtd)
189 checkStrictTypes(it, types);
190 gen.block(() => {
191 for (const group of RULES.rules)
192 groupKeywords(group);
193 groupKeywords(RULES.post);
194 });
195 function groupKeywords(group) {
196 if (!(0, applicability_1.shouldUseGroup)(schema, group))
197 return;
198 if (group.type) {
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) {
202 gen.else();
203 (0, dataType_2.reportTypeError)(it);
204 }
205 gen.endIf();
206 }
207 else {
208 iterateKeywords(it, group);
209 }
210 // TODO make it "ok" call?
211 if (!allErrors)
212 gen.if((0, codegen_1._) `${names_1.default.errors} === ${errsCount || 0}`);
213 }
214}
215function iterateKeywords(it, group) {
216 const { gen, schema, opts: { useDefaults }, } = it;
217 if (useDefaults)
218 (0, defaults_1.assignDefaults)(it, group.type);
219 gen.block(() => {
220 for (const rule of group.rules) {
221 if ((0, applicability_1.shouldUseRule)(schema, rule)) {
222 keywordCode(it, rule.keyword, rule.definition, group.type);
223 }
224 }
225 });
226}
227function checkStrictTypes(it, types) {
228 if (it.schemaEnv.meta || !it.opts.strictTypes)
229 return;
230 checkContextTypes(it, types);
231 if (!it.opts.allowUnionTypes)
232 checkMultipleTypes(it, types);
233 checkKeywordTypes(it, it.dataTypes);
234}
235function checkContextTypes(it, types) {
236 if (!types.length)
237 return;
238 if (!it.dataTypes.length) {
239 it.dataTypes = types;
240 return;
241 }
242 types.forEach((t) => {
243 if (!includesType(it.dataTypes, t)) {
244 strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`);
245 }
246 });
247 it.dataTypes = it.dataTypes.filter((t) => includesType(types, t));
248}
249function checkMultipleTypes(it, ts) {
250 if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) {
251 strictTypesError(it, "use allowUnionTypes to allow union type keyword");
252 }
253}
254function 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}"`);
262 }
263 }
264 }
265}
266function hasApplicableType(schTs, kwdT) {
267 return schTs.includes(kwdT) || (kwdT === "number" && schTs.includes("integer"));
268}
269function includesType(ts, t) {
270 return ts.includes(t) || (t === "integer" && ts.includes("number"));
271}
272function 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);
276}
277class KeywordCxt {
278 constructor(it, def, keyword) {
279 (0, keyword_1.validateKeywordUsage)(it, def, keyword);
280 this.gen = it.gen;
281 this.allErrors = it.allErrors;
282 this.keyword = keyword;
283 this.data = it.data;
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;
289 this.params = {};
290 this.it = it;
291 this.def = def;
292 if (this.$data) {
293 this.schemaCode = it.gen.const("vSchema", getData(this.$data, it));
294 }
295 else {
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)}`);
299 }
300 }
301 if ("code" in def ? def.trackErrors : def.errors !== false) {
302 this.errsCount = it.gen.const("_errs", names_1.default.errors);
303 }
304 }
305 result(condition, successAction, failAction) {
306 this.failResult((0, codegen_1.not)(condition), successAction, failAction);
307 }
308 failResult(condition, successAction, failAction) {
309 this.gen.if(condition);
310 if (failAction)
311 failAction();
312 else
313 this.error();
314 if (successAction) {
315 this.gen.else();
316 successAction();
317 if (this.allErrors)
318 this.gen.endIf();
319 }
320 else {
321 if (this.allErrors)
322 this.gen.endIf();
323 else
324 this.gen.else();
325 }
326 }
327 pass(condition, failAction) {
328 this.failResult((0, codegen_1.not)(condition), undefined, failAction);
329 }
330 fail(condition) {
331 if (condition === undefined) {
332 this.error();
333 if (!this.allErrors)
334 this.gen.if(false); // this branch will be removed by gen.optimize
335 return;
336 }
337 this.gen.if(condition);
338 this.error();
339 if (this.allErrors)
340 this.gen.endIf();
341 else
342 this.gen.else();
343 }
344 fail$data(condition) {
345 if (!this.$data)
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)})`);
349 }
350 error(append, errorParams, errorPaths) {
351 if (errorParams) {
352 this.setParams(errorParams);
353 this._error(append, errorPaths);
354 this.setParams({});
355 return;
356 }
357 this._error(append, errorPaths);
358 }
359 _error(append, errorPaths) {
360 ;
361 (append ? errors_1.reportExtraError : errors_1.reportError)(this, this.def.error, errorPaths);
362 }
363 $dataError() {
364 (0, errors_1.reportError)(this, this.def.$dataError || errors_1.keyword$DataError);
365 }
366 reset() {
367 if (this.errsCount === undefined)
368 throw new Error('add "trackErrors" to keyword definition');
369 (0, errors_1.resetErrorsCount)(this.gen, this.errsCount);
370 }
371 ok(cond) {
372 if (!this.allErrors)
373 this.gen.if(cond);
374 }
375 setParams(obj, assign) {
376 if (assign)
377 Object.assign(this.params, obj);
378 else
379 this.params = obj;
380 }
381 block$data(valid, codeBlock, $dataValid = codegen_1.nil) {
382 this.gen.block(() => {
383 this.check$data(valid, $dataValid);
384 codeBlock();
385 });
386 }
387 check$data(valid = codegen_1.nil, $dataValid = codegen_1.nil) {
388 if (!this.$data)
389 return;
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());
396 this.$dataError();
397 if (valid !== codegen_1.nil)
398 gen.assign(valid, false);
399 }
400 gen.else();
401 }
402 invalid$data() {
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)}`;
412 }
413 return codegen_1.nil;
414 }
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})`;
419 }
420 return codegen_1.nil;
421 }
422 }
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);
429 return nextContext;
430 }
431 mergeEvaluated(schemaCxt, toName) {
432 const { it, gen } = this;
433 if (!it.opts.unevaluated)
434 return;
435 if (it.props !== true && schemaCxt.props !== undefined) {
436 it.props = util_1.mergeEvaluated.props(gen, schemaCxt.props, it.props, toName);
437 }
438 if (it.items !== true && schemaCxt.items !== undefined) {
439 it.items = util_1.mergeEvaluated.items(gen, schemaCxt.items, it.items, toName);
440 }
441 }
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));
446 return true;
447 }
448 }
449}
450exports.KeywordCxt = KeywordCxt;
451function keywordCode(it, keyword, def, ruleType) {
452 const cxt = new KeywordCxt(it, def, keyword);
453 if ("code" in def) {
454 def.code(cxt, ruleType);
455 }
456 else if (cxt.$data && def.validate) {
457 (0, keyword_1.funcKeywordCode)(cxt, def);
458 }
459 else if ("macro" in def) {
460 (0, keyword_1.macroKeywordCode)(cxt, def);
461 }
462 else if (def.compile || def.validate) {
463 (0, keyword_1.funcKeywordCode)(cxt, def);
464 }
465}
466const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/;
467const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
468function getData($data, { dataLevel, dataNames, dataPathArr }) {
469 let jsonPointer;
470 let data;
471 if ($data === "")
472 return names_1.default.rootData;
473 if ($data[0] === "/") {
474 if (!JSON_POINTER.test($data))
475 throw new Error(`Invalid JSON-pointer: ${$data}`);
476 jsonPointer = $data;
477 data = names_1.default.rootData;
478 }
479 else {
480 const matches = RELATIVE_JSON_POINTER.exec($data);
481 if (!matches)
482 throw new Error(`Invalid JSON-pointer: ${$data}`);
483 const up = +matches[1];
484 jsonPointer = matches[2];
485 if (jsonPointer === "#") {
486 if (up >= dataLevel)
487 throw new Error(errorMsg("property/index", up));
488 return dataPathArr[dataLevel - up];
489 }
490 if (up > dataLevel)
491 throw new Error(errorMsg("data", up));
492 data = dataNames[dataLevel - up];
493 if (!jsonPointer)
494 return data;
495 }
496 let expr = data;
497 const segments = jsonPointer.split("/");
498 for (const segment of segments) {
499 if (segment) {
500 data = (0, codegen_1._) `${data}${(0, codegen_1.getProperty)((0, util_1.unescapeJsonPointer)(segment))}`;
501 expr = (0, codegen_1._) `${expr} && ${data}`;
502 }
503 }
504 return expr;
505 function errorMsg(pointerType, up) {
506 return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`;
507 }
508}
509exports.getData = getData;
510//# sourceMappingURL=index.js.map
\No newline at end of file