@develar/schema-utils
Advanced tools
Comparing version 2.1.0 to 2.6.5
105
CHANGELOG.md
@@ -5,2 +5,107 @@ # Changelog | ||
### [2.6.5](https://github.com/webpack/schema-utils/compare/v2.6.4...v2.6.5) (2020-03-11) | ||
### Bug Fixes | ||
* typo ([#88](https://github.com/webpack/schema-utils/issues/88)) ([52fc92b](https://github.com/webpack/schema-utils/commit/52fc92b531b7503b7bbe1bf9b21bfa26dffbc8f5)) | ||
### [2.6.4](https://github.com/webpack/schema-utils/compare/v2.6.3...v2.6.4) (2020-01-17) | ||
### Bug Fixes | ||
* change `initialised` to `initialized` ([#87](https://github.com/webpack/schema-utils/issues/87)) ([70f12d3](https://github.com/webpack/schema-utils/commit/70f12d33a8eaa27249bc9c1a27f886724cf91ea7)) | ||
### [2.6.3](https://github.com/webpack/schema-utils/compare/v2.6.2...v2.6.3) (2020-01-17) | ||
### Bug Fixes | ||
* prefer the `baseDataPath` option from arguments ([#86](https://github.com/webpack/schema-utils/issues/86)) ([e236859](https://github.com/webpack/schema-utils/commit/e236859e85b28e35e1294f86fc1ff596a5031cea)) | ||
### [2.6.2](https://github.com/webpack/schema-utils/compare/v2.6.1...v2.6.2) (2020-01-14) | ||
### Bug Fixes | ||
* better handle Windows absolute paths ([#85](https://github.com/webpack/schema-utils/issues/85)) ([1fa2930](https://github.com/webpack/schema-utils/commit/1fa2930a161e907b9fc53a7233d605910afdb883)) | ||
### [2.6.1](https://github.com/webpack/schema-utils/compare/v2.6.0...v2.6.1) (2019-11-28) | ||
### Bug Fixes | ||
* typescript declarations ([#84](https://github.com/webpack/schema-utils/issues/84)) ([89d55a9](https://github.com/webpack/schema-utils/commit/89d55a9a8edfa6a8ac8b112f226bb3154e260319)) | ||
## [2.6.0](https://github.com/webpack/schema-utils/compare/v2.5.0...v2.6.0) (2019-11-27) | ||
### Features | ||
* support configuration via title ([#81](https://github.com/webpack/schema-utils/issues/81)) ([afddc10](https://github.com/webpack/schema-utils/commit/afddc109f6891cd37a9f1835d50862d119a072bf)) | ||
### Bug Fixes | ||
* typescript definitions ([#70](https://github.com/webpack/schema-utils/issues/70)) ([f38158d](https://github.com/webpack/schema-utils/commit/f38158d6d040e2c701622778ae8122fb26a4f990)) | ||
## [2.5.0](https://github.com/webpack/schema-utils/compare/v2.4.1...v2.5.0) (2019-10-15) | ||
### Bug Fixes | ||
* rework format for maxLength, minLength ([#67](https://github.com/webpack/schema-utils/issues/67)) ([0d12259](https://github.com/webpack/schema-utils/commit/0d12259)) | ||
* support all cases with one number in range ([#64](https://github.com/webpack/schema-utils/issues/64)) ([7fc8069](https://github.com/webpack/schema-utils/commit/7fc8069)) | ||
* typescript definition and export naming ([#69](https://github.com/webpack/schema-utils/issues/69)) ([a435b79](https://github.com/webpack/schema-utils/commit/a435b79)) | ||
### Features | ||
* "smart" numbers range ([62fb107](https://github.com/webpack/schema-utils/commit/62fb107)) | ||
### [2.4.1](https://github.com/webpack/schema-utils/compare/v2.4.0...v2.4.1) (2019-09-27) | ||
### Bug Fixes | ||
* publish definitions ([#58](https://github.com/webpack/schema-utils/issues/58)) ([1885faa](https://github.com/webpack/schema-utils/commit/1885faa)) | ||
## [2.4.0](https://github.com/webpack/schema-utils/compare/v2.3.0...v2.4.0) (2019-09-26) | ||
### Features | ||
* better errors when the `type` keyword doesn't exist ([0988be2](https://github.com/webpack/schema-utils/commit/0988be2)) | ||
* support $data reference ([#56](https://github.com/webpack/schema-utils/issues/56)) ([d2f11d6](https://github.com/webpack/schema-utils/commit/d2f11d6)) | ||
* types definitions ([#52](https://github.com/webpack/schema-utils/issues/52)) ([facb431](https://github.com/webpack/schema-utils/commit/facb431)) | ||
## [2.3.0](https://github.com/webpack/schema-utils/compare/v2.2.0...v2.3.0) (2019-09-26) | ||
### Features | ||
* support `not` keyword ([#53](https://github.com/webpack/schema-utils/issues/53)) ([765f458](https://github.com/webpack/schema-utils/commit/765f458)) | ||
## [2.2.0](https://github.com/webpack/schema-utils/compare/v2.1.0...v2.2.0) (2019-09-02) | ||
### Features | ||
* better error output for `oneOf` and `anyOf` ([#48](https://github.com/webpack/schema-utils/issues/48)) ([#50](https://github.com/webpack/schema-utils/issues/50)) ([332242f](https://github.com/webpack/schema-utils/commit/332242f)) | ||
## [2.1.0](https://github.com/webpack-contrib/schema-utils/compare/v2.0.1...v2.1.0) (2019-08-07) | ||
### Bug Fixes | ||
* throw error on sparse arrays ([#47](https://github.com/webpack-contrib/schema-utils/issues/47)) ([b85ac38](https://github.com/webpack-contrib/schema-utils/commit/b85ac38)) | ||
### Features | ||
* export `ValidateError` ([#46](https://github.com/webpack-contrib/schema-utils/issues/46)) ([ff781d7](https://github.com/webpack-contrib/schema-utils/commit/ff781d7)) | ||
### [2.0.1](https://github.com/webpack-contrib/schema-utils/compare/v2.0.0...v2.0.1) (2019-07-18) | ||
@@ -7,0 +112,0 @@ |
@@ -8,3 +8,14 @@ "use strict"; | ||
function errorMessage(schema, data, message) { | ||
/** @typedef {import("ajv").Ajv} Ajv */ | ||
/** @typedef {import("../validate").SchemaUtilErrorObject} SchemaUtilErrorObject */ | ||
/** | ||
* | ||
* @param {string} data | ||
* @param {object} schema | ||
* @param {string} message | ||
* @returns {object} // Todo `returns` should be `SchemaUtilErrorObject` | ||
*/ | ||
function errorMessage(message, schema, data) { | ||
return { | ||
@@ -19,37 +30,64 @@ keyword: 'absolutePath', | ||
} | ||
/** | ||
* @param {boolean} shouldBeAbsolute | ||
* @param {object} schema | ||
* @param {string} data | ||
* @returns {object} | ||
*/ | ||
function getErrorFor(shouldBeAbsolute, data, schema) { | ||
function getErrorFor(shouldBeAbsolute, schema, data) { | ||
const message = shouldBeAbsolute ? `The provided value ${JSON.stringify(data)} is not an absolute path!` : `A relative path is expected. However, the provided value ${JSON.stringify(data)} is an absolute path!`; | ||
return errorMessage(schema, data, message); | ||
return errorMessage(message, schema, data); | ||
} | ||
/** | ||
* | ||
* @param {Ajv} ajv | ||
* @returns {Ajv} | ||
*/ | ||
var _default = ajv => ajv.addKeyword('absolutePath', { | ||
errors: true, | ||
type: 'string', | ||
compile(expected, schema) { | ||
function callback(data) { | ||
let passes = true; | ||
const isExclamationMarkPresent = data.includes('!'); | ||
const isCorrectAbsoluteOrRelativePath = expected === /^(?:[A-Za-z]:\\|\/)/.test(data); | ||
function addAbsolutePathKeyword(ajv) { | ||
ajv.addKeyword('absolutePath', { | ||
errors: true, | ||
type: 'string', | ||
if (isExclamationMarkPresent) { | ||
callback.errors = [errorMessage(schema, data, `The provided value ${JSON.stringify(data)} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.`)]; | ||
passes = false; | ||
} | ||
compile(schema, parentSchema) { | ||
/** | ||
* @param {any} data | ||
* @returns {boolean} | ||
*/ | ||
function callback(data) { | ||
let passes = true; | ||
const isExclamationMarkPresent = data.includes('!'); | ||
if (!isCorrectAbsoluteOrRelativePath) { | ||
callback.errors = [getErrorFor(expected, data, schema)]; | ||
passes = false; | ||
if (isExclamationMarkPresent) { | ||
callback.errors = [errorMessage(`The provided value ${JSON.stringify(data)} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.`, parentSchema, data)]; | ||
passes = false; | ||
} // ?:[A-Za-z]:\\ - Windows absolute path | ||
// \\\\ - Windows network absolute path | ||
// \/ - Unix-like OS absolute path | ||
const isCorrectAbsolutePath = schema === /^(?:[A-Za-z]:(\\|\/)|\\\\|\/)/.test(data); | ||
if (!isCorrectAbsolutePath) { | ||
callback.errors = [getErrorFor(schema, parentSchema, data)]; | ||
passes = false; | ||
} | ||
return passes; | ||
} | ||
/** @type {null | Array<SchemaUtilErrorObject>}*/ | ||
return passes; | ||
callback.errors = []; | ||
return callback; | ||
} | ||
callback.errors = []; | ||
return callback; | ||
} | ||
}); | ||
return ajv; | ||
} | ||
}); | ||
var _default = addAbsolutePathKeyword; | ||
exports.default = _default; |
@@ -18,5 +18,31 @@ "use strict"; | ||
/** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */ | ||
/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */ | ||
/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */ | ||
/** @typedef {import("ajv").ErrorObject} ErrorObject */ | ||
/** @typedef {(JSONSchema4 | JSONSchema6 | JSONSchema7)} Schema */ | ||
/** @typedef {ErrorObject & { children?: Array<ErrorObject>}} SchemaUtilErrorObject */ | ||
/** | ||
* @callback PostFormatter | ||
* @param {string} formattedError | ||
* @param {SchemaUtilErrorObject} error | ||
* @returns {string} | ||
*/ | ||
/** | ||
* @typedef {Object} ValidationErrorConfiguration | ||
* @property {string=} name | ||
* @property {string=} baseDataPath | ||
* @property {PostFormatter=} postFormatter | ||
*/ | ||
const ajv = new _ajv.default({ | ||
allErrors: true, | ||
verbose: true, | ||
$data: true, | ||
coerceTypes: true | ||
@@ -27,10 +53,20 @@ }); | ||
(0, _absolutePath.default)(ajv); | ||
/** | ||
* @param {Schema} schema | ||
* @param {Array<object> | object} options | ||
* @param {ValidationErrorConfiguration=} configuration | ||
* @returns {void} | ||
*/ | ||
function validate(schema, options, configuration = {}) { | ||
function validate(schema, options, configuration) { | ||
let errors = []; | ||
if (Array.isArray(options)) { | ||
errors = options.map(nestedOptions => validateObject(schema, nestedOptions)); | ||
errors = Array.from(options).map(nestedOptions => validateObject(schema, nestedOptions)); | ||
errors.forEach((list, idx) => { | ||
const applyPrefix = error => { | ||
const applyPrefix = | ||
/** | ||
* @param {SchemaUtilErrorObject} error | ||
*/ | ||
error => { | ||
// eslint-disable-next-line no-param-reassign | ||
@@ -54,19 +90,38 @@ error.dataPath = `[${idx}]${error.dataPath}`; | ||
} | ||
return errors; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @param {Array<object> | object} options | ||
* @returns {Array<SchemaUtilErrorObject>} | ||
*/ | ||
function validateObject(schema, options) { | ||
const compiledSchema = ajv.compile(schema); | ||
const valid = compiledSchema(options); | ||
if (!compiledSchema.errors) { | ||
return []; | ||
} | ||
return valid ? [] : filterErrors(compiledSchema.errors); | ||
} | ||
/** | ||
* @param {Array<ErrorObject>} errors | ||
* @returns {Array<SchemaUtilErrorObject>} | ||
*/ | ||
function filterErrors(errors) { | ||
/** @type {Array<SchemaUtilErrorObject>} */ | ||
let newErrors = []; | ||
for (const error of errors) { | ||
for (const error of | ||
/** @type {Array<SchemaUtilErrorObject>} */ | ||
errors) { | ||
const { | ||
dataPath | ||
} = error; | ||
/** @type {Array<SchemaUtilErrorObject>} */ | ||
let children = []; | ||
@@ -96,5 +151,8 @@ newErrors = newErrors.filter(oldError => { | ||
return newErrors; | ||
} | ||
} // TODO change after resolve https://github.com/microsoft/TypeScript/issues/34994 | ||
validate.ValidationError = _ValidationError.default; | ||
validate.ValidateError = _ValidationError.default; | ||
var _default = validate; | ||
exports.default = _default; |
@@ -7,2 +7,19 @@ "use strict"; | ||
exports.default = void 0; | ||
const Range = require('./util/Range'); | ||
/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */ | ||
/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */ | ||
/** @typedef {import("./validate").Schema} Schema */ | ||
/** @typedef {import("./validate").ValidationErrorConfiguration} ValidationErrorConfiguration */ | ||
/** @typedef {import("./validate").PostFormatter} PostFormatter */ | ||
/** @typedef {import("./validate").SchemaUtilErrorObject} SchemaUtilErrorObject */ | ||
/** @enum {number} */ | ||
const SPECIFICITY = { | ||
@@ -42,2 +59,8 @@ type: 1, | ||
}; | ||
/** | ||
* | ||
* @param {Array<SchemaUtilErrorObject>} array | ||
* @param {(item: SchemaUtilErrorObject) => number} fn | ||
* @returns {Array<SchemaUtilErrorObject>} | ||
*/ | ||
@@ -48,46 +71,205 @@ function filterMax(array, fn) { | ||
} | ||
/** | ||
* | ||
* @param {Array<SchemaUtilErrorObject>} children | ||
* @returns {Array<SchemaUtilErrorObject>} | ||
*/ | ||
function filterChildren(children) { | ||
let newChildren = children; | ||
newChildren = filterMax(newChildren, error => error.dataPath ? error.dataPath.length : 0); | ||
newChildren = filterMax(newChildren, error => SPECIFICITY[error.keyword] || 2); | ||
newChildren = filterMax(newChildren, | ||
/** | ||
* | ||
* @param {SchemaUtilErrorObject} error | ||
* @returns {number} | ||
*/ | ||
error => error.dataPath ? error.dataPath.length : 0); | ||
newChildren = filterMax(newChildren, | ||
/** | ||
* @param {SchemaUtilErrorObject} error | ||
* @returns {number} | ||
*/ | ||
error => SPECIFICITY[ | ||
/** @type {keyof typeof SPECIFICITY} */ | ||
error.keyword] || 2); | ||
return newChildren; | ||
} | ||
/** | ||
* Find all children errors | ||
* @param {Array<SchemaUtilErrorObject>} children | ||
* @param {Array<string>} schemaPaths | ||
* @return {number} returns index of first child | ||
*/ | ||
function findAllChildren(children, schemaPaths) { | ||
let i = children.length - 1; | ||
const predicate = | ||
/** | ||
* @param {string} schemaPath | ||
* @returns {boolean} | ||
*/ | ||
schemaPath => children[i].schemaPath.indexOf(schemaPath) !== 0; | ||
while (i > -1 && !schemaPaths.every(predicate)) { | ||
if (children[i].keyword === 'anyOf' || children[i].keyword === 'oneOf') { | ||
const refs = extractRefs(children[i]); | ||
const childrenStart = findAllChildren(children.slice(0, i), refs.concat(children[i].schemaPath)); | ||
i = childrenStart - 1; | ||
} else { | ||
i -= 1; | ||
} | ||
} | ||
return i + 1; | ||
} | ||
/** | ||
* Extracts all refs from schema | ||
* @param {SchemaUtilErrorObject} error | ||
* @return {Array<string>} | ||
*/ | ||
function extractRefs(error) { | ||
const { | ||
schema | ||
} = error; | ||
if (!Array.isArray(schema)) { | ||
return []; | ||
} | ||
return schema.map(({ | ||
$ref | ||
}) => $ref).filter(s => s); | ||
} | ||
/** | ||
* Groups children by their first level parent (assuming that error is root) | ||
* @param {Array<SchemaUtilErrorObject>} children | ||
* @return {Array<SchemaUtilErrorObject>} | ||
*/ | ||
function groupChildrenByFirstChild(children) { | ||
const result = []; | ||
let i = children.length - 1; | ||
while (i > 0) { | ||
const child = children[i]; | ||
if (child.keyword === 'anyOf' || child.keyword === 'oneOf') { | ||
const refs = extractRefs(child); | ||
const childrenStart = findAllChildren(children.slice(0, i), refs.concat(child.schemaPath)); | ||
if (childrenStart !== i) { | ||
result.push(Object.assign({}, child, { | ||
children: children.slice(childrenStart, i) | ||
})); | ||
i = childrenStart; | ||
} else { | ||
result.push(child); | ||
} | ||
} else { | ||
result.push(child); | ||
} | ||
i -= 1; | ||
} | ||
if (i === 0) { | ||
result.push(children[i]); | ||
} | ||
return result.reverse(); | ||
} | ||
/** | ||
* @param {string} str | ||
* @param {string} prefix | ||
* @returns {string} | ||
*/ | ||
function indent(str, prefix) { | ||
return str.replace(/\n(?!$)/g, `\n${prefix}`); | ||
} | ||
/** | ||
* @param {any} maybeObj | ||
* @returns {boolean} | ||
*/ | ||
function isObject(maybyObj) { | ||
return typeof maybyObj === 'object' && maybyObj !== null; | ||
function isObject(maybeObj) { | ||
return typeof maybeObj === 'object' && maybeObj !== null; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeNumber(schema) { | ||
return schema.type === 'number' || typeof schema.minimum !== 'undefined' || typeof schema.exclusiveMinimum !== 'undefined' || typeof schema.maximum !== 'undefined' || typeof schema.exclusiveMaximum !== 'undefined' || typeof schema.multipleOf !== 'undefined'; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeInteger(schema) { | ||
return schema.type === 'integer' || typeof schema.minimum !== 'undefined' || typeof schema.exclusiveMinimum !== 'undefined' || typeof schema.maximum !== 'undefined' || typeof schema.exclusiveMaximum !== 'undefined' || typeof schema.multipleOf !== 'undefined'; | ||
} | ||
/** | ||
* @param {Schema & {formatMinimum?: string; formatMaximum?: string;}} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeString(schema) { | ||
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'; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeBoolean(schema) { | ||
return schema.type === 'boolean'; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeArray(schema) { | ||
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'; | ||
} | ||
/** | ||
* @param {Schema & {patternRequired?: Array<string>}} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeObject(schema) { | ||
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'; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @returns {boolean} | ||
*/ | ||
function likeNull(schema) { | ||
return schema.type === 'null'; | ||
} | ||
/** | ||
* @param {string} type | ||
* @returns {string} | ||
*/ | ||
function getArticle(type) { | ||
@@ -100,17 +282,153 @@ if (/^[aeiou]/i.test(type)) { | ||
} | ||
/** | ||
* @param {Schema=} schema | ||
* @returns {string} | ||
*/ | ||
function getSchemaNonTypes(schema) { | ||
if (!schema) { | ||
return ''; | ||
} | ||
if (!schema.type) { | ||
if (likeNumber(schema) || likeInteger(schema)) { | ||
return ' | should be any non-number'; | ||
} | ||
if (likeString(schema)) { | ||
return ' | should be any non-string'; | ||
} | ||
if (likeArray(schema)) { | ||
return ' | should be any non-array'; | ||
} | ||
if (likeObject(schema)) { | ||
return ' | should be any non-object'; | ||
} | ||
} | ||
return ''; | ||
} | ||
/** | ||
* @param {Schema=} schema | ||
* @returns {Array<string>} | ||
*/ | ||
function numberHints(schema) { | ||
if (!schema) { | ||
return []; | ||
} | ||
const hints = []; | ||
const range = new Range(); | ||
if (typeof schema.minimum === 'number') { | ||
range.left(schema.minimum); | ||
} | ||
if (typeof schema.exclusiveMinimum === 'number') { | ||
range.left(schema.exclusiveMinimum, true); | ||
} | ||
if (typeof schema.maximum === 'number') { | ||
range.right(schema.maximum); | ||
} | ||
if (typeof schema.exclusiveMaximum === 'number') { | ||
range.right(schema.exclusiveMaximum, true); | ||
} | ||
const rangeFormat = range.format(); | ||
if (rangeFormat) { | ||
hints.push(rangeFormat); | ||
} | ||
if (typeof schema.multipleOf === 'number') { | ||
hints.push(`should be multiple of ${schema.multipleOf}`); | ||
} | ||
return hints; | ||
} | ||
/** | ||
* @param {Array<string>} hints | ||
* @returns {string} | ||
*/ | ||
function formatHints(hints) { | ||
return hints.length > 0 ? `(${hints.join(', ')})` : ''; | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @returns {string} | ||
*/ | ||
function getHints(schema) { | ||
if (likeNumber(schema) || likeInteger(schema)) { | ||
return formatHints(numberHints(schema)); | ||
} | ||
return ''; | ||
} | ||
class ValidationError extends Error { | ||
/** | ||
* @param {Array<SchemaUtilErrorObject>} errors | ||
* @param {Schema} schema | ||
* @param {ValidationErrorConfiguration} configuration | ||
*/ | ||
constructor(errors, schema, configuration = {}) { | ||
super(); | ||
/** @type {string} */ | ||
this.name = 'ValidationError'; | ||
/** @type {Array<SchemaUtilErrorObject>} */ | ||
this.errors = errors; | ||
/** @type {Schema} */ | ||
this.schema = schema; | ||
this.headerName = configuration.name || 'Object'; | ||
this.baseDataPath = configuration.baseDataPath || 'configuration'; | ||
let headerNameFromSchema; | ||
let baseDataPathFromSchema; | ||
if (schema.title && (!configuration.name || !configuration.baseDataPath)) { | ||
const splittedTitleFromSchema = schema.title.match(/^(.+) (.+)$/); | ||
if (splittedTitleFromSchema) { | ||
if (!configuration.name) { | ||
[, headerNameFromSchema] = splittedTitleFromSchema; | ||
} | ||
if (!configuration.baseDataPath) { | ||
[,, baseDataPathFromSchema] = splittedTitleFromSchema; | ||
} | ||
} | ||
} | ||
/** @type {string} */ | ||
this.headerName = configuration.name || headerNameFromSchema || 'Object'; | ||
/** @type {string} */ | ||
this.baseDataPath = configuration.baseDataPath || baseDataPathFromSchema || 'configuration'; | ||
/** @type {PostFormatter | null} */ | ||
this.postFormatter = configuration.postFormatter || null; | ||
const header = `Invalid ${this.baseDataPath} object. ${this.headerName} has been initialised using ${getArticle(this.baseDataPath)} ${this.baseDataPath} object that does not match the API schema.\n`; | ||
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`; | ||
/** @type {string} */ | ||
this.message = `${header}${this.formatValidationErrors(errors)}`; | ||
Error.captureStackTrace(this, this.constructor); | ||
} | ||
/** | ||
* @param {string} path | ||
* @returns {Schema} | ||
*/ | ||
getSchemaPart(path) { | ||
@@ -121,3 +439,5 @@ const newPath = path.split('/'); | ||
for (let i = 1; i < newPath.length; i++) { | ||
const inner = schemaPart[newPath[i]]; | ||
const inner = schemaPart[ | ||
/** @type {keyof Schema} */ | ||
newPath[i]]; | ||
@@ -133,5 +453,18 @@ if (!inner) { | ||
} | ||
/** | ||
* @param {Schema} schema | ||
* @param {Array<Object>} prevSchemas | ||
* @returns {string} | ||
*/ | ||
formatSchema(schema, prevSchemas = []) { | ||
const formatInnerSchema = (innerSchema, addSelf) => { | ||
const formatInnerSchema = | ||
/** | ||
* | ||
* @param {Object} innerSchema | ||
* @param {boolean=} addSelf | ||
* @returns {string} | ||
*/ | ||
(innerSchema, addSelf) => { | ||
if (!addSelf) { | ||
@@ -146,15 +479,30 @@ return this.formatSchema(innerSchema, prevSchemas); | ||
return this.formatSchema(innerSchema, prevSchemas.concat(schema)); | ||
}; // eslint-disable-next-line default-case | ||
}; | ||
if (schema.not && !likeObject(schema)) { | ||
return `non ${formatInnerSchema(schema.not)}`; | ||
} | ||
switch (schema.instanceof) { | ||
case 'Function': | ||
return 'function'; | ||
case 'RegExp': | ||
return 'RegExp'; | ||
if ( | ||
/** @type {Schema & {instanceof: string | Array<string>}} */ | ||
schema.instanceof) { | ||
const { | ||
instanceof: value | ||
} = | ||
/** @type {Schema & {instanceof: string | Array<string>}} */ | ||
schema; | ||
const values = !Array.isArray(value) ? [value] : value; | ||
return values.map( | ||
/** | ||
* @param {string} item | ||
* @returns {string} | ||
*/ | ||
item => item === 'Function' ? 'function' : item).join(' | '); | ||
} | ||
if (schema.enum) { | ||
return schema.enum.map(item => JSON.stringify(item)).join(' | '); | ||
return ( | ||
/** @type {Array<any>} */ | ||
schema.enum.map(item => JSON.stringify(item)).join(' | ') | ||
); | ||
} | ||
@@ -167,15 +515,33 @@ | ||
if (schema.oneOf) { | ||
return schema.oneOf.map(formatInnerSchema).join(' | '); | ||
return ( | ||
/** @type {Array<Schema>} */ | ||
schema.oneOf.map(item => formatInnerSchema(item, true)).join(' | ') | ||
); | ||
} | ||
if (schema.anyOf) { | ||
return schema.anyOf.map(formatInnerSchema).join(' | '); | ||
return ( | ||
/** @type {Array<Schema>} */ | ||
schema.anyOf.map(item => formatInnerSchema(item, true)).join(' | ') | ||
); | ||
} | ||
if (schema.allOf) { | ||
return schema.allOf.map(formatInnerSchema).join(' & '); | ||
return ( | ||
/** @type {Array<Schema>} */ | ||
schema.allOf.map(item => formatInnerSchema(item, true)).join(' & ') | ||
); | ||
} | ||
if (schema.if) { | ||
return `if ${formatInnerSchema(schema.if)} then ${formatInnerSchema(schema.then)}${schema.else ? ` else ${formatInnerSchema(schema.else)}` : ''}`; | ||
if ( | ||
/** @type {JSONSchema7} */ | ||
schema.if) { | ||
const { | ||
if: ifValue, | ||
then: thenValue, | ||
else: elseValue | ||
} = | ||
/** @type {JSONSchema7} */ | ||
schema; | ||
return `${ifValue ? `if ${formatInnerSchema(ifValue)}` : ''}${thenValue ? ` then ${formatInnerSchema(thenValue)}` : ''}${elseValue ? ` else ${formatInnerSchema(elseValue)}` : ''}`; | ||
} | ||
@@ -188,26 +554,5 @@ | ||
if (likeNumber(schema) || likeInteger(schema)) { | ||
const hints = []; | ||
if (typeof schema.minimum === 'number') { | ||
hints.push(`should be >= ${schema.minimum}`); | ||
} | ||
if (typeof schema.exclusiveMinimum === 'number') { | ||
hints.push(`should be > ${schema.exclusiveMinimum}`); | ||
} | ||
if (typeof schema.maximum === 'number') { | ||
hints.push(`should be <= ${schema.maximum}`); | ||
} | ||
if (typeof schema.exclusiveMaximum === 'number') { | ||
hints.push(`should be > ${schema.exclusiveMaximum}`); | ||
} | ||
if (typeof schema.multipleOf === 'number') { | ||
hints.push(`should be multiple of ${schema.multipleOf}`); | ||
} | ||
const type = schema.type === 'integer' ? 'integer' : 'number'; | ||
return `${type}${hints.length > 0 ? ` (${hints.join(', ')})` : ''}`; | ||
const hints = getHints(schema); | ||
return `${type}${hints.length > 0 ? ` ${hints}` : ''}`; | ||
} | ||
@@ -222,4 +567,6 @@ | ||
type = 'non-empty string'; | ||
} else { | ||
hints.push(`should not be shorter than ${schema.minLength} characters`); | ||
} else if (schema.minLength !== 0) { | ||
/* if min length === 0 it does not make hint for user */ | ||
const length = schema.minLength - 1; | ||
hints.push(`should be longer than ${length} character${length > 1 ? 's' : ''}`); | ||
} | ||
@@ -229,3 +576,7 @@ } | ||
if (typeof schema.maxLength === 'number') { | ||
hints.push(`should not be longer than ${schema.maxLength} character${schema.maxLength > 1 ? 's' : ''}`); | ||
if (schema.maxLength === 0) { | ||
type = 'empty string'; | ||
} else { | ||
hints.push(`should be shorter than ${schema.maxLength + 1} characters`); | ||
} | ||
} | ||
@@ -241,8 +592,24 @@ | ||
if (schema.formatMinimum) { | ||
hints.push(`should be ${schema.formatExclusiveMinimum ? '>' : '>='} ${JSON.stringify(schema.formatMinimum)}`); | ||
if ( | ||
/** @type {Schema & {formatMinimum?: string; formatExclusiveMinimum?: boolean;}} */ | ||
schema.formatMinimum) { | ||
const { | ||
formatExclusiveMinimum, | ||
formatMinimum | ||
} = | ||
/** @type {Schema & {formatMinimum?: string; formatExclusiveMinimum?: boolean;}} */ | ||
schema; | ||
hints.push(`should be ${formatExclusiveMinimum ? '>' : '>='} ${JSON.stringify(formatMinimum)}`); | ||
} | ||
if (schema.formatMaximum) { | ||
hints.push(`should be ${schema.formatExclusiveMaximum ? '<' : '<='} ${JSON.stringify(schema.formatMaximum)}`); | ||
if ( | ||
/** @type {Schema & {formatMaximum?: string; formatExclusiveMaximum?: boolean;}} */ | ||
schema.formatMaximum) { | ||
const { | ||
formatExclusiveMaximum, | ||
formatMaximum | ||
} = | ||
/** @type {Schema & {formatMaximum?: string; formatExclusiveMaximum?: boolean;}} */ | ||
schema; | ||
hints.push(`should be ${formatExclusiveMaximum ? '<' : '<='} ${JSON.stringify(formatMaximum)}`); | ||
} | ||
@@ -277,6 +644,8 @@ | ||
if (Array.isArray(schema.items) && schema.items.length > 0) { | ||
items = `${schema.items.map(formatInnerSchema).join(', ')}`; | ||
items = `${ | ||
/** @type {Array<Schema>} */ | ||
schema.items.map(item => formatInnerSchema(item)).join(', ')}`; | ||
if (hasAdditionalItems) { | ||
if (isObject(schema.additionalItems) && Object.keys(schema.additionalItems).length > 0) { | ||
if (schema.additionalItems && isObject(schema.additionalItems) && Object.keys(schema.additionalItems).length > 0) { | ||
hints.push(`additional items should be ${formatInnerSchema(schema.additionalItems)}`); | ||
@@ -312,3 +681,3 @@ } | ||
if (typeof schema.maxProperties === 'number') { | ||
hints.push(`should not have more than ${schema.maxProperties} ${schema.minProperties > 1 ? 'properties' : 'property'}`); | ||
hints.push(`should not have more than ${schema.maxProperties} ${schema.minProperties && schema.minProperties > 1 ? 'properties' : 'property'}`); | ||
} | ||
@@ -323,4 +692,5 @@ | ||
const required = schema.required ? schema.required : []; | ||
const allProperties = [...new Set([].concat(required).concat(properties))]; | ||
const hasAdditionalProperties = typeof schema.additionalProperties === 'undefined' || Boolean(schema.additionalProperties); | ||
const allProperties = [...new Set( | ||
/** @type {Array<string>} */ | ||
[].concat(required).concat(properties))]; | ||
const objectStructure = allProperties.map(property => { | ||
@@ -331,7 +701,14 @@ const isRequired = required.includes(property); // Some properties need quotes, maybe we should add check | ||
return `${property}${isRequired ? '' : '?'}`; | ||
}).concat(hasAdditionalProperties ? isObject(schema.additionalProperties) ? [`<key>: ${formatInnerSchema(schema.additionalProperties)}`] : ['…'] : []).join(', '); | ||
}).concat(typeof schema.additionalProperties === 'undefined' || Boolean(schema.additionalProperties) ? schema.additionalProperties && isObject(schema.additionalProperties) ? [`<key>: ${formatInnerSchema(schema.additionalProperties)}`] : ['…'] : []).join(', '); | ||
const { | ||
dependencies, | ||
propertyNames, | ||
patternRequired | ||
} = | ||
/** @type {Schema & {patternRequired?: Array<string>;}} */ | ||
schema; | ||
if (schema.dependencies) { | ||
Object.keys(schema.dependencies).forEach(dependencyName => { | ||
const dependency = schema.dependencies[dependencyName]; | ||
if (dependencies) { | ||
Object.keys(dependencies).forEach(dependencyName => { | ||
const dependency = dependencies[dependencyName]; | ||
@@ -346,8 +723,13 @@ if (Array.isArray(dependency)) { | ||
if (schema.propertyNames && Object.keys(schema.propertyNames).length > 0) { | ||
if (propertyNames && Object.keys(propertyNames).length > 0) { | ||
hints.push(`each property name should match format ${JSON.stringify(schema.propertyNames.format)}`); | ||
} | ||
if (schema.patternRequired && schema.patternRequired.length > 0) { | ||
hints.push(`should have property matching pattern ${schema.patternRequired.map(item => JSON.stringify(item))}`); | ||
if (patternRequired && patternRequired.length > 0) { | ||
hints.push(`should have property matching pattern ${patternRequired.map( | ||
/** | ||
* @param {string} item | ||
* @returns {string} | ||
*/ | ||
item => JSON.stringify(item))}`); | ||
} | ||
@@ -363,3 +745,3 @@ | ||
if (Array.isArray(schema.type)) { | ||
return `${schema.type.map(item => item).join(' | ')}`; | ||
return `${schema.type.join(' | ')}`; | ||
} // Fallback for unknown keywords | ||
@@ -372,7 +754,21 @@ | ||
} | ||
/** | ||
* @param {Schema=} schemaPart | ||
* @param {(boolean | Array<string>)=} additionalPath | ||
* @param {boolean=} needDot | ||
* @returns {string} | ||
*/ | ||
getSchemaPartText(schemaPart, additionalPath, needDot = false) { | ||
if (additionalPath) { | ||
if (!schemaPart) { | ||
return ''; | ||
} | ||
if (Array.isArray(additionalPath)) { | ||
for (let i = 0; i < additionalPath.length; i++) { | ||
const inner = schemaPart[additionalPath[i]]; | ||
/** @type {Schema | undefined} */ | ||
const inner = schemaPart[ | ||
/** @type {keyof Schema} */ | ||
additionalPath[i]]; | ||
@@ -401,4 +797,13 @@ if (inner) { | ||
} | ||
/** | ||
* @param {Schema=} schemaPart | ||
* @returns {string} | ||
*/ | ||
getSchemaPartDescription(schemaPart) { | ||
if (!schemaPart) { | ||
return ''; | ||
} | ||
while (schemaPart.$ref) { | ||
@@ -415,47 +820,103 @@ // eslint-disable-next-line no-param-reassign | ||
} | ||
/** | ||
* @param {SchemaUtilErrorObject} error | ||
* @returns {string} | ||
*/ | ||
formatValidationError(error) { | ||
const dataPath = `${this.baseDataPath}${error.dataPath}`; | ||
const { | ||
keyword, | ||
dataPath: errorDataPath | ||
} = error; | ||
const dataPath = `${this.baseDataPath}${errorDataPath}`; | ||
switch (error.keyword) { | ||
switch (keyword) { | ||
case 'type': | ||
// eslint-disable-next-line default-case | ||
switch (error.params.type) { | ||
case 'number': | ||
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`; | ||
{ | ||
const { | ||
parentSchema, | ||
params | ||
} = error; // eslint-disable-next-line default-case | ||
case 'integer': | ||
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`; | ||
switch ( | ||
/** @type {import("ajv").TypeParams} */ | ||
params.type) { | ||
case 'number': | ||
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`; | ||
case 'string': | ||
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`; | ||
case 'integer': | ||
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`; | ||
case 'boolean': | ||
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`; | ||
case 'string': | ||
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`; | ||
case 'array': | ||
return `${dataPath} should be an array:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
case 'boolean': | ||
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`; | ||
case 'object': | ||
return `${dataPath} should be an object:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
case 'array': | ||
return `${dataPath} should be an array:\n${this.getSchemaPartText(parentSchema)}`; | ||
case 'null': | ||
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`; | ||
case 'object': | ||
return `${dataPath} should be an object:\n${this.getSchemaPartText(parentSchema)}`; | ||
default: | ||
return `${dataPath} should be:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
case 'null': | ||
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`; | ||
default: | ||
return `${dataPath} should be:\n${this.getSchemaPartText(parentSchema)}`; | ||
} | ||
} | ||
case 'instanceof': | ||
return `${dataPath} should be an instance of ${this.getSchemaPartText(error.parentSchema)}.`; | ||
{ | ||
const { | ||
parentSchema | ||
} = error; | ||
return `${dataPath} should be an instance of ${this.getSchemaPartText(parentSchema)}.`; | ||
} | ||
case 'pattern': | ||
return `${dataPath} should match pattern ${JSON.stringify(error.params.pattern)}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
pattern | ||
} = | ||
/** @type {import("ajv").PatternParams} */ | ||
params; | ||
return `${dataPath} should match pattern ${JSON.stringify(pattern)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'format': | ||
return `${dataPath} should match format ${JSON.stringify(error.params.format)}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
format | ||
} = | ||
/** @type {import("ajv").FormatParams} */ | ||
params; | ||
return `${dataPath} should match format ${JSON.stringify(format)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'formatMinimum': | ||
case 'formatMaximum': | ||
return `${dataPath} should be ${error.params.comparison} ${JSON.stringify(error.params.limit)}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
comparison, | ||
limit | ||
} = | ||
/** @type {import("ajv").ComparisonParams} */ | ||
params; | ||
return `${dataPath} should be ${comparison} ${JSON.stringify(limit)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
@@ -466,17 +927,68 @@ case 'minimum': | ||
case 'exclusiveMaximum': | ||
return `${dataPath} should be ${error.params.comparison} ${error.params.limit}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
parentSchema, | ||
params | ||
} = error; | ||
const { | ||
comparison, | ||
limit | ||
} = | ||
/** @type {import("ajv").ComparisonParams} */ | ||
params; | ||
const hints = numberHints(parentSchema); | ||
if (hints.length === 0) { | ||
hints.push(`should be ${comparison} ${limit}`); | ||
} | ||
return `${dataPath} ${hints.join(' ')}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'multipleOf': | ||
return `${dataPath} should be multiple of ${error.params.multipleOf}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
multipleOf | ||
} = | ||
/** @type {import("ajv").MultipleOfParams} */ | ||
params; | ||
return `${dataPath} should be multiple of ${multipleOf}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'patternRequired': | ||
return `${dataPath} should have property matching pattern ${JSON.stringify(error.params.missingPattern)}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
missingPattern | ||
} = | ||
/** @type {import("ajv").PatternRequiredParams} */ | ||
params; | ||
return `${dataPath} should have property matching pattern ${JSON.stringify(missingPattern)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'minLength': | ||
{ | ||
if (error.params.limit === 1) { | ||
return `${dataPath} should be an non-empty string.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
if (limit === 1) { | ||
return `${dataPath} should be an non-empty string${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
return `${dataPath} should not be shorter than ${error.params.limit} characters.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
const length = limit - 1; | ||
return `${dataPath} should be longer than ${length} character${length > 1 ? 's' : ''}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
@@ -486,7 +998,17 @@ | ||
{ | ||
if (error.params.limit === 1) { | ||
return `${dataPath} should be an non-empty array.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
if (limit === 1) { | ||
return `${dataPath} should be an non-empty array${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
return `${dataPath} should not have fewer than ${error.params.limit} items.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
return `${dataPath} should not have fewer than ${limit} items${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
@@ -496,41 +1018,147 @@ | ||
{ | ||
if (error.params.limit === 1) { | ||
return `${dataPath} should be an non-empty object.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
if (limit === 1) { | ||
return `${dataPath} should be an non-empty object${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
return `${dataPath} should not have fewer than ${error.params.limit} properties.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
return `${dataPath} should not have fewer than ${limit} properties${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'maxLength': | ||
return `${dataPath} should not be longer than ${error.params.limit} characters.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
return `${dataPath} should be shorter than ${limit + 1} characters${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'maxItems': | ||
return `${dataPath} should not have more than ${error.params.limit} items.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
return `${dataPath} should not have more than ${limit} items${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'maxProperties': | ||
return `${dataPath} should not have more than ${error.params.limit} properties.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
return `${dataPath} should not have more than ${limit} properties${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'uniqueItems': | ||
return `${dataPath} should not contain the item '${error.data[error.params.i]}' twice.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
i | ||
} = | ||
/** @type {import("ajv").UniqueItemsParams} */ | ||
params; | ||
return `${dataPath} should not contain the item '${error.data[i]}' twice${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'additionalItems': | ||
return `${dataPath} should not have more than ${error.params.limit} items. These items are valid:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
limit | ||
} = | ||
/** @type {import("ajv").LimitParams} */ | ||
params; | ||
return `${dataPath} should not have more than ${limit} items${getSchemaNonTypes(parentSchema)}. These items are valid:\n${this.getSchemaPartText(parentSchema)}`; | ||
} | ||
case 'contains': | ||
return `${dataPath} should contains at least one ${this.getSchemaPartText(error.parentSchema, ['contains'])} item.`; | ||
{ | ||
const { | ||
parentSchema | ||
} = error; | ||
return `${dataPath} should contains at least one ${this.getSchemaPartText(parentSchema, ['contains'])} item${getSchemaNonTypes(parentSchema)}.`; | ||
} | ||
case 'required': | ||
{ | ||
const missingProperty = error.params.missingProperty.replace(/^\./, ''); | ||
const hasProperty = Boolean(error.parentSchema.properties && error.parentSchema.properties[missingProperty]); | ||
return `${dataPath} misses the property '${missingProperty}'.${hasProperty ? ` Should be:\n${this.getSchemaPartText(error.parentSchema, ['properties', missingProperty])}` : this.getSchemaPartDescription(error.parentSchema)}`; | ||
const { | ||
parentSchema, | ||
params | ||
} = error; | ||
const missingProperty = | ||
/** @type {import("ajv").DependenciesParams} */ | ||
params.missingProperty.replace(/^\./, ''); | ||
const hasProperty = parentSchema && Boolean( | ||
/** @type {Schema} */ | ||
parentSchema.properties && | ||
/** @type {Schema} */ | ||
parentSchema.properties[missingProperty]); | ||
return `${dataPath} misses the property '${missingProperty}'${getSchemaNonTypes(parentSchema)}.${hasProperty ? ` Should be:\n${this.getSchemaPartText(parentSchema, ['properties', missingProperty])}` : this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
case 'additionalProperties': | ||
return `${dataPath} has an unknown property '${error.params.additionalProperty}'. These properties are valid:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
additionalProperty | ||
} = | ||
/** @type {import("ajv").AdditionalPropertiesParams} */ | ||
params; | ||
return `${dataPath} has an unknown property '${additionalProperty}'${getSchemaNonTypes(parentSchema)}. These properties are valid:\n${this.getSchemaPartText(parentSchema)}`; | ||
} | ||
case 'dependencies': | ||
{ | ||
const dependencies = error.params.deps.split(',').map(dep => `'${dep.trim()}'`).join(', '); | ||
return `${dataPath} should have properties ${dependencies} when property '${error.params.property}' is present.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
property, | ||
deps | ||
} = | ||
/** @type {import("ajv").DependenciesParams} */ | ||
params; | ||
const dependencies = deps.split(',').map( | ||
/** | ||
* @param {string} dep | ||
* @returns {string} | ||
*/ | ||
dep => `'${dep.trim()}'`).join(', '); | ||
return `${dataPath} should have properties ${dependencies} when property '${property}' is present${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
@@ -540,4 +1168,13 @@ | ||
{ | ||
const invalidProperty = error.params.propertyName; | ||
return `${dataPath} property name '${invalidProperty}' is invalid. Property names should be match format ${JSON.stringify(error.schema.format)}.${this.getSchemaPartDescription(error.parentSchema)}`; | ||
const { | ||
params, | ||
parentSchema, | ||
schema | ||
} = error; | ||
const { | ||
propertyName | ||
} = | ||
/** @type {import("ajv").PropertyNamesParams} */ | ||
params; | ||
return `${dataPath} property name '${propertyName}' is invalid${getSchemaNonTypes(parentSchema)}. Property names should be match format ${JSON.stringify(schema.format)}.${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
@@ -547,55 +1184,117 @@ | ||
{ | ||
if (error.parentSchema && error.parentSchema.enum && error.parentSchema.enum.length === 1) { | ||
return `${dataPath} should be ${this.getSchemaPartText(error.parentSchema, false, true)}`; | ||
const { | ||
parentSchema | ||
} = error; | ||
if (parentSchema && | ||
/** @type {Schema} */ | ||
parentSchema.enum && | ||
/** @type {Schema} */ | ||
parentSchema.enum.length === 1) { | ||
return `${dataPath} should be ${this.getSchemaPartText(parentSchema, false, true)}`; | ||
} | ||
return `${dataPath} should be one of these:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}`; | ||
} | ||
case 'const': | ||
return `${dataPath} should be equal to constant ${this.getSchemaPartText(error.parentSchema)}`; | ||
{ | ||
const { | ||
parentSchema | ||
} = error; | ||
return `${dataPath} should be equal to constant ${this.getSchemaPartText(parentSchema)}`; | ||
} | ||
case 'not': | ||
{ | ||
const { | ||
schema, | ||
parentSchema | ||
} = error; | ||
return `${dataPath} should not be ${this.getSchemaPartText(schema)}${parentSchema && likeObject(parentSchema) ? `\n${this.getSchemaPartText(parentSchema)}` : ''}`; | ||
} | ||
case 'oneOf': | ||
case 'anyOf': | ||
{ | ||
if (error.children && error.children.length > 0) { | ||
const { | ||
parentSchema, | ||
children | ||
} = error; | ||
if (children && children.length > 0) { | ||
if (error.schema.length === 1) { | ||
const lastChild = error.children[error.children.length - 1]; | ||
const remainingChildren = error.children.slice(0, error.children.length - 1); | ||
const lastChild = children[children.length - 1]; | ||
const remainingChildren = children.slice(0, children.length - 1); | ||
return this.formatValidationError(Object.assign({}, lastChild, { | ||
children: remainingChildren, | ||
parentSchema: Object.assign({}, error.parentSchema, lastChild.parentSchema) | ||
parentSchema: Object.assign({}, parentSchema, lastChild.parentSchema) | ||
})); | ||
} | ||
const children = filterChildren(error.children); | ||
let filteredChildren = filterChildren(children); | ||
if (children.length === 1) { | ||
return this.formatValidationError(children[0]); | ||
if (filteredChildren.length === 1) { | ||
return this.formatValidationError(filteredChildren[0]); | ||
} | ||
return `${dataPath} should be one of these:\n${this.getSchemaPartText(error.parentSchema)}\nDetails:\n${children.map(nestedError => ` * ${indent(this.formatValidationError(nestedError), ' ')}`).join('\n')}`; | ||
filteredChildren = groupChildrenByFirstChild(filteredChildren); | ||
return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}\nDetails:\n${filteredChildren.map( | ||
/** | ||
* @param {SchemaUtilErrorObject} nestedError | ||
* @returns {string} | ||
*/ | ||
nestedError => ` * ${indent(this.formatValidationError(nestedError), ' ')}`).join('\n')}`; | ||
} | ||
return `${dataPath} should be one of these:\n${this.getSchemaPartText(error.parentSchema)}`; | ||
return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}`; | ||
} | ||
case 'if': | ||
return `${dataPath} should match "${error.params.failingKeyword}" schema:\n${this.getSchemaPartText(error.parentSchema, [error.params.failingKeyword])}`; | ||
{ | ||
const { | ||
params, | ||
parentSchema | ||
} = error; | ||
const { | ||
failingKeyword | ||
} = | ||
/** @type {import("ajv").IfParams} */ | ||
params; | ||
return `${dataPath} should match "${failingKeyword}" schema:\n${this.getSchemaPartText(parentSchema, [failingKeyword])}`; | ||
} | ||
case 'absolutePath': | ||
return `${dataPath}: ${error.message}${this.getSchemaPartDescription(error.parentSchema)}`; | ||
{ | ||
const { | ||
message, | ||
parentSchema | ||
} = error; | ||
return `${dataPath}: ${message}${this.getSchemaPartDescription(parentSchema)}`; | ||
} | ||
/* istanbul ignore next */ | ||
default: | ||
// For `custom`, `false schema`, `$ref` keywords | ||
// Fallback for unknown keywords | ||
{ | ||
const { | ||
message, | ||
parentSchema | ||
} = error; | ||
const ErrorInJSON = JSON.stringify(error, null, 2); // For `custom`, `false schema`, `$ref` keywords | ||
// Fallback for unknown keywords | ||
/* istanbul ignore next */ | ||
return `${dataPath} ${error.message} (${JSON.stringify(error, null, 2)}).\n${this.getSchemaPartText(error.parentSchema)}`; | ||
return `${dataPath} ${message} (${ErrorInJSON}).\n${this.getSchemaPartText(parentSchema, false)}`; | ||
} | ||
} | ||
} | ||
/** | ||
* @param {Array<SchemaUtilErrorObject>} errors | ||
* @returns {string} | ||
*/ | ||
formatValidationErrors(errors) { | ||
return errors.map(error => { | ||
let formattedError = this.formatValidationError(error, this.schema); | ||
let formattedError = this.formatValidationError(error); | ||
@@ -602,0 +1301,0 @@ if (this.postFormatter) { |
{ | ||
"name": "@develar/schema-utils", | ||
"version": "2.1.0", | ||
"version": "2.6.5", | ||
"description": "webpack Validation Utils", | ||
"license": "MIT", | ||
"repository": "webpack-contrib/schema-utils", | ||
"repository": "webpack/schema-utils", | ||
"author": "webpack Contrib (https://github.com/webpack-contrib)", | ||
"homepage": "https://github.com/webpack-contrib/schema-utils", | ||
"bugs": "https://github.com/webpack-contrib/schema-utils/issues", | ||
"homepage": "https://github.com/webpack/schema-utils", | ||
"bugs": "https://github.com/webpack/schema-utils/issues", | ||
"funding": { | ||
"type": "opencollective", | ||
"url": "https://opencollective.com/webpack" | ||
}, | ||
"main": "dist/index.js", | ||
"types": "declarations/index.d.ts", | ||
"engines": { | ||
@@ -16,49 +21,55 @@ "node": ">= 8.9.0" | ||
"start": "npm run build -- -w", | ||
"clean": "del-cli dist declarations", | ||
"prebuild": "npm run clean", | ||
"build": "cross-env NODE_ENV=production babel src -d dist --ignore \"src/**/*.test.js\" --copy-files", | ||
"clean": "del-cli dist", | ||
"build:types": "tsc --declaration --emitDeclarationOnly --outDir declarations && prettier \"declarations/**/*.ts\" --write", | ||
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files", | ||
"build": "npm-run-all -p \"build:**\"", | ||
"commitlint": "commitlint --from=master", | ||
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css}\" --list-different", | ||
"lint:js": "eslint --cache src test", | ||
"security": "npm audit", | ||
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different", | ||
"lint:js": "eslint --cache .", | ||
"lint:types": "tsc --pretty --noEmit", | ||
"lint": "npm-run-all -l -p \"lint:**\"", | ||
"test:only": "cross-env NODE_ENV=test jest", | ||
"test:watch": "npm run test:only -- --watch", | ||
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage", | ||
"pretest": "npm run lint", | ||
"test": "npm run test:coverage", | ||
"prepare": "npm run build", | ||
"release": "standard-version", | ||
"security": "npm audit", | ||
"test:only": "cross-env NODE_ENV=test jest", | ||
"test:watch": "cross-env NODE_ENV=test jest --watch", | ||
"test:coverage": "cross-env NODE_ENV=test jest --collectCoverageFrom=\"src/**/*.js\" --coverage", | ||
"pretest": "npm run lint", | ||
"test": "cross-env NODE_ENV=test npm run test:coverage", | ||
"defaults": "webpack-defaults" | ||
}, | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"declarations" | ||
], | ||
"dependencies": { | ||
"ajv": "^6.1.0", | ||
"ajv-keywords": "^3.1.0" | ||
"ajv": "^6.12.0", | ||
"ajv-keywords": "^3.4.1" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.4.4", | ||
"@babel/core": "^7.4.5", | ||
"@babel/preset-env": "^7.4.5", | ||
"@commitlint/cli": "^8.0.0", | ||
"@commitlint/config-conventional": "^8.0.0", | ||
"@webpack-contrib/defaults": "^5.0.0", | ||
"@babel/cli": "^7.8.3", | ||
"@babel/core": "^7.8.3", | ||
"@babel/preset-env": "^7.8.3", | ||
"@commitlint/cli": "^8.3.5", | ||
"@commitlint/config-conventional": "^8.3.4", | ||
"@types/json-schema": "^7.0.4", | ||
"@webpack-contrib/defaults": "^6.3.0", | ||
"@webpack-contrib/eslint-config-webpack": "^3.0.0", | ||
"babel-jest": "^24.8.0", | ||
"commitlint-azure-pipelines-cli": "^1.0.2", | ||
"cross-env": "^5.2.0", | ||
"del": "^5.0.0", | ||
"del-cli": "^2.0.0", | ||
"eslint": "^6.0.1", | ||
"eslint-config-prettier": "^6.0.0", | ||
"eslint-plugin-import": "^2.0.0", | ||
"husky": "^3.0.0", | ||
"jest": "^24.8.0", | ||
"jest-junit": "^6.4.0", | ||
"lint-staged": "^9.2.0", | ||
"babel-jest": "^24.9.0", | ||
"commitlint-azure-pipelines-cli": "^1.0.3", | ||
"cross-env": "^6.0.3", | ||
"del": "^5.1.0", | ||
"del-cli": "^3.0.0", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.9.0", | ||
"eslint-plugin-import": "^2.20.0", | ||
"husky": "^4.0.10", | ||
"jest": "^24.9.0", | ||
"jest-junit": "^10.0.0", | ||
"lint-staged": "^9.5.0", | ||
"npm-run-all": "^4.1.5", | ||
"prettier": "^1.0.0", | ||
"standard-version": "^6.0.1" | ||
"prettier": "^1.19.1", | ||
"standard-version": "^7.0.1", | ||
"typescript": "^3.7.5" | ||
}, | ||
@@ -65,0 +76,0 @@ "keywords": [ |
@@ -99,2 +99,22 @@ <div align="center"> | ||
There is an alternative method to configure the `name` and`baseDataPath` options via the `title` property in the schema. | ||
For example: | ||
```json | ||
{ | ||
"title": "My Loader options", | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"description": "This is description of option.", | ||
"type": "string" | ||
} | ||
}, | ||
"additionalProperties": false | ||
} | ||
``` | ||
The last word used for the `baseDataPath` option, other words used for the `name` option. | ||
Based on the example above the `name` option equals `My Loader`, the `baseDataPath` option equals `options`. | ||
#### `name` | ||
@@ -153,3 +173,3 @@ | ||
```shell | ||
Invalid options object. MyPlugin has been initialised using an options object that does not match the API schema. | ||
Invalid options object. MyPlugin has been initialized using an options object that does not match the API schema. | ||
- options.optionName should be a integer. | ||
@@ -156,0 +176,0 @@ Additional Information. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
72241
14
1608
277
24
1
Updatedajv@^6.12.0
Updatedajv-keywords@^3.4.1