UNPKG

36.2 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7
8const {
9 stringHints,
10 numberHints
11} = require('./util/hints');
12/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
13
14/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
15
16/** @typedef {import("./validate").Schema} Schema */
17
18/** @typedef {import("./validate").ValidationErrorConfiguration} ValidationErrorConfiguration */
19
20/** @typedef {import("./validate").PostFormatter} PostFormatter */
21
22/** @typedef {import("./validate").SchemaUtilErrorObject} SchemaUtilErrorObject */
23
24/** @enum {number} */
25
26
27const SPECIFICITY = {
28 type: 1,
29 not: 1,
30 oneOf: 1,
31 anyOf: 1,
32 if: 1,
33 enum: 1,
34 const: 1,
35 instanceof: 1,
36 required: 2,
37 pattern: 2,
38 patternRequired: 2,
39 format: 2,
40 formatMinimum: 2,
41 formatMaximum: 2,
42 minimum: 2,
43 exclusiveMinimum: 2,
44 maximum: 2,
45 exclusiveMaximum: 2,
46 multipleOf: 2,
47 uniqueItems: 2,
48 contains: 2,
49 minLength: 2,
50 maxLength: 2,
51 minItems: 2,
52 maxItems: 2,
53 minProperties: 2,
54 maxProperties: 2,
55 dependencies: 2,
56 propertyNames: 2,
57 additionalItems: 2,
58 additionalProperties: 2,
59 absolutePath: 2
60};
61/**
62 *
63 * @param {Array<SchemaUtilErrorObject>} array
64 * @param {(item: SchemaUtilErrorObject) => number} fn
65 * @returns {Array<SchemaUtilErrorObject>}
66 */
67
68function filterMax(array, fn) {
69 const evaluatedMax = array.reduce((max, item) => Math.max(max, fn(item)), 0);
70 return array.filter(item => fn(item) === evaluatedMax);
71}
72/**
73 *
74 * @param {Array<SchemaUtilErrorObject>} children
75 * @returns {Array<SchemaUtilErrorObject>}
76 */
77
78
79function filterChildren(children) {
80 let newChildren = children;
81 newChildren = filterMax(newChildren,
82 /**
83 *
84 * @param {SchemaUtilErrorObject} error
85 * @returns {number}
86 */
87 error => error.dataPath ? error.dataPath.length : 0);
88 newChildren = filterMax(newChildren,
89 /**
90 * @param {SchemaUtilErrorObject} error
91 * @returns {number}
92 */
93 error => SPECIFICITY[
94 /** @type {keyof typeof SPECIFICITY} */
95 error.keyword] || 2);
96 return newChildren;
97}
98/**
99 * Find all children errors
100 * @param {Array<SchemaUtilErrorObject>} children
101 * @param {Array<string>} schemaPaths
102 * @return {number} returns index of first child
103 */
104
105
106function findAllChildren(children, schemaPaths) {
107 let i = children.length - 1;
108
109 const predicate =
110 /**
111 * @param {string} schemaPath
112 * @returns {boolean}
113 */
114 schemaPath => children[i].schemaPath.indexOf(schemaPath) !== 0;
115
116 while (i > -1 && !schemaPaths.every(predicate)) {
117 if (children[i].keyword === 'anyOf' || children[i].keyword === 'oneOf') {
118 const refs = extractRefs(children[i]);
119 const childrenStart = findAllChildren(children.slice(0, i), refs.concat(children[i].schemaPath));
120 i = childrenStart - 1;
121 } else {
122 i -= 1;
123 }
124 }
125
126 return i + 1;
127}
128/**
129 * Extracts all refs from schema
130 * @param {SchemaUtilErrorObject} error
131 * @return {Array<string>}
132 */
133
134
135function extractRefs(error) {
136 const {
137 schema
138 } = error;
139
140 if (!Array.isArray(schema)) {
141 return [];
142 }
143
144 return schema.map(({
145 $ref
146 }) => $ref).filter(s => s);
147}
148/**
149 * Groups children by their first level parent (assuming that error is root)
150 * @param {Array<SchemaUtilErrorObject>} children
151 * @return {Array<SchemaUtilErrorObject>}
152 */
153
154
155function groupChildrenByFirstChild(children) {
156 const result = [];
157 let i = children.length - 1;
158
159 while (i > 0) {
160 const child = children[i];
161
162 if (child.keyword === 'anyOf' || child.keyword === 'oneOf') {
163 const refs = extractRefs(child);
164 const childrenStart = findAllChildren(children.slice(0, i), refs.concat(child.schemaPath));
165
166 if (childrenStart !== i) {
167 result.push(Object.assign({}, child, {
168 children: children.slice(childrenStart, i)
169 }));
170 i = childrenStart;
171 } else {
172 result.push(child);
173 }
174 } else {
175 result.push(child);
176 }
177
178 i -= 1;
179 }
180
181 if (i === 0) {
182 result.push(children[i]);
183 }
184
185 return result.reverse();
186}
187/**
188 * @param {string} str
189 * @param {string} prefix
190 * @returns {string}
191 */
192
193
194function indent(str, prefix) {
195 return str.replace(/\n(?!$)/g, `\n${prefix}`);
196}
197/**
198 * @param {Schema} schema
199 * @returns {schema is (Schema & {not: Schema})}
200 */
201
202
203function hasNotInSchema(schema) {
204 return !!schema.not;
205}
206/**
207 * @param {Schema} schema
208 * @return {Schema}
209 */
210
211
212function findFirstTypedSchema(schema) {
213 if (hasNotInSchema(schema)) {
214 return findFirstTypedSchema(schema.not);
215 }
216
217 return schema;
218}
219/**
220 * @param {Schema} schema
221 * @return {boolean}
222 */
223
224
225function canApplyNot(schema) {
226 const typedSchema = findFirstTypedSchema(schema);
227 return likeNumber(typedSchema) || likeInteger(typedSchema) || likeString(typedSchema) || likeNull(typedSchema) || likeBoolean(typedSchema);
228}
229/**
230 * @param {any} maybeObj
231 * @returns {boolean}
232 */
233
234
235function isObject(maybeObj) {
236 return typeof maybeObj === 'object' && maybeObj !== null;
237}
238/**
239 * @param {Schema} schema
240 * @returns {boolean}
241 */
242
243
244function likeNumber(schema) {
245 return schema.type === 'number' || typeof schema.minimum !== 'undefined' || typeof schema.exclusiveMinimum !== 'undefined' || typeof schema.maximum !== 'undefined' || typeof schema.exclusiveMaximum !== 'undefined' || typeof schema.multipleOf !== 'undefined';
246}
247/**
248 * @param {Schema} schema
249 * @returns {boolean}
250 */
251
252
253function likeInteger(schema) {
254 return schema.type === 'integer' || typeof schema.minimum !== 'undefined' || typeof schema.exclusiveMinimum !== 'undefined' || typeof schema.maximum !== 'undefined' || typeof schema.exclusiveMaximum !== 'undefined' || typeof schema.multipleOf !== 'undefined';
255}
256/**
257 * @param {Schema} schema
258 * @returns {boolean}
259 */
260
261
262function likeString(schema) {
263 return schema.type === 'string' || typeof schema.minLength !== 'undefined' || typeof schema.maxLength !== 'undefined' || typeof schema.pattern !== 'undefined' || typeof schema.format !== 'undefined' || typeof schema.formatMinimum !== 'undefined' || typeof schema.formatMaximum !== 'undefined';
264}
265/**
266 * @param {Schema} schema
267 * @returns {boolean}
268 */
269
270
271function likeBoolean(schema) {
272 return schema.type === 'boolean';
273}
274/**
275 * @param {Schema} schema
276 * @returns {boolean}
277 */
278
279
280function likeArray(schema) {
281 return schema.type === 'array' || typeof schema.minItems === 'number' || typeof schema.maxItems === 'number' || typeof schema.uniqueItems !== 'undefined' || typeof schema.items !== 'undefined' || typeof schema.additionalItems !== 'undefined' || typeof schema.contains !== 'undefined';
282}
283/**
284 * @param {Schema & {patternRequired?: Array<string>}} schema
285 * @returns {boolean}
286 */
287
288
289function likeObject(schema) {
290 return schema.type === 'object' || typeof schema.minProperties !== 'undefined' || typeof schema.maxProperties !== 'undefined' || typeof schema.required !== 'undefined' || typeof schema.properties !== 'undefined' || typeof schema.patternProperties !== 'undefined' || typeof schema.additionalProperties !== 'undefined' || typeof schema.dependencies !== 'undefined' || typeof schema.propertyNames !== 'undefined' || typeof schema.patternRequired !== 'undefined';
291}
292/**
293 * @param {Schema} schema
294 * @returns {boolean}
295 */
296
297
298function likeNull(schema) {
299 return schema.type === 'null';
300}
301/**
302 * @param {string} type
303 * @returns {string}
304 */
305
306
307function getArticle(type) {
308 if (/^[aeiou]/i.test(type)) {
309 return 'an';
310 }
311
312 return 'a';
313}
314/**
315 * @param {Schema=} schema
316 * @returns {string}
317 */
318
319
320function getSchemaNonTypes(schema) {
321 if (!schema) {
322 return '';
323 }
324
325 if (!schema.type) {
326 if (likeNumber(schema) || likeInteger(schema)) {
327 return ' | should be any non-number';
328 }
329
330 if (likeString(schema)) {
331 return ' | should be any non-string';
332 }
333
334 if (likeArray(schema)) {
335 return ' | should be any non-array';
336 }
337
338 if (likeObject(schema)) {
339 return ' | should be any non-object';
340 }
341 }
342
343 return '';
344}
345/**
346 * @param {Array<string>} hints
347 * @returns {string}
348 */
349
350
351function formatHints(hints) {
352 return hints.length > 0 ? `(${hints.join(', ')})` : '';
353}
354/**
355 * @param {Schema} schema
356 * @param {boolean} logic
357 * @returns {string[]}
358 */
359
360
361function getHints(schema, logic) {
362 if (likeNumber(schema) || likeInteger(schema)) {
363 return numberHints(schema, logic);
364 } else if (likeString(schema)) {
365 return stringHints(schema, logic);
366 }
367
368 return [];
369}
370
371class ValidationError extends Error {
372 /**
373 * @param {Array<SchemaUtilErrorObject>} errors
374 * @param {Schema} schema
375 * @param {ValidationErrorConfiguration} configuration
376 */
377 constructor(errors, schema, configuration = {}) {
378 super();
379 /** @type {string} */
380
381 this.name = 'ValidationError';
382 /** @type {Array<SchemaUtilErrorObject>} */
383
384 this.errors = errors;
385 /** @type {Schema} */
386
387 this.schema = schema;
388 let headerNameFromSchema;
389 let baseDataPathFromSchema;
390
391 if (schema.title && (!configuration.name || !configuration.baseDataPath)) {
392 const splittedTitleFromSchema = schema.title.match(/^(.+) (.+)$/);
393
394 if (splittedTitleFromSchema) {
395 if (!configuration.name) {
396 [, headerNameFromSchema] = splittedTitleFromSchema;
397 }
398
399 if (!configuration.baseDataPath) {
400 [,, baseDataPathFromSchema] = splittedTitleFromSchema;
401 }
402 }
403 }
404 /** @type {string} */
405
406
407 this.headerName = configuration.name || headerNameFromSchema || 'Object';
408 /** @type {string} */
409
410 this.baseDataPath = configuration.baseDataPath || baseDataPathFromSchema || 'configuration';
411 /** @type {PostFormatter | null} */
412
413 this.postFormatter = configuration.postFormatter || null;
414 const header = `Invalid ${this.baseDataPath} object. ${this.headerName} has been initialized using ${getArticle(this.baseDataPath)} ${this.baseDataPath} object that does not match the API schema.\n`;
415 /** @type {string} */
416
417 this.message = `${header}${this.formatValidationErrors(errors)}`;
418 Error.captureStackTrace(this, this.constructor);
419 }
420 /**
421 * @param {string} path
422 * @returns {Schema}
423 */
424
425
426 getSchemaPart(path) {
427 const newPath = path.split('/');
428 let schemaPart = this.schema;
429
430 for (let i = 1; i < newPath.length; i++) {
431 const inner = schemaPart[
432 /** @type {keyof Schema} */
433 newPath[i]];
434
435 if (!inner) {
436 break;
437 }
438
439 schemaPart = inner;
440 }
441
442 return schemaPart;
443 }
444 /**
445 * @param {Schema} schema
446 * @param {boolean} logic
447 * @param {Array<Object>} prevSchemas
448 * @returns {string}
449 */
450
451
452 formatSchema(schema, logic = true, prevSchemas = []) {
453 let newLogic = logic;
454
455 const formatInnerSchema =
456 /**
457 *
458 * @param {Object} innerSchema
459 * @param {boolean=} addSelf
460 * @returns {string}
461 */
462 (innerSchema, addSelf) => {
463 if (!addSelf) {
464 return this.formatSchema(innerSchema, newLogic, prevSchemas);
465 }
466
467 if (prevSchemas.includes(innerSchema)) {
468 return '(recursive)';
469 }
470
471 return this.formatSchema(innerSchema, newLogic, prevSchemas.concat(schema));
472 };
473
474 if (hasNotInSchema(schema) && !likeObject(schema)) {
475 if (canApplyNot(schema.not)) {
476 newLogic = !logic;
477 return formatInnerSchema(schema.not);
478 }
479
480 const needApplyLogicHere = !schema.not.not;
481 const prefix = logic ? '' : 'non ';
482 newLogic = !logic;
483 return needApplyLogicHere ? prefix + formatInnerSchema(schema.not) : formatInnerSchema(schema.not);
484 }
485
486 if (
487 /** @type {Schema & {instanceof: string | Array<string>}} */
488 schema.instanceof) {
489 const {
490 instanceof: value
491 } =
492 /** @type {Schema & {instanceof: string | Array<string>}} */
493 schema;
494 const values = !Array.isArray(value) ? [value] : value;
495 return values.map(
496 /**
497 * @param {string} item
498 * @returns {string}
499 */
500 item => item === 'Function' ? 'function' : item).join(' | ');
501 }
502
503 if (schema.enum) {
504 return (
505 /** @type {Array<any>} */
506 schema.enum.map(item => JSON.stringify(item)).join(' | ')
507 );
508 }
509
510 if (typeof schema.const !== 'undefined') {
511 return JSON.stringify(schema.const);
512 }
513
514 if (schema.oneOf) {
515 return (
516 /** @type {Array<Schema>} */
517 schema.oneOf.map(item => formatInnerSchema(item, true)).join(' | ')
518 );
519 }
520
521 if (schema.anyOf) {
522 return (
523 /** @type {Array<Schema>} */
524 schema.anyOf.map(item => formatInnerSchema(item, true)).join(' | ')
525 );
526 }
527
528 if (schema.allOf) {
529 return (
530 /** @type {Array<Schema>} */
531 schema.allOf.map(item => formatInnerSchema(item, true)).join(' & ')
532 );
533 }
534
535 if (
536 /** @type {JSONSchema7} */
537 schema.if) {
538 const {
539 if: ifValue,
540 then: thenValue,
541 else: elseValue
542 } =
543 /** @type {JSONSchema7} */
544 schema;
545 return `${ifValue ? `if ${formatInnerSchema(ifValue)}` : ''}${thenValue ? ` then ${formatInnerSchema(thenValue)}` : ''}${elseValue ? ` else ${formatInnerSchema(elseValue)}` : ''}`;
546 }
547
548 if (schema.$ref) {
549 return formatInnerSchema(this.getSchemaPart(schema.$ref), true);
550 }
551
552 if (likeNumber(schema) || likeInteger(schema)) {
553 const [type, ...hints] = getHints(schema, logic);
554 const str = `${type}${hints.length > 0 ? ` ${formatHints(hints)}` : ''}`;
555 return logic ? str : hints.length > 0 ? `non-${type} | ${str}` : `non-${type}`;
556 }
557
558 if (likeString(schema)) {
559 const [type, ...hints] = getHints(schema, logic);
560 const str = `${type}${hints.length > 0 ? ` ${formatHints(hints)}` : ''}`;
561 return logic ? str : str === 'string' ? 'non-string' : `non-string | ${str}`;
562 }
563
564 if (likeBoolean(schema)) {
565 return `${logic ? '' : 'non-'}boolean`;
566 }
567
568 if (likeArray(schema)) {
569 // not logic already applied in formatValidationError
570 newLogic = true;
571 const hints = [];
572
573 if (typeof schema.minItems === 'number') {
574 hints.push(`should not have fewer than ${schema.minItems} item${schema.minItems > 1 ? 's' : ''}`);
575 }
576
577 if (typeof schema.maxItems === 'number') {
578 hints.push(`should not have more than ${schema.maxItems} item${schema.maxItems > 1 ? 's' : ''}`);
579 }
580
581 if (schema.uniqueItems) {
582 hints.push('should not have duplicate items');
583 }
584
585 const hasAdditionalItems = typeof schema.additionalItems === 'undefined' || Boolean(schema.additionalItems);
586 let items = '';
587
588 if (schema.items) {
589 if (Array.isArray(schema.items) && schema.items.length > 0) {
590 items = `${
591 /** @type {Array<Schema>} */
592 schema.items.map(item => formatInnerSchema(item)).join(', ')}`;
593
594 if (hasAdditionalItems) {
595 if (schema.additionalItems && isObject(schema.additionalItems) && Object.keys(schema.additionalItems).length > 0) {
596 hints.push(`additional items should be ${formatInnerSchema(schema.additionalItems)}`);
597 }
598 }
599 } else if (schema.items && Object.keys(schema.items).length > 0) {
600 // "additionalItems" is ignored
601 items = `${formatInnerSchema(schema.items)}`;
602 } else {
603 // Fallback for empty `items` value
604 items = 'any';
605 }
606 } else {
607 // "additionalItems" is ignored
608 items = 'any';
609 }
610
611 if (schema.contains && Object.keys(schema.contains).length > 0) {
612 hints.push(`should contains at least one ${this.formatSchema(schema.contains)} item`);
613 }
614
615 return `[${items}${hasAdditionalItems ? ', ...' : ''}]${hints.length > 0 ? ` (${hints.join(', ')})` : ''}`;
616 }
617
618 if (likeObject(schema)) {
619 // not logic already applied in formatValidationError
620 newLogic = true;
621 const hints = [];
622
623 if (typeof schema.minProperties === 'number') {
624 hints.push(`should not have fewer than ${schema.minProperties} ${schema.minProperties > 1 ? 'properties' : 'property'}`);
625 }
626
627 if (typeof schema.maxProperties === 'number') {
628 hints.push(`should not have more than ${schema.maxProperties} ${schema.minProperties && schema.minProperties > 1 ? 'properties' : 'property'}`);
629 }
630
631 if (schema.patternProperties && Object.keys(schema.patternProperties).length > 0) {
632 const patternProperties = Object.keys(schema.patternProperties);
633 hints.push(`additional property names should match pattern${patternProperties.length > 1 ? 's' : ''} ${patternProperties.map(pattern => JSON.stringify(pattern)).join(' | ')}`);
634 }
635
636 const properties = schema.properties ? Object.keys(schema.properties) : [];
637 const required = schema.required ? schema.required : [];
638 const allProperties = [...new Set(
639 /** @type {Array<string>} */
640 [].concat(required).concat(properties))];
641 const objectStructure = allProperties.map(property => {
642 const isRequired = required.includes(property); // Some properties need quotes, maybe we should add check
643 // Maybe we should output type of property (`foo: string`), but it is looks very unreadable
644
645 return `${property}${isRequired ? '' : '?'}`;
646 }).concat(typeof schema.additionalProperties === 'undefined' || Boolean(schema.additionalProperties) ? schema.additionalProperties && isObject(schema.additionalProperties) ? [`<key>: ${formatInnerSchema(schema.additionalProperties)}`] : ['…'] : []).join(', ');
647 const {
648 dependencies,
649 propertyNames,
650 patternRequired
651 } =
652 /** @type {Schema & {patternRequired?: Array<string>;}} */
653 schema;
654
655 if (dependencies) {
656 Object.keys(dependencies).forEach(dependencyName => {
657 const dependency = dependencies[dependencyName];
658
659 if (Array.isArray(dependency)) {
660 hints.push(`should have ${dependency.length > 1 ? 'properties' : 'property'} ${dependency.map(dep => `'${dep}'`).join(', ')} when property '${dependencyName}' is present`);
661 } else {
662 hints.push(`should be valid according to the schema ${formatInnerSchema(dependency)} when property '${dependencyName}' is present`);
663 }
664 });
665 }
666
667 if (propertyNames && Object.keys(propertyNames).length > 0) {
668 hints.push(`each property name should match format ${JSON.stringify(schema.propertyNames.format)}`);
669 }
670
671 if (patternRequired && patternRequired.length > 0) {
672 hints.push(`should have property matching pattern ${patternRequired.map(
673 /**
674 * @param {string} item
675 * @returns {string}
676 */
677 item => JSON.stringify(item))}`);
678 }
679
680 return `object {${objectStructure ? ` ${objectStructure} ` : ''}}${hints.length > 0 ? ` (${hints.join(', ')})` : ''}`;
681 }
682
683 if (likeNull(schema)) {
684 return `${logic ? '' : 'non-'}null`;
685 }
686
687 if (Array.isArray(schema.type)) {
688 // not logic already applied in formatValidationError
689 return `${schema.type.join(' | ')}`;
690 } // Fallback for unknown keywords
691 // not logic already applied in formatValidationError
692
693 /* istanbul ignore next */
694
695
696 return JSON.stringify(schema, null, 2);
697 }
698 /**
699 * @param {Schema=} schemaPart
700 * @param {(boolean | Array<string>)=} additionalPath
701 * @param {boolean=} needDot
702 * @param {boolean=} logic
703 * @returns {string}
704 */
705
706
707 getSchemaPartText(schemaPart, additionalPath, needDot = false, logic = true) {
708 if (!schemaPart) {
709 return '';
710 }
711
712 if (Array.isArray(additionalPath)) {
713 for (let i = 0; i < additionalPath.length; i++) {
714 /** @type {Schema | undefined} */
715 const inner = schemaPart[
716 /** @type {keyof Schema} */
717 additionalPath[i]];
718
719 if (inner) {
720 // eslint-disable-next-line no-param-reassign
721 schemaPart = inner;
722 } else {
723 break;
724 }
725 }
726 }
727
728 while (schemaPart.$ref) {
729 // eslint-disable-next-line no-param-reassign
730 schemaPart = this.getSchemaPart(schemaPart.$ref);
731 }
732
733 let schemaText = `${this.formatSchema(schemaPart, logic)}${needDot ? '.' : ''}`;
734
735 if (schemaPart.description) {
736 schemaText += `\n-> ${schemaPart.description}`;
737 }
738
739 return schemaText;
740 }
741 /**
742 * @param {Schema=} schemaPart
743 * @returns {string}
744 */
745
746
747 getSchemaPartDescription(schemaPart) {
748 if (!schemaPart) {
749 return '';
750 }
751
752 while (schemaPart.$ref) {
753 // eslint-disable-next-line no-param-reassign
754 schemaPart = this.getSchemaPart(schemaPart.$ref);
755 }
756
757 if (schemaPart.description) {
758 return `\n-> ${schemaPart.description}`;
759 }
760
761 return '';
762 }
763 /**
764 * @param {SchemaUtilErrorObject} error
765 * @returns {string}
766 */
767
768
769 formatValidationError(error) {
770 const {
771 keyword,
772 dataPath: errorDataPath
773 } = error;
774 const dataPath = `${this.baseDataPath}${errorDataPath}`;
775
776 switch (keyword) {
777 case 'type':
778 {
779 const {
780 parentSchema,
781 params
782 } = error; // eslint-disable-next-line default-case
783
784 switch (
785 /** @type {import("ajv").TypeParams} */
786 params.type) {
787 case 'number':
788 return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
789
790 case 'integer':
791 return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
792
793 case 'string':
794 return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
795
796 case 'boolean':
797 return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
798
799 case 'array':
800 return `${dataPath} should be an array:\n${this.getSchemaPartText(parentSchema)}`;
801
802 case 'object':
803 return `${dataPath} should be an object:\n${this.getSchemaPartText(parentSchema)}`;
804
805 case 'null':
806 return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
807
808 default:
809 return `${dataPath} should be:\n${this.getSchemaPartText(parentSchema)}`;
810 }
811 }
812
813 case 'instanceof':
814 {
815 const {
816 parentSchema
817 } = error;
818 return `${dataPath} should be an instance of ${this.getSchemaPartText(parentSchema, false, true)}`;
819 }
820
821 case 'pattern':
822 {
823 const {
824 params,
825 parentSchema
826 } = error;
827 const {
828 pattern
829 } =
830 /** @type {import("ajv").PatternParams} */
831 params;
832 return `${dataPath} should match pattern ${JSON.stringify(pattern)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
833 }
834
835 case 'format':
836 {
837 const {
838 params,
839 parentSchema
840 } = error;
841 const {
842 format
843 } =
844 /** @type {import("ajv").FormatParams} */
845 params;
846 return `${dataPath} should match format ${JSON.stringify(format)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
847 }
848
849 case 'formatMinimum':
850 case 'formatMaximum':
851 {
852 const {
853 params,
854 parentSchema
855 } = error;
856 const {
857 comparison,
858 limit
859 } =
860 /** @type {import("ajv").ComparisonParams} */
861 params;
862 return `${dataPath} should be ${comparison} ${JSON.stringify(limit)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
863 }
864
865 case 'minimum':
866 case 'maximum':
867 case 'exclusiveMinimum':
868 case 'exclusiveMaximum':
869 {
870 const {
871 parentSchema,
872 params
873 } = error;
874 const {
875 comparison,
876 limit
877 } =
878 /** @type {import("ajv").ComparisonParams} */
879 params;
880 const [, ...hints] = getHints(
881 /** @type {Schema} */
882 parentSchema, true);
883
884 if (hints.length === 0) {
885 hints.push(`should be ${comparison} ${limit}`);
886 }
887
888 return `${dataPath} ${hints.join(' ')}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
889 }
890
891 case 'multipleOf':
892 {
893 const {
894 params,
895 parentSchema
896 } = error;
897 const {
898 multipleOf
899 } =
900 /** @type {import("ajv").MultipleOfParams} */
901 params;
902 return `${dataPath} should be multiple of ${multipleOf}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
903 }
904
905 case 'patternRequired':
906 {
907 const {
908 params,
909 parentSchema
910 } = error;
911 const {
912 missingPattern
913 } =
914 /** @type {import("ajv").PatternRequiredParams} */
915 params;
916 return `${dataPath} should have property matching pattern ${JSON.stringify(missingPattern)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
917 }
918
919 case 'minLength':
920 {
921 const {
922 params,
923 parentSchema
924 } = error;
925 const {
926 limit
927 } =
928 /** @type {import("ajv").LimitParams} */
929 params;
930
931 if (limit === 1) {
932 return `${dataPath} should be an non-empty string${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
933 }
934
935 const length = limit - 1;
936 return `${dataPath} should be longer than ${length} character${length > 1 ? 's' : ''}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
937 }
938
939 case 'minItems':
940 {
941 const {
942 params,
943 parentSchema
944 } = error;
945 const {
946 limit
947 } =
948 /** @type {import("ajv").LimitParams} */
949 params;
950
951 if (limit === 1) {
952 return `${dataPath} should be an non-empty array${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
953 }
954
955 return `${dataPath} should not have fewer than ${limit} items${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
956 }
957
958 case 'minProperties':
959 {
960 const {
961 params,
962 parentSchema
963 } = error;
964 const {
965 limit
966 } =
967 /** @type {import("ajv").LimitParams} */
968 params;
969
970 if (limit === 1) {
971 return `${dataPath} should be an non-empty object${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
972 }
973
974 return `${dataPath} should not have fewer than ${limit} properties${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
975 }
976
977 case 'maxLength':
978 {
979 const {
980 params,
981 parentSchema
982 } = error;
983 const {
984 limit
985 } =
986 /** @type {import("ajv").LimitParams} */
987 params;
988 const max = limit + 1;
989 return `${dataPath} should be shorter than ${max} character${max > 1 ? 's' : ''}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
990 }
991
992 case 'maxItems':
993 {
994 const {
995 params,
996 parentSchema
997 } = error;
998 const {
999 limit
1000 } =
1001 /** @type {import("ajv").LimitParams} */
1002 params;
1003 return `${dataPath} should not have more than ${limit} items${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
1004 }
1005
1006 case 'maxProperties':
1007 {
1008 const {
1009 params,
1010 parentSchema
1011 } = error;
1012 const {
1013 limit
1014 } =
1015 /** @type {import("ajv").LimitParams} */
1016 params;
1017 return `${dataPath} should not have more than ${limit} properties${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
1018 }
1019
1020 case 'uniqueItems':
1021 {
1022 const {
1023 params,
1024 parentSchema
1025 } = error;
1026 const {
1027 i
1028 } =
1029 /** @type {import("ajv").UniqueItemsParams} */
1030 params;
1031 return `${dataPath} should not contain the item '${error.data[i]}' twice${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
1032 }
1033
1034 case 'additionalItems':
1035 {
1036 const {
1037 params,
1038 parentSchema
1039 } = error;
1040 const {
1041 limit
1042 } =
1043 /** @type {import("ajv").LimitParams} */
1044 params;
1045 return `${dataPath} should not have more than ${limit} items${getSchemaNonTypes(parentSchema)}. These items are valid:\n${this.getSchemaPartText(parentSchema)}`;
1046 }
1047
1048 case 'contains':
1049 {
1050 const {
1051 parentSchema
1052 } = error;
1053 return `${dataPath} should contains at least one ${this.getSchemaPartText(parentSchema, ['contains'])} item${getSchemaNonTypes(parentSchema)}.`;
1054 }
1055
1056 case 'required':
1057 {
1058 const {
1059 parentSchema,
1060 params
1061 } = error;
1062 const missingProperty =
1063 /** @type {import("ajv").DependenciesParams} */
1064 params.missingProperty.replace(/^\./, '');
1065 const hasProperty = parentSchema && Boolean(
1066 /** @type {Schema} */
1067 parentSchema.properties &&
1068 /** @type {Schema} */
1069 parentSchema.properties[missingProperty]);
1070 return `${dataPath} misses the property '${missingProperty}'${getSchemaNonTypes(parentSchema)}.${hasProperty ? ` Should be:\n${this.getSchemaPartText(parentSchema, ['properties', missingProperty])}` : this.getSchemaPartDescription(parentSchema)}`;
1071 }
1072
1073 case 'additionalProperties':
1074 {
1075 const {
1076 params,
1077 parentSchema
1078 } = error;
1079 const {
1080 additionalProperty
1081 } =
1082 /** @type {import("ajv").AdditionalPropertiesParams} */
1083 params;
1084 return `${dataPath} has an unknown property '${additionalProperty}'${getSchemaNonTypes(parentSchema)}. These properties are valid:\n${this.getSchemaPartText(parentSchema)}`;
1085 }
1086
1087 case 'dependencies':
1088 {
1089 const {
1090 params,
1091 parentSchema
1092 } = error;
1093 const {
1094 property,
1095 deps
1096 } =
1097 /** @type {import("ajv").DependenciesParams} */
1098 params;
1099 const dependencies = deps.split(',').map(
1100 /**
1101 * @param {string} dep
1102 * @returns {string}
1103 */
1104 dep => `'${dep.trim()}'`).join(', ');
1105 return `${dataPath} should have properties ${dependencies} when property '${property}' is present${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
1106 }
1107
1108 case 'propertyNames':
1109 {
1110 const {
1111 params,
1112 parentSchema,
1113 schema
1114 } = error;
1115 const {
1116 propertyName
1117 } =
1118 /** @type {import("ajv").PropertyNamesParams} */
1119 params;
1120 return `${dataPath} property name '${propertyName}' is invalid${getSchemaNonTypes(parentSchema)}. Property names should be match format ${JSON.stringify(schema.format)}.${this.getSchemaPartDescription(parentSchema)}`;
1121 }
1122
1123 case 'enum':
1124 {
1125 const {
1126 parentSchema
1127 } = error;
1128
1129 if (parentSchema &&
1130 /** @type {Schema} */
1131 parentSchema.enum &&
1132 /** @type {Schema} */
1133 parentSchema.enum.length === 1) {
1134 return `${dataPath} should be ${this.getSchemaPartText(parentSchema, false, true)}`;
1135 }
1136
1137 return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}`;
1138 }
1139
1140 case 'const':
1141 {
1142 const {
1143 parentSchema
1144 } = error;
1145 return `${dataPath} should be equal to constant ${this.getSchemaPartText(parentSchema, false, true)}`;
1146 }
1147
1148 case 'not':
1149 {
1150 const postfix = likeObject(
1151 /** @type {Schema} */
1152 error.parentSchema) ? `\n${this.getSchemaPartText(error.parentSchema)}` : '';
1153 const schemaOutput = this.getSchemaPartText(error.schema, false, false, false);
1154
1155 if (canApplyNot(error.schema)) {
1156 return `${dataPath} should be any ${schemaOutput}${postfix}.`;
1157 }
1158
1159 const {
1160 schema,
1161 parentSchema
1162 } = error;
1163 return `${dataPath} should not be ${this.getSchemaPartText(schema, false, true)}${parentSchema && likeObject(parentSchema) ? `\n${this.getSchemaPartText(parentSchema)}` : ''}`;
1164 }
1165
1166 case 'oneOf':
1167 case 'anyOf':
1168 {
1169 const {
1170 parentSchema,
1171 children
1172 } = error;
1173
1174 if (children && children.length > 0) {
1175 if (error.schema.length === 1) {
1176 const lastChild = children[children.length - 1];
1177 const remainingChildren = children.slice(0, children.length - 1);
1178 return this.formatValidationError(Object.assign({}, lastChild, {
1179 children: remainingChildren,
1180 parentSchema: Object.assign({}, parentSchema, lastChild.parentSchema)
1181 }));
1182 }
1183
1184 let filteredChildren = filterChildren(children);
1185
1186 if (filteredChildren.length === 1) {
1187 return this.formatValidationError(filteredChildren[0]);
1188 }
1189
1190 filteredChildren = groupChildrenByFirstChild(filteredChildren);
1191 return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}\nDetails:\n${filteredChildren.map(
1192 /**
1193 * @param {SchemaUtilErrorObject} nestedError
1194 * @returns {string}
1195 */
1196 nestedError => ` * ${indent(this.formatValidationError(nestedError), ' ')}`).join('\n')}`;
1197 }
1198
1199 return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}`;
1200 }
1201
1202 case 'if':
1203 {
1204 const {
1205 params,
1206 parentSchema
1207 } = error;
1208 const {
1209 failingKeyword
1210 } =
1211 /** @type {import("ajv").IfParams} */
1212 params;
1213 return `${dataPath} should match "${failingKeyword}" schema:\n${this.getSchemaPartText(parentSchema, [failingKeyword])}`;
1214 }
1215
1216 case 'absolutePath':
1217 {
1218 const {
1219 message,
1220 parentSchema
1221 } = error;
1222 return `${dataPath}: ${message}${this.getSchemaPartDescription(parentSchema)}`;
1223 }
1224
1225 /* istanbul ignore next */
1226
1227 default:
1228 {
1229 const {
1230 message,
1231 parentSchema
1232 } = error;
1233 const ErrorInJSON = JSON.stringify(error, null, 2); // For `custom`, `false schema`, `$ref` keywords
1234 // Fallback for unknown keywords
1235
1236 return `${dataPath} ${message} (${ErrorInJSON}).\n${this.getSchemaPartText(parentSchema, false)}`;
1237 }
1238 }
1239 }
1240 /**
1241 * @param {Array<SchemaUtilErrorObject>} errors
1242 * @returns {string}
1243 */
1244
1245
1246 formatValidationErrors(errors) {
1247 return errors.map(error => {
1248 let formattedError = this.formatValidationError(error);
1249
1250 if (this.postFormatter) {
1251 formattedError = this.postFormatter(formattedError, error);
1252 }
1253
1254 return ` - ${indent(formattedError, ' ')}`;
1255 }).join('\n');
1256 }
1257
1258}
1259
1260var _default = ValidationError;
1261exports.default = _default;
\No newline at end of file