From 4c19792a74790334269bf3fc436c4af6328b2563 Mon Sep 17 00:00:00 2001 From: Mickael Jeanroy Date: Sat, 30 Nov 2019 14:44:00 +0100 Subject: [PATCH] refactor: extract some code --- src/format-path.js | 51 +++++++++++ src/license-plugin-option.js | 155 +--------------------------------- src/schema-validator.js | 150 ++++++++++++++++++++++++++++++++ test/format-path.spec.js | 36 ++++++++ test/schema-validator.spec.js | 131 ++++++++++++++++++++++++++++ 5 files changed, 371 insertions(+), 152 deletions(-) create mode 100644 src/format-path.js create mode 100644 src/schema-validator.js create mode 100644 test/format-path.spec.js create mode 100644 test/schema-validator.spec.js diff --git a/src/format-path.js b/src/format-path.js new file mode 100644 index 00000000..cb591251 --- /dev/null +++ b/src/format-path.js @@ -0,0 +1,51 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 Mickael Jeanroy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +const _ = require('lodash'); + +/** + * Format given array of path to a human readable path. + * + * @param {Array} paths List of paths. + * @return {string} The full path. + */ +function formatPath(paths) { + let str = ''; + + _.forEach(paths, (p) => { + if (_.isNumber(p)) { + str += `[${p}]`; + } else if (!str) { + str += p; + } else { + str += `.${p}`; + } + }); + + return str; +} + +module.exports = formatPath; diff --git a/src/license-plugin-option.js b/src/license-plugin-option.js index 354cbd29..dd7cccc3 100644 --- a/src/license-plugin-option.js +++ b/src/license-plugin-option.js @@ -27,157 +27,8 @@ const _ = require('lodash'); const PLUGIN_NAME = require('./license-plugin-name.js'); const validators = require('./schema-validators.js'); - -/** - * Format given array of path to a human readable path. - * - * @param {Array} paths List of paths. - * @return {string} The full path. - */ -function formatPath(paths) { - let str = ''; - - _.forEach(paths, (p) => { - if (_.isNumber(p)) { - str += `[${p}]`; - } else if (!str) { - str += p; - } else { - str += `.${p}`; - } - }); - - return str; -} - -/** - * Validate value against given schema. - * It is assumed that `value` will not be `null` or `undefined`. - * - * @param {*} value The value being validated. - * @param {Array|Object} schema The validation schema. - * @param {Array} path The path being validated. - * @returns {Array} Found errors. - */ -function doItemValidation(value, schema, path) { - const validators = _.castArray(schema); - const matchedValidators = _.filter(validators, (validator) => validator.test(value)); - - // No one matched, we can stop here and return an error with a proper message. - if (_.isEmpty(matchedValidators)) { - return [ - { - path, - message: _.map(validators, (validator) => `"${formatPath(path)}" ${validator.message}`).join(' OR '), - }, - ]; - } - - // Run "sub-validators" - return _.chain(matchedValidators) - .filter((validator) => validator.schema) - .map((validator) => validate(value, validator.schema, path)) - .flatten() - .value(); -} - -/** - * Validate object against given schema. - * Note that `null` or `undefined` is allowed and do not produce an error. - * - * @param {Object} obj The object to validate. - * @param {Array|Object} schema The validation schema. - * @param {Array} current The current path being validated. - * @returns {Array} Found errors. - */ -function validateObject(obj, schema, current) { - const errors = []; - - _.forEach(obj, (value, k) => { - if (_.isNil(value)) { - return; - } - - const path = [...current, k]; - - if (!_.has(schema, k)) { - errors.push({type: 'object.allowUnknown', path}); - } else { - errors.push(...doItemValidation(value, schema[k], path)); - } - }); - - return errors; -} - -/* - * Validate element of an array. - * - * Instead of "classic" object validation, `null` and `undefined` will produce - * an error here. - * - * @returns {Array|Object} schema The validation schema. - * @param {Array} current The path being validated. - * @return {Array} Found errors. - */ -function validateArrayItem(item, idx, schema, current) { - const path = [...current, idx]; - - if (_.isUndefined(item)) { - return [{path, message: `"${formatPath(path)}" is undefined.`}]; - } - - if (_.isNull(item)) { - return [{path, message: `"${formatPath(path)}" is null.`}]; - } - - return doItemValidation(item, schema, path); -} - -/** - * Validate all elements of given array against given schema (or array of schemas). - * - * @param {Array<*>} array Array of elements to validate. - * @param {Array|Object} schema The schema to use for validation. - * @param {string} current The path being validated. - * @return {Array} Found errors. - */ -function validateArray(array, schema, current) { - return _.chain(array) - .map((item, idx) => validateArrayItem(item, idx, schema, current)) - .flatten() - .value(); -} - -/** - * Validate given object against given schema. - * - * Note that the very first version used `@hapi/joi` but this package does not support node < 8 in its latest version. - * Since I don't want to depends on deprecated and non maintained packages, and I want to keep compatibility with - * Node 6, I re-implemented the small part I needed here. - * - * Once node 6 will not be supported (probably with rollup >= 2), it will be time to drop this in favor of `@hapi/joi` - * for example. - * - * @param {Object} obj Object to validate. - * @param {Object} schema The schema against the given object will be validated. - * @param {Array} current The current path context of given object, useful to validate against subobject. - * @return {Array} Found errors. - */ -function validate(obj, schema, current = []) { - return _.isArray(obj) ? validateArray(obj, schema, current) : validateObject(obj, schema, current); -} +const validateSchema = require('./schema-validator.js'); +const formatPath = require('./format-path.js'); /** * The option object schema. @@ -407,7 +258,7 @@ function normalizeOptions(options) { * @return {Array} An array of all errors. */ function doValidation(options) { - return validate(options, SCHEMA); + return validateSchema(options, SCHEMA); } /** diff --git a/src/schema-validator.js b/src/schema-validator.js new file mode 100644 index 00000000..45449619 --- /dev/null +++ b/src/schema-validator.js @@ -0,0 +1,150 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 Mickael Jeanroy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +const _ = require('lodash'); +const formatPath = require('./format-path.js'); + +/** + * Validate value against given schema. + * It is assumed that `value` will not be `null` or `undefined`. + * + * @param {*} value The value being validated. + * @param {Array|Object} schema The validation schema. + * @param {Array} path The path being validated. + * @returns {Array} Found errors. + */ +function doItemValidation(value, schema, path) { + const validators = _.castArray(schema); + const matchedValidators = _.filter(validators, (validator) => validator.test(value)); + + // No one matched, we can stop here and return an error with a proper message. + if (_.isEmpty(matchedValidators)) { + return [ + { + path, + message: _.map(validators, (validator) => `"${formatPath(path)}" ${validator.message}`).join(' OR '), + }, + ]; + } + + // Run "sub-validators" + return _.chain(matchedValidators) + .filter((validator) => validator.schema) + .map((validator) => validate(value, validator.schema, path)) + .flatten() + .value(); +} + +/** + * Validate object against given schema. + * Note that `null` or `undefined` is allowed and do not produce an error. + * + * @param {Object} obj The object to validate. + * @param {Array|Object} schema The validation schema. + * @param {Array} current The current path being validated. + * @returns {Array} Found errors. + */ +function validateObject(obj, schema, current) { + const errors = []; + + _.forEach(obj, (value, k) => { + if (_.isNil(value)) { + return; + } + + const path = [...current, k]; + + if (!_.has(schema, k)) { + errors.push({type: 'object.allowUnknown', path}); + } else { + errors.push(...doItemValidation(value, schema[k], path)); + } + }); + + return errors; +} + +/** + * Validate element of an array. + * + * Instead of "classic" object validation, `null` and `undefined` will produce + * an error here. + * + * @param {*} item The item to validate. + * @param {number} idx The index of item in original array. + * @param {Array|Object} schema The validation schema. + * @param {Array} current The path being validated. + * @return {Array} Found errors. + */ +function validateArrayItem(item, idx, schema, current) { + const path = [...current, idx]; + + if (_.isUndefined(item)) { + return [{path, message: `"${formatPath(path)}" is undefined.`}]; + } + + if (_.isNull(item)) { + return [{path, message: `"${formatPath(path)}" is null.`}]; + } + + return doItemValidation(item, schema, path); +} + +/** + * Validate all elements of given array against given schema (or array of schemas). + * + * @param {Array<*>} array Array of elements to validate. + * @param {Array|Object} schema The schema to use for validation. + * @param {string} current The path being validated. + * @return {Array} Found errors. + */ +function validateArray(array, schema, current) { + return _.chain(array) + .map((item, idx) => validateArrayItem(item, idx, schema, current)) + .flatten() + .value(); +} + +/** + * Validate given object against given schema. + * + * Note that the very first version used `@hapi/joi` but this package does not support node < 8 in its latest version. + * Since I don't want to depends on deprecated and non maintained packages, and I want to keep compatibility with + * Node 6, I re-implemented the small part I needed here. + * + * Once node 6 will not be supported (probably with rollup >= 2), it will be time to drop this in favor of `@hapi/joi` + * for example. + * + * @param {Object} obj Object to validate. + * @param {Object} schema The schema against the given object will be validated. + * @param {Array} current The current path context of given object, useful to validate against subobject. + * @return {Array} Found errors. + */ +function validate(obj, schema, current = []) { + return _.isArray(obj) ? validateArray(obj, schema, current) : validateObject(obj, schema, current); +} + +module.exports = validate; diff --git a/test/format-path.spec.js b/test/format-path.spec.js new file mode 100644 index 00000000..8695b26d --- /dev/null +++ b/test/format-path.spec.js @@ -0,0 +1,36 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 Mickael Jeanroy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +const formatPath = require('../dist/format-path.js'); + +describe('formatPath', () => { + it('should format path of object', () => { + expect(formatPath([])).toBe(''); + expect(formatPath(['person', 'name'])).toBe('person.name'); + expect(formatPath(['persons', 0])).toBe('persons[0]'); + expect(formatPath(['persons', 0, 'name'])).toBe('persons[0].name'); + }); +}); diff --git a/test/schema-validator.spec.js b/test/schema-validator.spec.js new file mode 100644 index 00000000..c41e10e3 --- /dev/null +++ b/test/schema-validator.spec.js @@ -0,0 +1,131 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 Mickael Jeanroy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +const validateSchema = require('../dist/schema-validator.js'); +const validators = require('../dist/schema-validators.js'); + +describe('validateSchema', () => { + it('should validate simple object', () => { + const schema = { + id: validators.string(), + active: validators.boolean(), + test: validators.func(), + }; + + const o = { + id: '123', + active: true, + test: () => true, + }; + + expect(validateSchema(o, schema)).toEqual([]); + }); + + it('should validate and return errors', () => { + const schema = { + id: validators.string(), + active: validators.boolean(), + test: validators.func(), + }; + + const o = { + id: 1, + active: 'foo', + test: true, + }; + + expect(validateSchema(o, schema)).toEqual([ + {path: ['id'], message: '"id" must be a string'}, + {path: ['active'], message: '"active" must be a boolean'}, + {path: ['test'], message: '"test" must be a function'}, + ]); + }); + + it('should validate sub object and return errors', () => { + const schema = { + person: validators.object({ + id: validators.string(), + active: validators.boolean(), + test: validators.func(), + }), + }; + + const o = { + person: { + id: 1, + active: 'foo', + test: true, + }, + }; + + expect(validateSchema(o, schema)).toEqual([ + {path: ['person', 'id'], message: '"person.id" must be a string'}, + {path: ['person', 'active'], message: '"person.active" must be a boolean'}, + {path: ['person', 'test'], message: '"person.test" must be a function'}, + ]); + }); + + it('should validate array of options', () => { + const schema = { + id: validators.string(), + active: validators.boolean(), + test: [ + validators.boolean(), + validators.func(), + ], + }; + + const o = { + id: '1', + active: true, + test: true, + }; + + expect(validateSchema(o, schema)).toEqual([]); + }); + + it('should fail with unknown properties', () => { + const schema = { + id: validators.string(), + active: validators.boolean(), + test: [ + validators.boolean(), + validators.func(), + ], + }; + + const o = { + id: '1', + active: true, + test: true, + name: 'John Doe', + }; + + expect(validateSchema(o, schema)).toEqual([ + {path: ['name'], type: 'object.allowUnknown'}, + ]); + }); +});