From 40bf39b98909d3d3f9615d3f3d743f9abbb7d2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frantis=CC=8Cek=20Ha=CC=81ba?= Date: Fri, 16 Mar 2012 21:26:21 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20the=20=E2=80=98getProperty=E2=80=99=20met?= =?UTF-8?q?hod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- releases/0.4.2/amanda.js | 3212 +++++++++++++++---------------- releases/0.4.2/amanda.min.js | 38 +- releases/latest/amanda.js | 3212 +++++++++++++++---------------- releases/latest/amanda.min.js | 38 +- src/engines/json/getProperty.js | 2 +- 5 files changed, 3173 insertions(+), 3329 deletions(-) diff --git a/releases/0.4.2/amanda.js b/releases/0.4.2/amanda.js index 7dc6ead..93fcbb8 100644 --- a/releases/0.4.2/amanda.js +++ b/releases/0.4.2/amanda.js @@ -1,4 +1,4 @@ -(function() { +(function () { /** * Engines @@ -6,1996 +6,1918 @@ */ var engines = {}; -/** - * DetectType - * - * @param {object} input - */ -var detectType = function(input) { - return typeof input; -}; - -/** - * Each - * - * Applies an iterator function to each item in an array or an object, in series. - * - * @param {object} list - * @param {function} iterator - * @param {function} callback - */ -var each = function(list, iterator, callback) { - /** - * SyncEach + * DetectType * - * @param {object} list - * @param {function} iterator + * @param {object} input */ - var syncEach = function(list, iterator) { - - // If the list is an array - if (isArray(list) && !isEmpty(list)) { - for (var i = 0, len = list.length; i < len; i++) { - iterator.apply(list, [i, list[i]]); - } - } - - // If the list is an object - if (isObject(list) && !isEmpty(list)) { - for (var key in list) { - if (list.hasOwnProperty(key)) { - iterator.apply(list, [key, list[key]]); - } - } - } - - }; + var detectType = function (input) { + return typeof input; + }; /** - * AsyncEach + * Each + * + * Applies an iterator function to each item in an array or an object, in series. + * * @param {object} list * @param {function} iterator * @param {function} callback */ - var asyncEach = function(list, iterator, callback) { + var each = function (list, iterator, callback) { - var queue = []; + /** + * SyncEach + * + * @param {object} list + * @param {function} iterator + */ + var syncEach = function (list, iterator) { - /** - * AddToQueue - * - * @param {string} key - * @param {string|object} value - */ - var addToQueue = function(key, value) { - var index = queue.length + 1; - queue.push(function() { - - var next = function(error) { - var fn = queue[index]; - if (!error && fn) { - return fn(); - } else if (!error && !fn) { - return callback(); - } else { - return callback(error); + // If the list is an array + if (isArray(list) && !isEmpty(list)) { + for (var i = 0, len = list.length; i < len; i++) { + iterator.apply(list, [i, list[i]]); + } } - }; - - return iterator(key, value, next); - - }); - }; - - // If the list is an array - if (isArray(list) && !isEmpty(list)) { - for (var i = 0, len = list.length; i < len; i++) { - addToQueue(i, list[i]); - } - - // If the list is an object - } else if (isObject(list) && !isEmpty(list)) { - for (var key in list) { - if (list.hasOwnProperty(key)) { - addToQueue(key, list[key]); - } - } - // If the list is not an array or an object - } else { - return callback(); - } + // If the list is an object + if (isObject(list) && !isEmpty(list)) { + for (var key in list) { + if (list.hasOwnProperty(key)) { + iterator.apply(list, [key, list[key]]); + } + } + } - // And go! - return queue[0](); + }; - }; + /** + * AsyncEach + * @param {object} list + * @param {function} iterator + * @param {function} callback + */ + var asyncEach = function (list, iterator, callback) { + + var queue = []; + + /** + * AddToQueue + * + * @param {string} key + * @param {string|object} value + */ + var addToQueue = function (key, value) { + var index = queue.length + 1; + queue.push(function () { + + var next = function (error) { + var fn = queue[index]; + if (!error && fn) { + return fn(); + } else if (!error && !fn) { + return callback(); + } else { + return callback(error); + } + }; + + return iterator(key, value, next); - if (typeof callback === 'undefined') { - return syncEach.apply(this, arguments); - } else { - return asyncEach.apply(this, arguments); - } + }); + }; -}; - -/** - * Every - * - * @param {object} arr - * @param {function} iterator - */ -var every = function(arr, iterator) { - return Array.prototype.every.apply(arr, [iterator]); -}; - -/** - * Filter - * - * @param {object} arr - * @param {function} iterator - */ -var filter = function(arr, iterator, context) { - return Array.prototype.filter.apply(arr, [iterator, context || this]); -}; - -/** - * HasProperty - * - * @param {object} input - */ -var hasProperty = function(obj, property) { - return Object.prototype.hasOwnProperty.apply(obj, [property]); -}; - -/** - * IsArray - * - * Returns true if the passed-in object is an array. - * - * @param {object} input - */ -var isArray = function(input) { - return Object.prototype.toString.call(input) === '[object Array]'; -}; - -/** - * IsBoolean - * - * @param {object} input - */ -var isBoolean = function(input) { - return typeof input === 'boolean'; -}; - -/** - * IsDefined - * - * @param {object} input - */ -var isDefined = function(input) { - return typeof input !== 'undefined'; -}; - -/** - * IsEmpty - * - * Returns true if the passed-in object is empty. - * - * @param {object} input - */ -var isEmpty = function(input) { - - if (isNumber(input)) { - return false; - } + // If the list is an array + if (isArray(list) && !isEmpty(list)) { + for (var i = 0, len = list.length; i < len; i++) { + addToQueue(i, list[i]); + } - if (input === null) { - return true; - } + // If the list is an object + } else if (isObject(list) && !isEmpty(list)) { + for (var key in list) { + if (list.hasOwnProperty(key)) { + addToQueue(key, list[key]); + } + } - // If the passed-in object is an array or a string - if (isArray(input) || typeof input === 'string') { - return input.length === 0; - } + // If the list is not an array or an object + } else { + return callback(); + } - // If the passed-in object is an object - if (isObject(input)) { - for (var key in input) { - if (hasOwnProperty.call(input, key)) return false; - } - } + // And go! + return queue[0](); - return true; + }; -}; + if (typeof callback === 'undefined') { + return syncEach.apply(this, arguments); + } else { + return asyncEach.apply(this, arguments); + } -/** - * IsEqual - * - * @param {object} obj1 - * @param {object} obj2 - */ -var isEqual = function(obj1, obj2) { + }; /** - * Arrays + * Every + * + * @param {object} arr + * @param {function} iterator */ - if (isArray(obj1, obj2)) { + var every = function (arr, iterator) { + return Array.prototype.every.apply(arr, [iterator]); + }; - if (obj1.length !== obj2.length) { - return false; - } + /** + * Filter + * + * @param {object} arr + * @param {function} iterator + */ + var filter = function (arr, iterator, context) { + return Array.prototype.filter.apply(arr, [iterator, context || this]); + }; - return every(obj1, function(value, index, context) { - return obj2[index] === value; - }); + /** + * HasProperty + * + * @param {object} input + */ + var hasProperty = function (obj, property) { + return Object.prototype.hasOwnProperty.apply(obj, [property]); + }; - } + /** + * IsArray + * + * Returns true if the passed-in object is an array. + * + * @param {object} input + */ + var isArray = function (input) { + return Object.prototype.toString.call(input) === '[object Array]'; + }; /** - * Objects + * IsBoolean + * + * @param {object} input */ - if (isObject(obj1, obj2)) { + var isBoolean = function (input) { + return typeof input === 'boolean'; + }; - var keys1 = keys(obj1), - keys2 = keys(obj2); + /** + * IsDefined + * + * @param {object} input + */ + var isDefined = function (input) { + return typeof input !== 'undefined'; + }; - if (!isEqual(keys1, keys2)) { - return false; - } + /** + * IsEmpty + * + * Returns true if the passed-in object is empty. + * + * @param {object} input + */ + var isEmpty = function (input) { - for (key in obj1) { - if (!obj2[key] || obj1[key] !== obj2[key]) { + if (isNumber(input)) { return false; } - } - return true; + if (input === null) { + return true; + } - } + // If the passed-in object is an array or a string + if (isArray(input) || typeof input === 'string') { + return input.length === 0; + } - return false; - -}; - -/** - * IsFunction - * - * @param {object} input - */ -var isFunction = function(input) { - return typeof input === 'function'; -}; - -/** - * IsInteger - * - * @param {object} input - */ -var isInteger = function(input) { - return isNumber(input) && input % 1 === 0; -}; - -/** - * IsNull - * - * @param {object} input - */ -var isNull = function(input) { - return input === null; -}; - -/** - * IsNumber - * - * @param {object} input - */ -var isNumber = function(input) { - return typeof input === 'number'; -}; - -/** - * IsObject - * - * Returns true if the passed-in object is an object. - * - * @param {object} input - */ -var isObject = function(input) { - return Object.prototype.toString.call(input) === '[object Object]'; -}; - -/** - * IsString - * - * @param {object} input - */ -var isString = function(input) { - return typeof input === 'string'; -}; - -/** - * IsUndefined - * - * @param {object} input - */ -var isUndefined = function(input) { - return typeof input === 'undefined'; -}; - -/** - * Keys - * - * @param {object} obj - */ -var keys = function(obj) { - return Object.keys(obj); -}; - -/** - * Merge - * - * Copy all of the properties in the source objects over to the destination object. - * - * @param {object} obj1 - * @param {object} obj2 - */ -var merge = function(obj1, obj2) { - for (var key in obj2) { - if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) { - obj1[key] = obj2[key]; - } - } - return obj1; -}; - -/** - * Pluck - * - * Extracts a list of property values. - * - * @param {object} list - * @param {string} propertyName - */ -var pluck = function(list, propertyName) { - var output = []; - for (var i = 0, len = list.length; i < len; i++) { - var property = list[i][propertyName]; - if (output.indexOf(property) === -1) { - output.push(property); - } - } - return output; -}; - -/** - * ReturnTrue - */ -var returnTrue = function() { - return true; -}; - -/** - * Some - * - * @param {object} arr - * @param {function} iterator - */ -var some = function(arr, iterator) { - return Array.prototype.some.apply(arr, [iterator]); -}; - -(function() { - -/** - * Validation - * - * @constructor - * @param {object} options - */ -var Validation = function(options) { - - // Save a reference to the ‘this’ - var self = this; - - var defaultOptions = { - singleError: true, - messages: errorMessages, - cache: false - }; + // If the passed-in object is an object + if (isObject(input)) { + for (var key in input) { + if (hasOwnProperty.call(input, key)) return false; + } + } - each(defaultOptions, function(key, value) { + return true; - if (isObject(value) && options[key]) { - self[key] = merge(options[key], defaultOptions[key]); + }; - } else if (isObject(value) && !options[key]) { - self[key] = merge ({}, defaultOptions[key]); + /** + * IsEqual + * + * @param {object} obj1 + * @param {object} obj2 + */ + var isEqual = function (obj1, obj2) { - } else { - self[key] = (isDefined(options[key])) ? options[key] : defaultOptions[key]; - } + /** + * Arrays + */ + if (isArray(obj1, obj2)) { - }); + if (obj1.length !== obj2.length) { + return false; + } - this.errors = new ValidationError(this); + return every(obj1, function (value, index, context) { + return obj2[index] === value; + }); -}; + } -/** - * Attributes - * -------------------- - */ -Validation.prototype.attributes = {}; + /** + * Objects + */ + if (isObject(obj1, obj2)) { -/** - * AddAttribute - * - * @param {string} attributeName - * @param {function} attributeFn - */ -Validation.prototype.addAttribute = function(attributeName, attributeFn) { - return Validation.prototype.attributes[attributeName] = attributeFn; -}; + var keys1 = keys(obj1), + keys2 = keys(obj2); -/** - * AddAttributeConstructor - * - * @param {string} attributeName - * @param {function} attributeConstructor - */ -Validation.prototype.addAttributeConstructor = function(attributeName, attributeConstructor) { - return Validation.prototype.attributes[attributeName] = attributeConstructor(); -}; + if (!isEqual(keys1, keys2)) { + return false; + } -/** - * AdditionalProperties - */ -var additionalPropertiesAttribute = function additionalProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { + for (key in obj1) { + if (!obj2[key] || obj1[key] !== obj2[key]) { + return false; + } + } - var self = this; + return true; - /** - * { - * additionalProperties: true, - * ... - * } - */ - if (attributeValue === true) { - return callback(); - } + } - // Filter the forbidden properties - var propertyKeys = keys(propertyValue); - var forbiddenProperties = filter(propertyKeys, function(key) { - return !propertyAttributes.properties[key]; - }); + return false; - if (isEmpty(forbiddenProperties)) { - return callback(); - } + }; /** - * { - * additionalProperties: false, - * ... - * } + * IsFunction + * + * @param {object} input */ - if (attributeValue === false) { - - forbiddenProperties.forEach(function(forbiddenProperty) { - this.addError({ - property: this.joinPath(property, forbiddenProperty), - propertyValue: propertyValue[forbiddenProperty] - }); - }, this); - - return callback(); + var isFunction = function (input) { + return typeof input === 'function'; + }; - } + /** + * IsInteger + * + * @param {object} input + */ + var isInteger = function (input) { + return isNumber(input) && input % 1 === 0; + }; /** - * { - * additionalProperties: { - * type: 'string', - * ... - * }, - * ... - * } + * IsNull + * + * @param {object} input */ - if (isObject(attributeValue)) { - return each(forbiddenProperties, function(index, key, callback) { - return self.validateSchema( - propertyValue[key], - attributeValue, - property + key, - callback - ); - }, callback); - } + var isNull = function (input) { + return input === null; + }; -}; + /** + * IsNumber + * + * @param {object} input + */ + var isNumber = function (input) { + return typeof input === 'number'; + }; -// Export -Validation.prototype.addAttribute('additionalProperties', additionalPropertiesAttribute); + /** + * IsObject + * + * Returns true if the passed-in object is an object. + * + * @param {object} input + */ + var isObject = function (input) { + return Object.prototype.toString.call(input) === '[object Object]'; + }; -/** - * DivisibleBy - */ -var divisibleByAttribute = function divisibleBy(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * IsString + * + * @param {object} input + */ + var isString = function (input) { + return typeof input === 'string'; + }; - if (attributeValue === 0) { - throw new Error('The value of this attribute should not be 0.'); - } + /** + * IsUndefined + * + * @param {object} input + */ + var isUndefined = function (input) { + return typeof input === 'undefined'; + }; - if (isNumber(propertyValue) && (propertyValue % attributeValue !== 0)) { - this.addError(); - } + /** + * Keys + * + * @param {object} obj + */ + var keys = function (obj) { + return Object.keys(obj); + }; - return callback(); + /** + * Merge + * + * Copy all of the properties in the source objects over to the destination object. + * + * @param {object} obj1 + * @param {object} obj2 + */ + var merge = function (obj1, obj2) { + for (var key in obj2) { + if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) { + obj1[key] = obj2[key]; + } + } + return obj1; + }; -}; + /** + * Pluck + * + * Extracts a list of property values. + * + * @param {object} list + * @param {string} propertyName + */ + var pluck = function (list, propertyName) { + var output = []; + for (var i = 0, len = list.length; i < len; i++) { + var property = list[i][propertyName]; + if (output.indexOf(property) === -1) { + output.push(property); + } + } + return output; + }; -// Export -Validation.prototype.addAttribute('divisibleBy', divisibleByAttribute); + /** + * ReturnTrue + */ + var returnTrue = function () { + return true; + }; -/** - * Enum - */ -var enumAttribute = function(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * Some + * + * @param {object} arr + * @param {function} iterator + */ + var some = function (arr, iterator) { + return Array.prototype.some.apply(arr, [iterator]); + }; - if (attributeValue.indexOf(propertyValue) === -1) { - this.addError(); - } + (function () { - return callback(); + /** + * Validation + * + * @constructor + * @param {object} options + */ + var Validation = function (options) { -}; + // Save a reference to the ‘this’ + var self = this; -// Export -Validation.prototype.addAttribute('enum', enumAttribute); + var defaultOptions = { + singleError: true, + messages: errorMessages, + cache: false + }; -/** - * Except - */ -var exceptAttribute = function except(property, propertyValue, attributeValue, propertyAttributes, callback) { + each(defaultOptions, function (key, value) { - if (attributeValue.indexOf(propertyValue) !== -1) { - this.addError(); - } + if (isObject(value) && options[key]) { + self[key] = merge(options[key], defaultOptions[key]); - return callback(); + } else if (isObject(value) && !options[key]) { + self[key] = merge({}, defaultOptions[key]); -}; + } else { + self[key] = (isDefined(options[key])) ? options[key] : defaultOptions[key]; + } -// Export -Validation.prototype.addAttribute('except', exceptAttribute); + }); -/** - * Format - */ -Validation.prototype.addAttributeConstructor('format', function formatConstructor() { - - // Uložíme si referenci na this - var self = this; + this.errors = new ValidationError(this); - /** - * Formats - */ - var formats = { + }; /** - * date-time - * - * This should be a date in ISO 8601 format of YYYY-MM-DDThh:mm:ssZ in UTC - * time. This is the recommended form of date/timestamp. + * Attributes + * -------------------- */ - 'date-time': { - type: 'string', - pattern: /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ - }, + Validation.prototype.attributes = {}; /** - * date + * AddAttribute * - * This should be a date in the format of YYYY-MM-DD. It is recommended that you - * use the "date-time" format instead of "date" unless you need to transfer only the date part. + * @param {string} attributeName + * @param {function} attributeFn */ - date: function(input) { - if (isString(input)) { - return input.match(/^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/); - } - if (isObject(input)) { - return Object.prototype.toString.call(input) === '[object Date]'; - } - return false; - }, + Validation.prototype.addAttribute = function (attributeName, attributeFn) { + return Validation.prototype.attributes[attributeName] = attributeFn; + }; /** - * time + * AddAttributeConstructor * - * This should be a time in the format of hh:mm:ss. + * @param {string} attributeName + * @param {function} attributeConstructor */ - 'time': { - type: 'string', - pattern: /^\d{2}:\d{2}:\d{2}$/ - }, + Validation.prototype.addAttributeConstructor = function (attributeName, attributeConstructor) { + return Validation.prototype.attributes[attributeName] = attributeConstructor(); + }; /** - * utc-milisec - * - * This should be the difference, measured in milliseconds, between the specified - * time and midnight, 00:00 of January 1, 1970 UTC. The value - * should be a number (integer or float). + * AdditionalProperties */ - 'utc-milisec': { - type: 'number' - }, + var additionalPropertiesAttribute = function additionalProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { + + var self = this; + + /** + * { + * additionalProperties: true, + * ... + * } + */ + if (attributeValue === true) { + return callback(); + } - /** - * regex - * - * This should be a time in the format of hh:mm:ss. - */ - regex: function(input) { - return input && input.test && input.exec; - }, + // Filter the forbidden properties + var propertyKeys = keys(propertyValue); + var forbiddenProperties = filter(propertyKeys, function (key) { + return !propertyAttributes.properties[key]; + }); - /** - * color - * - * This is a CSS color (like "#FF0000" or "red"), based on CSS 2.1. - */ - 'color': { - type: 'string' - }, + if (isEmpty(forbiddenProperties)) { + return callback(); + } - /** - * style - * - * This is a CSS style definition (like "color: red; background-color:#FFF"), based on CSS 2.1. - */ - 'style': { - type: 'string' - }, + /** + * { + * additionalProperties: false, + * ... + * } + */ + if (attributeValue === false) { - /** - * phone - * - * This should be a phone number. - */ - 'phone': { - type: 'number' - }, + forbiddenProperties.forEach(function (forbiddenProperty) { + this.addError({ + property: this.joinPath(property, forbiddenProperty), + propertyValue: propertyValue[forbiddenProperty] + }); + }, this); - /** - * uri - * - * This value should be a URI. - */ - 'uri': { - type: 'string', - pattern: /^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/ - }, + return callback(); - /** - * email - * - * This should be an email address. - */ - 'email': { - type: 'string', - pattern: /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/ - }, + } - /** - * ip-address - * - * This should be an ip version 4 address. - */ - 'ip-address': { - type: 'string', - pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ - }, + /** + * { + * additionalProperties: { + * type: 'string', + * ... + * }, + * ... + * } + */ + if (isObject(attributeValue)) { + return each(forbiddenProperties, function (index, key, callback) { + return self.validateSchema( + propertyValue[key], attributeValue, property + key, callback); + }, callback); + } - /** - * ipv6 - * - * This should be an ip version 6 address. - */ - 'ipv6': { - type: 'string', - pattern: /(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-f\d]{1,4}:)*[a-f\d]{1,4})?::(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)/ - }, + }; + + // Export + Validation.prototype.addAttribute('additionalProperties', additionalPropertiesAttribute); /** - * host-name - * - * This should be a host-name. + * DivisibleBy */ - 'host-name': { - type: 'string' - } + var divisibleByAttribute = function divisibleBy(property, propertyValue, attributeValue, propertyAttributes, callback) { - }; + if (attributeValue === 0) { + throw new Error('The value of this attribute should not be 0.'); + } - /** - * CustomFormats - * -------------------- - */ - formats.alpha = { - required: true, - type: 'string', - pattern: /^[a-zA-Z]+$/ - }; + if (isNumber(propertyValue) && (propertyValue % attributeValue !== 0)) { + this.addError(); + } - formats.alphanumeric = { - required: true, - type: ['string', 'number'], - pattern: /^[a-zA-Z0-9]+$/ - }; + return callback(); - formats.decimal = function(input) { - if (!isNumber(input)) return false; - return (input + '').match(/^[0-9]+(\.[0-9]{1,2})?$/); - }; + }; - formats.percentage = { - required: true, - type: ['string', 'number'], - pattern: /^-?[0-9]{0,2}(\.[0-9]{1,2})?$|^-?(100)(\.[0]{1,2})?$/, - minimum: -100, - maximum: 100 - }; + // Export + Validation.prototype.addAttribute('divisibleBy', divisibleByAttribute); - formats.port = { - required: true, - type: ['string', 'number'], - pattern: /\:\d+/ - }; + /** + * Enum + */ + var enumAttribute = function (property, propertyValue, attributeValue, propertyAttributes, callback) { - /** - * Aliases - * -------------------- - */ - var aliases = { - url: 'uri', - ip: 'ip-address', - ipv4: 'ip-address', - host: 'host-name', - hostName: 'host-name' - }; + if (attributeValue.indexOf(propertyValue) === -1) { + this.addError(); + } - // Apply aliases - each(aliases, function(alias, format) { - formats[alias] = formats[format]; - }); + return callback(); - // Export - return function format(property, propertyValue, attributeValue, propertyAttributes, callback) { + }; - /** - * { - * format: { - * type: 'string', - * pattern: /abc/ - * ... - * } - * ... - * } - */ - if (isObject(attributeValue)) { - return this.validateProperty(property, propertyValue, attributeValue, callback); - } + // Export + Validation.prototype.addAttribute('enum', enumAttribute); /** - * { - * format: 'lorem ipsum dolor', - * ... - * } + * Except */ - if (isString(attributeValue) && !hasProperty(formats, attributeValue)) { - throw new Error('The format ‘' + attributeValue + '’ is not supported.'); - } + var exceptAttribute = function except(property, propertyValue, attributeValue, propertyAttributes, callback) { - /** - * { - * format: 'phone', - * ... - * } - */ - if (isString(attributeValue)) { - - var fn = formats[attributeValue]; - - if (isFunction(fn)) { - var noError = fn(propertyValue); - if (!noError) { + if (attributeValue.indexOf(propertyValue) !== -1) { this.addError(); } + return callback(); - } - if (isObject(fn)) { - return this.validateProperty(property, propertyValue, fn, callback); - } + }; - } + // Export + Validation.prototype.addAttribute('except', exceptAttribute); - }; + /** + * Format + */ + Validation.prototype.addAttributeConstructor('format', function formatConstructor() { -}); + // Uložíme si referenci na this + var self = this; -/** - * Length - */ -var lengthAttribute = function length(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * Formats + */ + var formats = { + + /** + * date-time + * + * This should be a date in ISO 8601 format of YYYY-MM-DDThh:mm:ssZ in UTC + * time. This is the recommended form of date/timestamp. + */'date-time': { + type: 'string', + pattern: /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ + }, + + /** + * date + * + * This should be a date in the format of YYYY-MM-DD. It is recommended that you + * use the "date-time" format instead of "date" unless you need to transfer only the date part. + */ + date: function (input) { + if (isString(input)) { + return input.match(/^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/); + } + if (isObject(input)) { + return Object.prototype.toString.call(input) === '[object Date]'; + } + return false; + }, + + /** + * time + * + * This should be a time in the format of hh:mm:ss. + */'time': { + type: 'string', + pattern: /^\d{2}:\d{2}:\d{2}$/ + }, + + /** + * utc-milisec + * + * This should be the difference, measured in milliseconds, between the specified + * time and midnight, 00:00 of January 1, 1970 UTC. The value + * should be a number (integer or float). + */'utc-milisec': { + type: 'number' + }, + + /** + * regex + * + * This should be a time in the format of hh:mm:ss. + */ + regex: function (input) { + return input && input.test && input.exec; + }, + + /** + * color + * + * This is a CSS color (like "#FF0000" or "red"), based on CSS 2.1. + */'color': { + type: 'string' + }, + + /** + * style + * + * This is a CSS style definition (like "color: red; background-color:#FFF"), based on CSS 2.1. + */'style': { + type: 'string' + }, + + /** + * phone + * + * This should be a phone number. + */'phone': { + type: 'number' + }, + + /** + * uri + * + * This value should be a URI. + */'uri': { + type: 'string', + pattern: /^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/ + }, + + /** + * email + * + * This should be an email address. + */'email': { + type: 'string', + pattern: /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/ + }, + + /** + * ip-address + * + * This should be an ip version 4 address. + */'ip-address': { + type: 'string', + pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ + }, + + /** + * ipv6 + * + * This should be an ip version 6 address. + */'ipv6': { + type: 'string', + pattern: /(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-f\d]{1,4}:)*[a-f\d]{1,4})?::(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)/ + }, + + /** + * host-name + * + * This should be a host-name. + */'host-name': { + type: 'string' + } - if (isString(propertyValue) && propertyValue.length !== attributeValue) { - this.addError(); - } + }; - return callback(); + /** + * CustomFormats + * -------------------- + */ + formats.alpha = { + required: true, + type: 'string', + pattern: /^[a-zA-Z]+$/ + }; -}; + formats.alphanumeric = { + required: true, + type: ['string', 'number'], + pattern: /^[a-zA-Z0-9]+$/ + }; -// Export -Validation.prototype.addAttribute('length', lengthAttribute); + formats.decimal = function (input) { + if (!isNumber(input)) return false; + return (input + '').match(/^[0-9]+(\.[0-9]{1,2})?$/); + }; -/** - * Maximum - */ -var maximumAttribute = function maximum(property, propertyValue, attributeValue, propertyAttributes, callback) { + formats.percentage = { + required: true, + type: ['string', 'number'], + pattern: /^-?[0-9]{0,2}(\.[0-9]{1,2})?$|^-?(100)(\.[0]{1,2})?$/, + minimum: -100, + maximum: 100 + }; - if (isNumber(propertyValue)) { - if ((propertyAttributes.exclusiveMaximum && propertyValue >= attributeValue) || (propertyValue > attributeValue)) { - this.addError(); - } - } + formats.port = { + required: true, + type: ['string', 'number'], + pattern: /\:\d+/ + }; - return callback(); + /** + * Aliases + * -------------------- + */ + var aliases = { + url: 'uri', + ip: 'ip-address', + ipv4: 'ip-address', + host: 'host-name', + hostName: 'host-name' + }; -}; + // Apply aliases + each(aliases, function (alias, format) { + formats[alias] = formats[format]; + }); -// Export -Validation.prototype.addAttribute('maximum', maximumAttribute); + // Export + return function format(property, propertyValue, attributeValue, propertyAttributes, callback) { + + /** + * { + * format: { + * type: 'string', + * pattern: /abc/ + * ... + * } + * ... + * } + */ + if (isObject(attributeValue)) { + return this.validateProperty(property, propertyValue, attributeValue, callback); + } -/** - * MaxItems - */ -var maxItemsAttribute = function maxItems(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * { + * format: 'lorem ipsum dolor', + * ... + * } + */ + if (isString(attributeValue) && !hasProperty(formats, attributeValue))  { + throw new Error('The format ‘' + attributeValue + '’ is not supported.'); + } - if (isArray(propertyValue) && propertyValue.length > attributeValue) { - this.addError(); - } + /** + * { + * format: 'phone', + * ... + * } + */ + if (isString(attributeValue)) { - return callback(); + var fn = formats[attributeValue]; -}; + if (isFunction(fn)) { + var noError = fn(propertyValue); + if (!noError) { + this.addError(); + } + return callback(); + } -// Export -Validation.prototype.addAttribute('maxItems', maxItemsAttribute); + if (isObject(fn)) { + return this.validateProperty(property, propertyValue, fn, callback); + } -/** - * MaxLength - */ -var maxLengthAttribute = function maxLength(property, propertyValue, attributeValue, propertyAttributes, callback) { + } - if (isString(propertyValue) && propertyValue.length > attributeValue) { - this.addError(); - } + }; - return callback(); + }); -}; + /** + * Length + */ + var lengthAttribute = function length(property, propertyValue, attributeValue, propertyAttributes, callback) { -// Export -Validation.prototype.addAttribute('maxLength', maxLengthAttribute); + if (isString(propertyValue) && propertyValue.length !== attributeValue) { + this.addError(); + } -/** - * Minimum - */ -var minimumAttribute = function minimum(property, propertyValue, attributeValue, propertyAttributes, callback) { + return callback(); - if (isNumber(propertyValue)) { - if ((propertyAttributes.exclusiveMinimum && propertyValue <= attributeValue) || (propertyValue < attributeValue)) { - this.addError(); - } - } + }; - return callback(); + // Export + Validation.prototype.addAttribute('length', lengthAttribute); -}; + /** + * Maximum + */ + var maximumAttribute = function maximum(property, propertyValue, attributeValue, propertyAttributes, callback) { -// Export -Validation.prototype.addAttribute('minimum', minimumAttribute); + if (isNumber(propertyValue)) { + if ((propertyAttributes.exclusiveMaximum && propertyValue >= attributeValue) || (propertyValue > attributeValue)) { + this.addError(); + } + } + return callback(); -/** - * MinItems - */ -var minItems = function minItems(property, propertyValue, attributeValue, propertyAttributes, callback) { + }; - if (isArray(propertyValue) && propertyValue.length < attributeValue) { - this.addError(); - } + // Export + Validation.prototype.addAttribute('maximum', maximumAttribute); - return callback(); + /** + * MaxItems + */ + var maxItemsAttribute = function maxItems(property, propertyValue, attributeValue, propertyAttributes, callback) { -}; + if (isArray(propertyValue) && propertyValue.length > attributeValue) { + this.addError(); + } -// Export -Validation.prototype.addAttribute('minItems', minItems); + return callback(); + + }; -/** - * MinLength - */ -var minLengthAttribute = function minLength(property, propertyValue, attributeValue, propertyAttributes, callback) { + // Export + Validation.prototype.addAttribute('maxItems', maxItemsAttribute); - if (isString(propertyValue) && propertyValue.length < attributeValue) { - this.addError(); - } + /** + * MaxLength + */ + var maxLengthAttribute = function maxLength(property, propertyValue, attributeValue, propertyAttributes, callback) { + + if (isString(propertyValue) && propertyValue.length > attributeValue) { + this.addError(); + } - return callback(); + return callback(); -}; + }; -// Export -Validation.prototype.addAttribute('minLength', minLengthAttribute); + // Export + Validation.prototype.addAttribute('maxLength', maxLengthAttribute); -/** - * Pattern - */ -var patternAttribute = function pattern(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * Minimum + */ + var minimumAttribute = function minimum(property, propertyValue, attributeValue, propertyAttributes, callback) { - if (isString(propertyValue) && !propertyValue.match(attributeValue)) { - this.addError(); - } + if (isNumber(propertyValue)) { + if ((propertyAttributes.exclusiveMinimum && propertyValue <= attributeValue) || (propertyValue < attributeValue)) { + this.addError(); + } + } - return callback(); + return callback(); -}; + }; -// Export -Validation.prototype.addAttribute('pattern', patternAttribute); + // Export + Validation.prototype.addAttribute('minimum', minimumAttribute); -(function() { - /** - * PatternProperties - */ - var attribute = function patternProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * MinItems + */ + var minItems = function minItems(property, propertyValue, attributeValue, propertyAttributes, callback) { - // Saves a reference to ‘this’ - var self = this; + if (isArray(propertyValue) && propertyValue.length < attributeValue) { + this.addError(); + } - // Skip - if (isEmpty(attributeValue)) { - return callback(); - } + return callback(); - var matches = {}; - var patterns = keys(attributeValue); + }; + + // Export + Validation.prototype.addAttribute('minItems', minItems); - each(propertyValue, function(key, value) { + /** + * MinLength + */ + var minLengthAttribute = function minLength(property, propertyValue, attributeValue, propertyAttributes, callback) { - each(patterns, function(index, pattern) { - if (key.match(new RegExp(pattern))) { - matches[key] = attributeValue[pattern]; + if (isString(propertyValue) && propertyValue.length < attributeValue) { + this.addError(); } - }); - }); + return callback(); - if (isEmpty(matches)) { - return callback(); - } + }; - each(matches, function(propertyName, propertySchema, callback) { - return self.validateSchema( - propertyValue[propertyName], - propertySchema, - self.joinPath(property, propertyName), - callback - ); - }, callback); + // Export + Validation.prototype.addAttribute('minLength', minLengthAttribute); - }; + /** + * Pattern + */ + var patternAttribute = function pattern(property, propertyValue, attributeValue, propertyAttributes, callback) { + + if (isString(propertyValue) && !propertyValue.match(attributeValue)) { + this.addError(); + } + + return callback(); - // Export - Validation.prototype.addAttribute('patternProperties', attribute); + }; -}()); + // Export + Validation.prototype.addAttribute('pattern', patternAttribute); -/** - * Required - */ -var requiredAttribute = function required(property, propertyValue, attributeValue, propertyAttributes, callback) { + (function () { - if (attributeValue) { + /** + * PatternProperties + */ + var attribute = function patternProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { - var undefinedCondition = isUndefined(propertyValue); - var emptyCondition = (isString(propertyValue) || isArray(propertyValue) || isObject(propertyValue)) && isEmpty(propertyValue); + // Saves a reference to ‘this’ + var self = this; - if (undefinedCondition || emptyCondition) { - this.addError(); - } + // Skip + if (isEmpty(attributeValue)) { + return callback(); + } - } + var matches = {}; + var patterns = keys(attributeValue); + + each(propertyValue, function (key, value) { - return callback(); + each(patterns, function (index, pattern) { + if (key.match(new RegExp(pattern))) { + matches[key] = attributeValue[pattern]; + } + }); -}; + }); -// Export -Validation.prototype.addAttribute('required', requiredAttribute); + if (isEmpty(matches)) { + return callback(); + } -/** - * Type - */ -var typeConstructor = function typeConstructor() { + each(matches, function (propertyName, propertySchema, callback) { + return self.validateSchema( + propertyValue[propertyName], propertySchema, self.joinPath(property, propertyName), callback); + }, callback); - /** - * Types - */ - var types = { - 'string': isString, - 'number': isNumber, - 'function': isFunction, - 'boolean': isBoolean, - 'object': isObject, - 'array': isArray, - 'integer': isInteger, - 'int': isInteger, - 'null': isNull, - 'any': returnTrue - }; + }; + + // Export + Validation.prototype.addAttribute('patternProperties', attribute); - // Export - return function type(property, propertyValue, attributeValue, propertyAttributes, callback) { + }()); /** - * { - * type: ['string', 'number'] - * } + * Required */ - if (isArray(attributeValue)) { + var requiredAttribute = function required(property, propertyValue, attributeValue, propertyAttributes, callback) { - var noError = attributeValue.some(function(type) { + if (attributeValue) { - if (!hasProperty(types, type)) { - throw new Error('Type ‘' + attributeValue + '’ is not supported.'); - } + var undefinedCondition = isUndefined(propertyValue); + var emptyCondition = (isString(propertyValue) || isArray(propertyValue) || isObject(propertyValue)) && isEmpty(propertyValue); - return types[type](propertyValue); + if (undefinedCondition || emptyCondition) { + this.addError(); + } - }); + } - if (!noError) { - this.errors.addError(); - } + return callback(); + + }; - return callback(); + // Export + Validation.prototype.addAttribute('required', requiredAttribute); /** - * { - * type: 'string' - * } + * Type */ - } else { + var typeConstructor = function typeConstructor() { + + /** + * Types + */ + var types = { + 'string': isString, + 'number': isNumber, + 'function': isFunction, + 'boolean': isBoolean, + 'object': isObject, + 'array': isArray, + 'integer': isInteger, + 'int': isInteger, + 'null': isNull, + 'any': returnTrue + }; - if (!hasProperty(types, attributeValue)) { - throw new Error('Type ‘' + attributeValue + '’ is not supported.'); - } + // Export + return function type(property, propertyValue, attributeValue, propertyAttributes, callback) { - if (!types[attributeValue](propertyValue)) { - this.addError(); - } + /** + * { + * type: ['string', 'number'] + * } + */ + if (isArray(attributeValue)) { - return callback(); + var noError = attributeValue.some(function (type) { - } + if (!hasProperty(types, type)) { + throw new Error('Type ‘' + attributeValue + '’ is not supported.'); + } - }; + return types[type](propertyValue); -}; + }); -// Export -Validation.prototype.addAttributeConstructor('type', typeConstructor); - -(function() { + if (!noError) { + this.errors.addError(); + } - /** - * UniqueItems - */ - var attribute = function uniqueItems(property, propertyValue, attributeValue, propertyAttributes, callback) { + return callback(); - var self = this; + /** + * { + * type: 'string' + * } + */ + } else { - each(propertyValue, function(index, value) { + if (!hasProperty(types, attributeValue)) { + throw new Error('Type ‘' + attributeValue + '’ is not supported.'); + } - if (isString(value)) { - if ((propertyValue.indexOf(value) < index)) { - self.addError(); - } - } + if (!types[attributeValue](propertyValue)) { + this.addError(); + } - if (isObject(value) || isArray(value)) { - propertyValue.forEach(function(subValue, subIndex) { + return callback(); - if (subIndex !== index) { - if (isEqual(value, subValue)) { - self.addError({ - property: self.joinPath(property, subIndex) - }); - } } - }); - } + }; - }); + }; - return callback(); + // Export + Validation.prototype.addAttributeConstructor('type', typeConstructor); - }; + (function () { - // Export - Validation.prototype.addAttribute('uniqueItems', attribute); + /** + * UniqueItems + */ + var attribute = function uniqueItems(property, propertyValue, attributeValue, propertyAttributes, callback) { -}()); + var self = this; -/** - * Error - * - * @constructor - */ -var ValidationError = function(parent) { + each(propertyValue, function (index, value) { - this.length = 0; - - this.errorMessages = parent.messages; + if (isString(value)) { + if ((propertyValue.indexOf(value) < index)) { + self.addError(); + } + } -}; + if (isObject(value) || isArray(value)) { + propertyValue.forEach(function (subValue, subIndex) { -ValidationError.prototype.renderErrorMessage = function(error) { + if (subIndex !== index) { + if (isEqual(value, subValue))  { + self.addError({ + property: self.joinPath(property, subIndex) + }); + } + } - var errorMessage = this.errorMessages[error.attributeName]; + }); + } - if (errorMessage && isFunction(errorMessage)) { - return errorMessage( - error.property, - error.propertyValue, - error.attributeValue - ); - } + }); - if (errorMessage && isString(errorMessage)) { + return callback(); - [ - 'property', - 'propertyValue', - 'attributeValue' - ].forEach(function(placeholder) { - errorMessage = errorMessage.replace(new RegExp('{{' + placeholder + '}}', 'g'), error[placeholder]); - }); + }; - // Deprecated - errorMessage = errorMessage.replace(/{{validator}}/g, error['attributeValue']); + // Export + Validation.prototype.addAttribute('uniqueItems', attribute); - return errorMessage.replace(/\s+/g, ' '); + }()); - } + /** + * Error + * + * @constructor + */ + var ValidationError = function (parent) { - return error.message; + this.length = 0; -}; + this.errorMessages = parent.messages; -ValidationError.prototype.push = function(error) { + }; - this[this.length] = { + ValidationError.prototype.renderErrorMessage = function (error) { - property: error.property, - propertyValue: error.propertyValue, - attributeName: error.attributeName, - attributeValue: error.attributeValue, - message: this.renderErrorMessage(error), + var errorMessage = this.errorMessages[error.attributeName]; - // Deprecated - validator: error.attributeName, - validatorName: error.attributeName, - validatorValue: error.attributeValue + if (errorMessage && isFunction(errorMessage)) { + return errorMessage( + error.property, error.propertyValue, error.attributeValue); + } - }; + if (errorMessage && isString(errorMessage)) { - this.length += 1; + [ + 'property', + 'propertyValue', + 'attributeValue' + ].forEach(function (placeholder) { + errorMessage = errorMessage.replace(new RegExp('{{' + placeholder + '}}', 'g'), error[placeholder]); + }); -}; + // Deprecated + errorMessage = errorMessage.replace(/{{validator}}/g, error['attributeValue']); -/** - * GetProperties - */ -ValidationError.prototype.getProperties = function() { - return pluck(this, 'property'); -}; + return errorMessage.replace(/\s+/g, ' '); -/** - * GetMessages - */ -ValidationError.prototype.getMessages = function() { - return pluck(this, 'message'); -}; + } -/** - * Messages - * -------------------- - */ -var errorMessages = { + return error.message; - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - required: function(property, propertyValue, attributeValue) { - return 'The ‘' + property + '’ property is required.'; - }, + }; - /** - * MinLength - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - minLength: function(property, propertyValue, attributeValue) { - return [ - 'The ' + property + ' property must be at least ' + attributeValue + ' characters.', - 'The length of the property is ' + propertyValue.length + '.' - ].join(' '); - }, + ValidationError.prototype.push = function (error) { - /** - * MaxLength - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - maxLength: function(property, propertyValue, attributeValue) { - return [ - 'The ' + property + ' property must not exceed ' + attributeValue + ' characters.', - 'The length of the property is ' + propertyValue.length + '.' - ].join(' '); - }, + this[this.length] = { - /** - * MaxLength - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - length: function(property, propertyValue, attributeValue) { - return [ - 'The ' + property + ' property must be exactly ' + attributeValue + ' characters.', - 'The length of the property is ' + propertyValue.length + '.' - ].join(' '); - }, - - /** - * Format - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - format: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', - 'The current value of the property is ‘' + propertyValue + '’' - ].join(' '); - }, + property: error.property, + propertyValue: error.propertyValue, + attributeName: error.attributeName, + attributeValue: error.attributeValue, + message: this.renderErrorMessage(error), - /** - * Type - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - type: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', - 'The type of the property is ‘' + detectType(propertyValue) + '’' - ].join(' '); - }, + // Deprecated + validator: error.attributeName, + validatorName: error.attributeName, + validatorValue: error.attributeValue - /** - * Except - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - except: function(property, propertyValue, attributeValue) { - return; - }, + }; - /** - * Minimum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - minimum: function(property, propertyValue, attributeValue) { - return [ - 'The minimum value of the ‘' + property + '’ must be ' + attributeValue + '.', - 'The current value of the property is ‘' + propertyValue + '’' - ].join(' '); - }, + this.length += 1; - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - maximum: function(property, propertyValue, attributeValue) { - return [ - 'The maximum value of the ‘' + property + '’ must be ' + attributeValue + '.', - 'The current value of the property is ‘' + propertyValue + '’.' - ].join(' '); - }, + }; - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - pattern: function(property, propertyValue, attributeValue) { - return 'The ‘' + property + '’ does not match the ‘' + attributeValue + '’ pattern.'; - }, + /** + * GetProperties + */ + ValidationError.prototype.getProperties = function () { + return pluck(this, 'property'); + }; - /** - * MaxItems - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - maxItems: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must not contain more than ‘' + attributeValue + '’ items.', - 'Currently it contains ‘' + propertyValue.items + '’ items.' - ].join(' '); - }, + /** + * GetMessages + */ + ValidationError.prototype.getMessages = function () { + return pluck(this, 'message'); + }; - /** - * MinItems - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - minItems: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must contain at least ‘' + attributeValue + '’ items.', - 'Currently it contains ‘' + propertyValue.items + '’ items.' - ].join(' '); - }, + /** + * Messages + * -------------------- + */ + var errorMessages = { - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - divisibleBy: function(property, propertyValue, attributeValue) { - return 'The ‘' + property + '’ is not divisible by ‘' + attributeValue + '’.'; - }, + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + required: function (property, propertyValue, attributeValue) { + return 'The ‘' + property + '’ property is required.'; + }, - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - uniqueItems: function(property, propertyValue, attributeValue) { - return 'All items in the ‘' + property + '’ property must be unique.'; - }, + /** + * MinLength + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + minLength: function (property, propertyValue, attributeValue) { + return ['The ' + property + ' property must be at least ' + attributeValue + ' characters.', 'The length of the property is ' + propertyValue.length + '.'].join(' '); + }, - /** - * Enum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - 'enum': function(property, propertyValue, attributeValue) { - return 'Value of the ‘' + property + '’ must be ' + attributeValue.join(' or ') + '.'; - } + /** + * MaxLength + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + maxLength: function (property, propertyValue, attributeValue) { + return ['The ' + property + ' property must not exceed ' + attributeValue + ' characters.', 'The length of the property is ' + propertyValue.length + '.'].join(' '); + }, -}; - -/** - * GetProperty - * - * @param {string} property - * @param {object} source - */ -Validation.prototype.getProperty = function(property, source) { - var tree = property.match(/([a-zA-Z0-9\s]+)/g); - return tree.reduce(function(previousValue, currentValue, index) { - return (previousValue && previousValue[currentValue]) ? previousValue[currentValue] : undefined; - }, source); -}; - -/** - * JoinPath - * - * @param {string} path - * @param {string} property - */ -Validation.prototype.joinPath = function(path, property) { - - // If the ‘path’ is undefined (object), convert the path to a string - path = path || ''; - - // Converts the ‘property’ to a string - property = property + ''; - - if (property.match(/^[a-zA-Z]+$/)) { - return (path) ? (path + '.' + property) : property; - } else if (property.match(/\d+/)) { - return path + '[' + property + ']'; - } else { - return path + '["' + property + '"]'; - } + /** + * MaxLength + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + length: function (property, propertyValue, attributeValue) { + return ['The ' + property + ' property must be exactly ' + attributeValue + ' characters.', 'The length of the property is ' + propertyValue.length + '.'].join(' '); + }, -}; + /** + * Format + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + format: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', 'The current value of the property is ‘' + propertyValue + '’'].join(' '); + }, -/** - * Validation.validate - * - * @param {object} instance - * @param {object} schema - * @param {boolean} singleError - * @param {function} callback - */ -Validation.prototype.validate = function(instance, schema, callback) { + /** + * Type + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + type: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', 'The type of the property is ‘' + detectType(propertyValue) + '’'].join(' '); + }, - // Save a reference to the ‘this’ - var self = this; + /** + * Except + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + except: function (property, propertyValue, attributeValue) { + return; + }, - this.instance = instance; - this.schema = schema; + /** + * Minimum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + minimum: function (property, propertyValue, attributeValue) { + return ['The minimum value of the ‘' + property + '’ must be ' + attributeValue + '.', 'The current value of the property is ‘' + propertyValue + '’'].join(' '); + }, - /** - * Basic Types - */ - var basicTypes = [ - 'string', - 'number', - 'function', - 'boolean', - 'integer', - 'int', - 'null' - ]; + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + maximum: function (property, propertyValue, attributeValue) { + return ['The maximum value of the ‘' + property + '’ must be ' + attributeValue + '.', 'The current value of the property is ‘' + propertyValue + '’.'].join(' '); + }, - /** - * Object Types - */ - var objectTypes = [ - 'object', - 'array' - ]; + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + pattern: function (property, propertyValue, attributeValue) { + return 'The ‘' + property + '’ does not match the ‘' + attributeValue + '’ pattern.'; + }, - /** - * CallbackProxy - */ - var callbackProxy = function() { - if (self.errors.length !== 0) { - return callback(self.errors); - } else { - return callback(); - } - }; + /** + * MaxItems + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + maxItems: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must not contain more than ‘' + attributeValue + '’ items.', 'Currently it contains ‘' + propertyValue.items + '’ items.'].join(' '); + }, - /** - * { - * type: 'string', - * ... - * } - */ - if (basicTypes.indexOf(schema.type) !== -1) { - return this.validateProperty(undefined, instance, schema, callbackProxy); - } + /** + * MinItems + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + minItems: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must contain at least ‘' + attributeValue + '’ items.', 'Currently it contains ‘' + propertyValue.items + '’ items.'].join(' '); + }, - /** - * { - * type: 'object', - * ... - * } - */ - if (objectTypes.indexOf(schema.type) !== -1) { + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + divisibleBy: function (property, propertyValue, attributeValue) { + return 'The ‘' + property + '’ is not divisible by ‘' + attributeValue + '’.'; + }, - if (isString(instance)) { - try { - instance = JSON.parse(instance); - } catch(parseError) { + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + uniqueItems: function (property, propertyValue, attributeValue) { + return 'All items in the ‘' + property + '’ property must be unique.'; + }, + /** + * Enum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */'enum': function (property, propertyValue, attributeValue) { + return 'Value of the ‘' + property + '’ must be ' + attributeValue.join(' or ') + '.'; } - } - return this.validateSchema(instance, schema, '', callbackProxy); + }; - } + /** + * GetProperty + * + * @param {string} property + * @param {object} source + */ + Validation.prototype.getProperty = function (property, source) { + var tree = property.match(/([a-zA-Z0-9\s]+)/g); + return tree.reduce(function (previousValue, currentValue, index) { + return (previousValue && isDefined(previousValue[currentValue])) ? previousValue[currentValue] : undefined; + }, source); + }; - /** - * { - * type: ???, - * ... - * } - */ - if (schema.type === 'any' || !schema.type) { + /** + * JoinPath + * + * @param {string} path + * @param {string} property + */ + Validation.prototype.joinPath = function (path, property) { - if (isString(instance)) { - try { - instance = JSON.parse(instance); - return this.validateSchema(instance, schema, '', callbackProxy); - } catch(parseError2) { + // If the ‘path’ is undefined (object), convert the path to a string + path = path || ''; + + // Converts the ‘property’ to a string + property = property + ''; + if (property.match(/^[a-zA-Z]+$/)) { + return (path) ? (path + '.' + property) : property; + } else if (property.match(/\d+/)) { + return path + '[' + property + ']'; + } else { + return path + '["' + property + '"]'; } - } - if (isObject(instance) || isArray (instance)) { - return this.validateSchema(instance, schema, '', callbackProxy); - } + }; - return this.validateProperty(undefined, instance, schema, callbackProxy); + /** + * Validation.validate + * + * @param {object} instance + * @param {object} schema + * @param {boolean} singleError + * @param {function} callback + */ + Validation.prototype.validate = function (instance, schema, callback) { - } + // Save a reference to the ‘this’ + var self = this; -}; + this.instance = instance; + this.schema = schema; -/** - * Validation.validateItems - * - * @param {object} instance - * @param {object} schema - * @param {string} path - * @param {function} callback - */ -Validation.prototype.validateItems = function(instance, schema, path, callback) { + /** + * Basic Types + */ + var basicTypes = [ + 'string', + 'number', + 'function', + 'boolean', + 'integer', + 'int', + 'null' + ]; - // Save a reference to the ‘this’ - var self = this; + /** + * Object Types + */ + var objectTypes = [ + 'object', + 'array' + ]; - /** - * { - * type: 'array' - * items: [ - * { - * type: 'string' - * }, - * { - * type: 'number' - * }, - * ... - * ], - * ... - * } - */ - if (isArray(schema.items)) { - - // Additional items are allowed - if (isUndefined(schema.additionalItems) || schema.additionalItems === true) { - return each(schema.items, function(itemIndex, itemSchema, callback) { - return self.validateSchema( - instance[itemIndex], - itemSchema, - self.joinPath(path, itemIndex), - callback - ); - }, callback); - } - - return each(instance, function(itemIndex, itemValue, callback) { - - // The ‘additionalItems’ attribute is a schema that defines - // the schema of the additional items - if (schema.items[itemIndex] || isObject(schema.additionalItems)) { - return self.validateSchema( - itemValue, - schema.items[itemIndex], - self.joinPath(path, itemIndex), - callback - ); - } + /** + * CallbackProxy + */ + var callbackProxy = function () { + if (self.errors.length !== 0) { + return callback(self.errors); + } else { + return callback(); + } + }; - // Additional items are disallowed - if (schema.additionalItems === false) { - self.errors.push({ - property: self.joinPath(path, itemIndex), - propertyValue: itemValue, - attributeName: 'additionalItems', - attributeValue: false - }); - return callback(); + /** + * { + * type: 'string', + * ... + * } + */ + if (basicTypes.indexOf(schema.type) !== -1) { + return this.validateProperty(undefined, instance, schema, callbackProxy); } - }, callback); + /** + * { + * type: 'object', + * ... + * } + */ + if (objectTypes.indexOf(schema.type) !== -1) { - } + if (isString(instance)) { + try { + instance = JSON.parse(instance); + } catch (parseError) { - /** - * { - * type: 'array' - * items: { - * type: 'string' - * }, - * ... - * } - */ - if (isObject(schema.items) && instance && !isEmpty(instance)) { - return each(instance, function(itemIndex, itemValue, callback) { - return self.validateSchema( - instance[itemIndex], - schema.items, - self.joinPath(path, itemIndex), - callback - ); - }, callback); - } else { - return callback(); - } + } + } -}; + return this.validateSchema(instance, schema, '', callbackProxy); -/** - * Validation.validateProperties - * - * @param {object} instance - * @param {object} schema - * @param {string} path - * @param {function} callback - */ -Validation.prototype.validateProperties = function(instance, schema, path, callback) { - - // Save a reference to the ‘this’ - var self = this; + } - // Goes - return each(schema.properties, function(property, propertyAttributes, callback) { + /** + * { + * type: ???, + * ... + * } + */ + if (schema.type === 'any' || !schema.type) { + + if (isString(instance)) { + try { + instance = JSON.parse(instance); + return this.validateSchema(instance, schema, '', callbackProxy); + } catch (parseError2) { + + } + } + + if (isObject(instance) || isArray(instance)) { + return this.validateSchema(instance, schema, '', callbackProxy); + } - var isObject = propertyAttributes.type === 'object' && propertyAttributes.properties, - isArray = propertyAttributes.type === 'array'; + return this.validateProperty(undefined, instance, schema, callbackProxy); - // Get the value of property (instance[property]) - var propertyValue = self.getProperty(property, instance); - var propertyPath = self.joinPath(path, property); + } + + }; /** - * { - * type: 'object', - * properties: { - * user: { - * type: 'object', - * properties: { - * ... - * } - * } - * } - * } + * Validation.validateItems + * + * @param {object} instance + * @param {object} schema + * @param {string} path + * @param {function} callback */ - if (isObject || isArray) { - return self.validateSchema( - propertyValue, - schema.properties[property], - propertyPath, - callback - ); - } else { - return self.validateProperty( - propertyPath, - propertyValue, - propertyAttributes, - callback - ); - } - - }, callback); - -}; - -/** - * Validation.validateProperty - * - * @param {string} propertyName - * @param {object} propertyAttributes - * @param {string|object} propertyValue - * @param {boolean} singleError - * @param {function} callback - */ -Validation.prototype.validateProperty = function(property, propertyValue, propertyAttributes, callback) { - - // Save a reference to the ‘this’ - var self = this; - - var context = {}; - - [ - 'validateItems', - 'validateProperties', - 'validateSchema', - 'validateProperty', - 'getProperty', - 'attributes', - 'errors', - 'joinPath' - ].forEach(function(key) { - context[key] = this[key]; - }, self); + Validation.prototype.validateItems = function (instance, schema, path, callback) { - /** - * Iterator - * - * @param {string} attributeName - * @param {function} attributeFn - * @param {function} callback - */ - var iterator = function(attributeName, attributeFn, callback) { + // Save a reference to the ‘this’ + var self = this; - var lastLength = self.errors.length; + /** + * { + * type: 'array' + * items: [ + * { + * type: 'string' + * }, + * { + * type: 'number' + * }, + * ... + * ], + * ... + * } + */ + if (isArray(schema.items)) { + + // Additional items are allowed + if (isUndefined(schema.additionalItems) || schema.additionalItems === true) { + return each(schema.items, function (itemIndex, itemSchema, callback) { + return self.validateSchema( + instance[itemIndex], itemSchema, self.joinPath(path, itemIndex), callback); + }, callback); + } - // Overwrite the ‘addError’ method - context.addError = function(message) { + return each(instance, function (itemIndex, itemValue, callback) { + + // The ‘additionalItems’ attribute is a schema that defines + // the schema of the additional items + if (schema.items[itemIndex] || isObject(schema.additionalItems)) { + return self.validateSchema( + itemValue, schema.items[itemIndex], self.joinPath(path, itemIndex), callback); + } + + // Additional items are disallowed + if (schema.additionalItems === false) { + self.errors.push({ + property: self.joinPath(path, itemIndex), + propertyValue: itemValue, + attributeName: 'additionalItems', + attributeValue: false + }); + return callback(); + } + + }, callback); - if (isObject(message)) { - return self.errors.push({ - property: message.property || property, - propertyValue: message.propertyValue || propertyValue, - attributeName: message.attributeName || attributeName, - attributeValue: message.attributeValue || propertyAttributes[attributeName], - message: message.message || undefined - }); } - return self.errors.push({ - property: property, - propertyValue: propertyValue, - attributeName: attributeName, - attributeValue: propertyAttributes[attributeName], - message: message - }); + /** + * { + * type: 'array' + * items: { + * type: 'string' + * }, + * ... + * } + */ + if (isObject(schema.items) && instance && !isEmpty(instance)) { + return each(instance, function (itemIndex, itemValue, callback) { + return self.validateSchema( + instance[itemIndex], schema.items, self.joinPath(path, itemIndex), callback); + }, callback); + } else { + return callback(); + } }; /** - * OnComplete + * Validation.validateProperties + * + * @param {object} instance + * @param {object} schema + * @param {string} path + * @param {function} callback */ - var onComplete = function(error) { - - // Deprecated - if (error === true || isString(error)) { - context.addError(error); - return callback(true); - }; + Validation.prototype.validateProperties = function (instance, schema, path, callback) { + + // Save a reference to the ‘this’ + var self = this; + + // Goes + return each(schema.properties, function (property, propertyAttributes, callback) { + + var isObject = propertyAttributes.type === 'object' && propertyAttributes.properties, + isArray = propertyAttributes.type === 'array'; + + // Get the value of property (instance[property]) + var propertyValue = self.getProperty(property, instance); + var propertyPath = self.joinPath(path, property); + + /** + * { + * type: 'object', + * properties: { + * user: { + * type: 'object', + * properties: { + * ... + * } + * } + * } + * } + */ + if (isObject || isArray) { + return self.validateSchema( + propertyValue, schema.properties[property], propertyPath, callback); + } else { + return self.validateProperty( + propertyPath, propertyValue, propertyAttributes, callback); + } - if (self.errors.length > lastLength && self.singleError) { - return callback(true); - } else { - return callback(); - } + }, callback); }; - if (isDefined(propertyAttributes[attributeName])) { - return attributeFn.apply(context, [ - property, - propertyValue, - propertyAttributes[attributeName], - propertyAttributes, - onComplete - ]); - } else { - return callback(); - } + /** + * Validation.validateProperty + * + * @param {string} propertyName + * @param {object} propertyAttributes + * @param {string|object} propertyValue + * @param {boolean} singleError + * @param {function} callback + */ + Validation.prototype.validateProperty = function (property, propertyValue, propertyAttributes, callback) { + + // Save a reference to the ‘this’ + var self = this; + + var context = {}; + + [ + 'validateItems', + 'validateProperties', + 'validateSchema', + 'validateProperty', + 'getProperty', + 'attributes', + 'errors', + 'joinPath' + ].forEach(function (key) { + context[key] = this[key]; + }, self); + + /** + * Iterator + * + * @param {string} attributeName + * @param {function} attributeFn + * @param {function} callback + */ + var iterator = function (attributeName, attributeFn, callback) { - }; + var lastLength = self.errors.length; - // If it's not a required param and it's empty, skip - if (propertyAttributes.required !== true && isUndefined(propertyValue)) { - return callback(); - } - - // Validate the property - return each(self.attributes, iterator, callback); + // Overwrite the ‘addError’ method + context.addError = function (message) { -}; + if (isObject(message)) { + return self.errors.push({ + property: message.property || property, + propertyValue: message.propertyValue || propertyValue, + attributeName: message.attributeName || attributeName, + attributeValue: message.attributeValue || propertyAttributes[attributeName], + message: message.message || undefined + }); + } -/** - * Validation.validateSchema - * - * @param {object} instance - * @param {object} schema - * @param {string} path - * @param {function} callback - */ -Validation.prototype.validateSchema = function(instance, schema, path, callback) { + return self.errors.push({ + property: property, + propertyValue: propertyValue, + attributeName: attributeName, + attributeValue: propertyAttributes[attributeName], + message: message + }); + + }; + + /** + * OnComplete + */ + var onComplete = function (error) { + + // Deprecated + if (error === true || isString(error)) { + context.addError(error); + return callback(true); + }; + + if (self.errors.length > lastLength && self.singleError) { + return callback(true); + } else { + return callback(); + } + + }; + + if (isDefined(propertyAttributes[attributeName])) { + return attributeFn.apply(context, [ + property, + propertyValue, + propertyAttributes[attributeName], + propertyAttributes, + onComplete + ]); + } else { + return callback(); + } - var self = this; + }; - return self.validateProperty(path, instance, schema, function(error) { + // If it's not a required param and it's empty, skip + if (propertyAttributes.required !== true && isUndefined(propertyValue)) { + return callback(); + } - /** - * { - * type: 'object', - * properties: { - * ... - * } - * } - */ - if (schema.properties) { - return self.validateProperties( - instance, - schema, - path, - callback - ); + // Validate the property + return each(self.attributes, iterator, callback); - /** - * { - * type: 'array', - * items: { - * type: 'string' - * ... - * } - * } - */ - } else if (schema.items) { - return self.validateItems( - instance, - schema, - path, - callback - ); + }; /** - * { - * type: 'array' - * } - * — or — - * { - * type: 'object' - * } + * Validation.validateSchema + * + * @param {object} instance + * @param {object} schema + * @param {string} path + * @param {function} callback */ - } else { - return callback(); - } + Validation.prototype.validateSchema = function (instance, schema, path, callback) { + + var self = this; + + return self.validateProperty(path, instance, schema, function (error) { + + /** + * { + * type: 'object', + * properties: { + * ... + * } + * } + */ + if (schema.properties) { + return self.validateProperties( + instance, schema, path, callback); + + /** + * { + * type: 'array', + * items: { + * type: 'string' + * ... + * } + * } + */ + } else if (schema.items) { + return self.validateItems( + instance, schema, path, callback); + + /** + * { + * type: 'array' + * } + * — or — + * { + * type: 'object' + * } + */ + } else { + return callback(); + } - }); + }); -}; + }; - - /** - * Export - * -------------------- - */ - engines.json = (function() { /** - * Cache + * Export + * -------------------- */ - var cache = []; - var cacheIndex = {}; - - return { + engines.json = (function () { /** - * Validate - * - * @param {object} instance - * @param {object} schema - * @param {object} options - * @param {function} callback + * Cache */ - validate: function(instance, schema, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; + var cache = []; + var cacheIndex = {}; + + return { + + /** + * Validate + * + * @param {object} instance + * @param {object} schema + * @param {object} options + * @param {function} callback + */ + validate: function (instance, schema, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + return (new Validation(options)).validate(instance, schema, callback); + }, + + /** + * AddAttribute + * + * @param {string} attributeName + * @param {function} attributeFn + */ + addAttribute: function (attributeName, attributeFn) { + return Validation.prototype.addAttribute.apply(Validation, arguments); + }, + + /** + * AddAttributeConstructor + * + * @param {string} attributeName + * @param {function} attributeConstructor + */ + addAttributeConstructor: function (attributeName, attributeConstructor) { + return Validation.prototype.addAttributeConstructor.apply(Validation, arguments); } - return (new Validation(options)).validate(instance, schema, callback); - }, - /** - * AddAttribute - * - * @param {string} attributeName - * @param {function} attributeFn - */ - addAttribute: function(attributeName, attributeFn) { - return Validation.prototype.addAttribute.apply(Validation, arguments); - }, + }; - /** - * AddAttributeConstructor - * - * @param {string} attributeName - * @param {function} attributeConstructor - */ - addAttributeConstructor: function(attributeName, attributeConstructor) { - return Validation.prototype.addAttributeConstructor.apply(Validation, arguments); + }()); + + }()); + + var amanda = function (engine) { + + if (!hasProperty(engines, engine)) { + throw new Error('The ‘' + engine + '’ engine is not supported. Please use a different one.'); } - }; + return engines[engine]; - }()); + }; -}()); + /** + * Amanda.validate + * + * This method is deprecated, please use ‘amanda('json').validate’ instead. + */ + amanda.validate = function (instance, schema, options, callback) { + var json = engines.json; + return json.validate.apply(json, arguments); + }; -var amanda = function(engine) { + /** + * Amanda.addValidator + * + * This method is deprecated, please use ‘amanda('json').addValidator’ instead. + */ + amanda.addValidator = function (attributeName, attributeFn) { + var json = engines.json; + return json.addAttribute.apply(json, arguments); + }; - if (!hasProperty(engines, engine)) { - throw new Error('The ‘' + engine + '’ engine is not supported. Please use a different one.'); - } + /** + * Amanda.addAttribute + * + * This method is deprecated, please use ‘amanda('json').addAttribute’ instead. + */ + amanda.addAttribute = function (attributeName, attributeFn) { + var json = engines.json; + return json.addAttribute.apply(json, arguments); + }; - return engines[engine]; - -}; - -/** - * Amanda.validate - * - * This method is deprecated, please use ‘amanda('json').validate’ instead. - */ -amanda.validate = function(instance, schema, options, callback) { - var json = engines.json; - return json.validate.apply(json, arguments); -}; - -/** - * Amanda.addValidator - * - * This method is deprecated, please use ‘amanda('json').addValidator’ instead. - */ -amanda.addValidator = function(attributeName, attributeFn) { - var json = engines.json; - return json.addAttribute.apply(json, arguments); -}; - -/** - * Amanda.addAttribute - * - * This method is deprecated, please use ‘amanda('json').addAttribute’ instead. - */ -amanda.addAttribute = function(attributeName, attributeFn) { - var json = engines.json; - return json.addAttribute.apply(json, arguments); -}; - -/** - * Amanda.addAttributeConstructor - * - * This method is deprecated, please use ‘amanda('json').addAttributeConstructor’ instead. - */ -amanda.addAttributeConstructor = function(attributeName, attributeConstructor) { - var json = engines.json; - return json.addAttributeConstructor.apply(json, arguments); -}; + /** + * Amanda.addAttributeConstructor + * + * This method is deprecated, please use ‘amanda('json').addAttributeConstructor’ instead. + */ + amanda.addAttributeConstructor = function (attributeName, attributeConstructor) { + var json = engines.json; + return json.addAttributeConstructor.apply(json, arguments); + }; /** * Export @@ -2004,7 +1926,7 @@ amanda.addAttributeConstructor = function(attributeName, attributeConstructor) { if (typeof module !== 'undefined' && module.exports) { module.exports = amanda; } else if (typeof define !== 'undefined') { - define(function() { + define(function () { return amanda; }); } else { diff --git a/releases/0.4.2/amanda.min.js b/releases/0.4.2/amanda.min.js index f08569f..e49bdd0 100644 --- a/releases/0.4.2/amanda.min.js +++ b/releases/0.4.2/amanda.min.js @@ -1,26 +1,26 @@ -(function(){var p={},n=function(a,g,h){var f=function(b,f,a){var c=[],j=function(b,i){var j=c.length+1;c.push(function(){return f(b,i,function(b){var f=c[j];return!b&&f?f():!b&&!f?a():a(b)})})};if(k(b)&&!o(b))for(var i=0,g=b.length;i=a||b>a)&&this.addError();return c()});a.prototype.addAttribute("maxItems",function(f,b,a,e,c){k(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("maxLength",function(f,b,a,e,c){m(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("minimum",function(f,b,a,e,c){q(b)&&(e.exclusiveMinimum&&b<=a||b=a||b>a)&&this.addError();return c()});a.prototype.addAttribute("maxItems",function(f,b,a,e,c){l(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("maxLength",function(f,b,a,e,c){m(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("minimum",function(f,b,a,e,c){q(b)&&(e.exclusiveMinimum&&b<=a||bn&&c.singleError?k(!0):k()}]):k()},e)};a.prototype.validateSchema=function(a,b,d,e){var c=this;return c.validateProperty(d,a,b,function(){return b.properties? -c.validateProperties(a,b,d,e):b.items?c.validateItems(a,b,d,e):e()})};p.json=function(){return{validate:function(f,b,d,e){"function"===typeof d&&(e=d,d={});return(new a(d)).validate(f,b,e)},addAttribute:function(f,b){return a.prototype.addAttribute.apply(a,arguments)},addAttributeConstructor:function(f,b){return a.prototype.addAttributeConstructor.apply(a,arguments)}}}()})();var r=function(a){if(!Object.prototype.hasOwnProperty.apply(p,[a]))throw Error("The \u2018"+a+"\u2019 engine is not supported. Please use a different one."); +a+"\u2019 must be "+d.join(" or ")+"."}};a.prototype.getProperty=function(a,b){return a.match(/([a-zA-Z0-9\s]+)/g).reduce(function(a,b){return a&&s(a[b])?a[b]:void 0},b)};a.prototype.joinPath=function(a,b){a=a||"";b+="";return b.match(/^[a-zA-Z]+$/)?a?a+"."+b:b:b.match(/\d+/)?a+"["+b+"]":a+'["'+b+'"]'};a.prototype.validate=function(a,b,d){var e=this;this.instance=a;this.schema=b;var c=function(){return 0!==e.errors.length?d(e.errors):d()};if(-1!=="string,number,function,boolean,integer,int,null".split(",").indexOf(b.type))return this.validateProperty(void 0, +a,b,c);if(-1!==["object","array"].indexOf(b.type)){if(m(a))try{a=JSON.parse(a)}catch(j){}return this.validateSchema(a,b,"",c)}if("any"===b.type||!b.type){if(m(a))try{return a=JSON.parse(a),this.validateSchema(a,b,"",c)}catch(i){}return k(a)||l(a)?this.validateSchema(a,b,"",c):this.validateProperty(void 0,a,b,c)}};a.prototype.validateItems=function(a,b,d,e){var c=this;return l(b.items)?u(b.additionalItems)||!0===b.additionalItems?n(b.items,function(b,e,g){return c.validateSchema(a[b],e,c.joinPath(d, +b),g)},e):n(a,function(a,f,e){if(b.items[a]||k(b.additionalItems))return c.validateSchema(f,b.items[a],c.joinPath(d,a),e);if(!1===b.additionalItems)return c.errors.push({property:c.joinPath(d,a),propertyValue:f,attributeName:"additionalItems",attributeValue:!1}),e()},e):k(b.items)&&a&&!o(a)?n(a,function(e,i,g){return c.validateSchema(a[e],b.items,c.joinPath(d,e),g)},e):e()};a.prototype.validateProperties=function(a,b,d,e){var c=this;return n(b.properties,function(e,i,g){var h="object"===i.type&&i.properties, +k="array"===i.type,m=c.getProperty(e,a),v=c.joinPath(d,e);return h||k?c.validateSchema(m,b.properties[e],v,g):c.validateProperty(v,m,i,g)},e)};a.prototype.validateProperty=function(a,b,d,e){var c=this,g={};"validateItems,validateProperties,validateSchema,validateProperty,getProperty,attributes,errors,joinPath".split(",").forEach(function(a){g[a]=this[a]},c);return!0!==d.required&&u(b)?e():n(c.attributes,function(e,h,l){var n=c.errors.length;g.addError=function(g){return k(g)?c.errors.push({property:g.property|| +a,propertyValue:g.propertyValue||b,attributeName:g.attributeName||e,attributeValue:g.attributeValue||d[e],message:g.message||void 0}):c.errors.push({property:a,propertyValue:b,attributeName:e,attributeValue:d[e],message:g})};var o=function(a){return!0===a||m(a)?(g.addError(a),l(!0)):c.errors.length>n&&c.singleError?l(!0):l()};return s(d[e])?h.apply(g,[a,b,d[e],d,o]):l()},e)};a.prototype.validateSchema=function(a,b,d,e){var c=this;return c.validateProperty(d,a,b,function(){return b.properties?c.validateProperties(a, +b,d,e):b.items?c.validateItems(a,b,d,e):e()})};p.json=function(){return{validate:function(f,b,d,e){"function"===typeof d&&(e=d,d={});return(new a(d)).validate(f,b,e)},addAttribute:function(f,b){return a.prototype.addAttribute.apply(a,arguments)},addAttributeConstructor:function(f,b){return a.prototype.addAttributeConstructor.apply(a,arguments)}}}()})();var r=function(a){if(!Object.prototype.hasOwnProperty.apply(p,[a]))throw Error("The \u2018"+a+"\u2019 engine is not supported. Please use a different one."); return p[a]};r.validate=function(a,g,h,f){var b=p.json;return b.validate.apply(b,arguments)};r.addValidator=function(a,g){var h=p.json;return h.addAttribute.apply(h,arguments)};r.addAttribute=function(a,g){var h=p.json;return h.addAttribute.apply(h,arguments)};r.addAttributeConstructor=function(a,g){var h=p.json;return h.addAttributeConstructor.apply(h,arguments)};"undefined"!==typeof module&&module.exports?module.exports=r:"undefined"!==typeof define?define(function(){return r}):this.amanda=r})(); \ No newline at end of file diff --git a/releases/latest/amanda.js b/releases/latest/amanda.js index 7dc6ead..93fcbb8 100644 --- a/releases/latest/amanda.js +++ b/releases/latest/amanda.js @@ -1,4 +1,4 @@ -(function() { +(function () { /** * Engines @@ -6,1996 +6,1918 @@ */ var engines = {}; -/** - * DetectType - * - * @param {object} input - */ -var detectType = function(input) { - return typeof input; -}; - -/** - * Each - * - * Applies an iterator function to each item in an array or an object, in series. - * - * @param {object} list - * @param {function} iterator - * @param {function} callback - */ -var each = function(list, iterator, callback) { - /** - * SyncEach + * DetectType * - * @param {object} list - * @param {function} iterator + * @param {object} input */ - var syncEach = function(list, iterator) { - - // If the list is an array - if (isArray(list) && !isEmpty(list)) { - for (var i = 0, len = list.length; i < len; i++) { - iterator.apply(list, [i, list[i]]); - } - } - - // If the list is an object - if (isObject(list) && !isEmpty(list)) { - for (var key in list) { - if (list.hasOwnProperty(key)) { - iterator.apply(list, [key, list[key]]); - } - } - } - - }; + var detectType = function (input) { + return typeof input; + }; /** - * AsyncEach + * Each + * + * Applies an iterator function to each item in an array or an object, in series. + * * @param {object} list * @param {function} iterator * @param {function} callback */ - var asyncEach = function(list, iterator, callback) { + var each = function (list, iterator, callback) { - var queue = []; + /** + * SyncEach + * + * @param {object} list + * @param {function} iterator + */ + var syncEach = function (list, iterator) { - /** - * AddToQueue - * - * @param {string} key - * @param {string|object} value - */ - var addToQueue = function(key, value) { - var index = queue.length + 1; - queue.push(function() { - - var next = function(error) { - var fn = queue[index]; - if (!error && fn) { - return fn(); - } else if (!error && !fn) { - return callback(); - } else { - return callback(error); + // If the list is an array + if (isArray(list) && !isEmpty(list)) { + for (var i = 0, len = list.length; i < len; i++) { + iterator.apply(list, [i, list[i]]); + } } - }; - - return iterator(key, value, next); - - }); - }; - - // If the list is an array - if (isArray(list) && !isEmpty(list)) { - for (var i = 0, len = list.length; i < len; i++) { - addToQueue(i, list[i]); - } - - // If the list is an object - } else if (isObject(list) && !isEmpty(list)) { - for (var key in list) { - if (list.hasOwnProperty(key)) { - addToQueue(key, list[key]); - } - } - // If the list is not an array or an object - } else { - return callback(); - } + // If the list is an object + if (isObject(list) && !isEmpty(list)) { + for (var key in list) { + if (list.hasOwnProperty(key)) { + iterator.apply(list, [key, list[key]]); + } + } + } - // And go! - return queue[0](); + }; - }; + /** + * AsyncEach + * @param {object} list + * @param {function} iterator + * @param {function} callback + */ + var asyncEach = function (list, iterator, callback) { + + var queue = []; + + /** + * AddToQueue + * + * @param {string} key + * @param {string|object} value + */ + var addToQueue = function (key, value) { + var index = queue.length + 1; + queue.push(function () { + + var next = function (error) { + var fn = queue[index]; + if (!error && fn) { + return fn(); + } else if (!error && !fn) { + return callback(); + } else { + return callback(error); + } + }; + + return iterator(key, value, next); - if (typeof callback === 'undefined') { - return syncEach.apply(this, arguments); - } else { - return asyncEach.apply(this, arguments); - } + }); + }; -}; - -/** - * Every - * - * @param {object} arr - * @param {function} iterator - */ -var every = function(arr, iterator) { - return Array.prototype.every.apply(arr, [iterator]); -}; - -/** - * Filter - * - * @param {object} arr - * @param {function} iterator - */ -var filter = function(arr, iterator, context) { - return Array.prototype.filter.apply(arr, [iterator, context || this]); -}; - -/** - * HasProperty - * - * @param {object} input - */ -var hasProperty = function(obj, property) { - return Object.prototype.hasOwnProperty.apply(obj, [property]); -}; - -/** - * IsArray - * - * Returns true if the passed-in object is an array. - * - * @param {object} input - */ -var isArray = function(input) { - return Object.prototype.toString.call(input) === '[object Array]'; -}; - -/** - * IsBoolean - * - * @param {object} input - */ -var isBoolean = function(input) { - return typeof input === 'boolean'; -}; - -/** - * IsDefined - * - * @param {object} input - */ -var isDefined = function(input) { - return typeof input !== 'undefined'; -}; - -/** - * IsEmpty - * - * Returns true if the passed-in object is empty. - * - * @param {object} input - */ -var isEmpty = function(input) { - - if (isNumber(input)) { - return false; - } + // If the list is an array + if (isArray(list) && !isEmpty(list)) { + for (var i = 0, len = list.length; i < len; i++) { + addToQueue(i, list[i]); + } - if (input === null) { - return true; - } + // If the list is an object + } else if (isObject(list) && !isEmpty(list)) { + for (var key in list) { + if (list.hasOwnProperty(key)) { + addToQueue(key, list[key]); + } + } - // If the passed-in object is an array or a string - if (isArray(input) || typeof input === 'string') { - return input.length === 0; - } + // If the list is not an array or an object + } else { + return callback(); + } - // If the passed-in object is an object - if (isObject(input)) { - for (var key in input) { - if (hasOwnProperty.call(input, key)) return false; - } - } + // And go! + return queue[0](); - return true; + }; -}; + if (typeof callback === 'undefined') { + return syncEach.apply(this, arguments); + } else { + return asyncEach.apply(this, arguments); + } -/** - * IsEqual - * - * @param {object} obj1 - * @param {object} obj2 - */ -var isEqual = function(obj1, obj2) { + }; /** - * Arrays + * Every + * + * @param {object} arr + * @param {function} iterator */ - if (isArray(obj1, obj2)) { + var every = function (arr, iterator) { + return Array.prototype.every.apply(arr, [iterator]); + }; - if (obj1.length !== obj2.length) { - return false; - } + /** + * Filter + * + * @param {object} arr + * @param {function} iterator + */ + var filter = function (arr, iterator, context) { + return Array.prototype.filter.apply(arr, [iterator, context || this]); + }; - return every(obj1, function(value, index, context) { - return obj2[index] === value; - }); + /** + * HasProperty + * + * @param {object} input + */ + var hasProperty = function (obj, property) { + return Object.prototype.hasOwnProperty.apply(obj, [property]); + }; - } + /** + * IsArray + * + * Returns true if the passed-in object is an array. + * + * @param {object} input + */ + var isArray = function (input) { + return Object.prototype.toString.call(input) === '[object Array]'; + }; /** - * Objects + * IsBoolean + * + * @param {object} input */ - if (isObject(obj1, obj2)) { + var isBoolean = function (input) { + return typeof input === 'boolean'; + }; - var keys1 = keys(obj1), - keys2 = keys(obj2); + /** + * IsDefined + * + * @param {object} input + */ + var isDefined = function (input) { + return typeof input !== 'undefined'; + }; - if (!isEqual(keys1, keys2)) { - return false; - } + /** + * IsEmpty + * + * Returns true if the passed-in object is empty. + * + * @param {object} input + */ + var isEmpty = function (input) { - for (key in obj1) { - if (!obj2[key] || obj1[key] !== obj2[key]) { + if (isNumber(input)) { return false; } - } - return true; + if (input === null) { + return true; + } - } + // If the passed-in object is an array or a string + if (isArray(input) || typeof input === 'string') { + return input.length === 0; + } - return false; - -}; - -/** - * IsFunction - * - * @param {object} input - */ -var isFunction = function(input) { - return typeof input === 'function'; -}; - -/** - * IsInteger - * - * @param {object} input - */ -var isInteger = function(input) { - return isNumber(input) && input % 1 === 0; -}; - -/** - * IsNull - * - * @param {object} input - */ -var isNull = function(input) { - return input === null; -}; - -/** - * IsNumber - * - * @param {object} input - */ -var isNumber = function(input) { - return typeof input === 'number'; -}; - -/** - * IsObject - * - * Returns true if the passed-in object is an object. - * - * @param {object} input - */ -var isObject = function(input) { - return Object.prototype.toString.call(input) === '[object Object]'; -}; - -/** - * IsString - * - * @param {object} input - */ -var isString = function(input) { - return typeof input === 'string'; -}; - -/** - * IsUndefined - * - * @param {object} input - */ -var isUndefined = function(input) { - return typeof input === 'undefined'; -}; - -/** - * Keys - * - * @param {object} obj - */ -var keys = function(obj) { - return Object.keys(obj); -}; - -/** - * Merge - * - * Copy all of the properties in the source objects over to the destination object. - * - * @param {object} obj1 - * @param {object} obj2 - */ -var merge = function(obj1, obj2) { - for (var key in obj2) { - if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) { - obj1[key] = obj2[key]; - } - } - return obj1; -}; - -/** - * Pluck - * - * Extracts a list of property values. - * - * @param {object} list - * @param {string} propertyName - */ -var pluck = function(list, propertyName) { - var output = []; - for (var i = 0, len = list.length; i < len; i++) { - var property = list[i][propertyName]; - if (output.indexOf(property) === -1) { - output.push(property); - } - } - return output; -}; - -/** - * ReturnTrue - */ -var returnTrue = function() { - return true; -}; - -/** - * Some - * - * @param {object} arr - * @param {function} iterator - */ -var some = function(arr, iterator) { - return Array.prototype.some.apply(arr, [iterator]); -}; - -(function() { - -/** - * Validation - * - * @constructor - * @param {object} options - */ -var Validation = function(options) { - - // Save a reference to the ‘this’ - var self = this; - - var defaultOptions = { - singleError: true, - messages: errorMessages, - cache: false - }; + // If the passed-in object is an object + if (isObject(input)) { + for (var key in input) { + if (hasOwnProperty.call(input, key)) return false; + } + } - each(defaultOptions, function(key, value) { + return true; - if (isObject(value) && options[key]) { - self[key] = merge(options[key], defaultOptions[key]); + }; - } else if (isObject(value) && !options[key]) { - self[key] = merge ({}, defaultOptions[key]); + /** + * IsEqual + * + * @param {object} obj1 + * @param {object} obj2 + */ + var isEqual = function (obj1, obj2) { - } else { - self[key] = (isDefined(options[key])) ? options[key] : defaultOptions[key]; - } + /** + * Arrays + */ + if (isArray(obj1, obj2)) { - }); + if (obj1.length !== obj2.length) { + return false; + } - this.errors = new ValidationError(this); + return every(obj1, function (value, index, context) { + return obj2[index] === value; + }); -}; + } -/** - * Attributes - * -------------------- - */ -Validation.prototype.attributes = {}; + /** + * Objects + */ + if (isObject(obj1, obj2)) { -/** - * AddAttribute - * - * @param {string} attributeName - * @param {function} attributeFn - */ -Validation.prototype.addAttribute = function(attributeName, attributeFn) { - return Validation.prototype.attributes[attributeName] = attributeFn; -}; + var keys1 = keys(obj1), + keys2 = keys(obj2); -/** - * AddAttributeConstructor - * - * @param {string} attributeName - * @param {function} attributeConstructor - */ -Validation.prototype.addAttributeConstructor = function(attributeName, attributeConstructor) { - return Validation.prototype.attributes[attributeName] = attributeConstructor(); -}; + if (!isEqual(keys1, keys2)) { + return false; + } -/** - * AdditionalProperties - */ -var additionalPropertiesAttribute = function additionalProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { + for (key in obj1) { + if (!obj2[key] || obj1[key] !== obj2[key]) { + return false; + } + } - var self = this; + return true; - /** - * { - * additionalProperties: true, - * ... - * } - */ - if (attributeValue === true) { - return callback(); - } + } - // Filter the forbidden properties - var propertyKeys = keys(propertyValue); - var forbiddenProperties = filter(propertyKeys, function(key) { - return !propertyAttributes.properties[key]; - }); + return false; - if (isEmpty(forbiddenProperties)) { - return callback(); - } + }; /** - * { - * additionalProperties: false, - * ... - * } + * IsFunction + * + * @param {object} input */ - if (attributeValue === false) { - - forbiddenProperties.forEach(function(forbiddenProperty) { - this.addError({ - property: this.joinPath(property, forbiddenProperty), - propertyValue: propertyValue[forbiddenProperty] - }); - }, this); - - return callback(); + var isFunction = function (input) { + return typeof input === 'function'; + }; - } + /** + * IsInteger + * + * @param {object} input + */ + var isInteger = function (input) { + return isNumber(input) && input % 1 === 0; + }; /** - * { - * additionalProperties: { - * type: 'string', - * ... - * }, - * ... - * } + * IsNull + * + * @param {object} input */ - if (isObject(attributeValue)) { - return each(forbiddenProperties, function(index, key, callback) { - return self.validateSchema( - propertyValue[key], - attributeValue, - property + key, - callback - ); - }, callback); - } + var isNull = function (input) { + return input === null; + }; -}; + /** + * IsNumber + * + * @param {object} input + */ + var isNumber = function (input) { + return typeof input === 'number'; + }; -// Export -Validation.prototype.addAttribute('additionalProperties', additionalPropertiesAttribute); + /** + * IsObject + * + * Returns true if the passed-in object is an object. + * + * @param {object} input + */ + var isObject = function (input) { + return Object.prototype.toString.call(input) === '[object Object]'; + }; -/** - * DivisibleBy - */ -var divisibleByAttribute = function divisibleBy(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * IsString + * + * @param {object} input + */ + var isString = function (input) { + return typeof input === 'string'; + }; - if (attributeValue === 0) { - throw new Error('The value of this attribute should not be 0.'); - } + /** + * IsUndefined + * + * @param {object} input + */ + var isUndefined = function (input) { + return typeof input === 'undefined'; + }; - if (isNumber(propertyValue) && (propertyValue % attributeValue !== 0)) { - this.addError(); - } + /** + * Keys + * + * @param {object} obj + */ + var keys = function (obj) { + return Object.keys(obj); + }; - return callback(); + /** + * Merge + * + * Copy all of the properties in the source objects over to the destination object. + * + * @param {object} obj1 + * @param {object} obj2 + */ + var merge = function (obj1, obj2) { + for (var key in obj2) { + if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) { + obj1[key] = obj2[key]; + } + } + return obj1; + }; -}; + /** + * Pluck + * + * Extracts a list of property values. + * + * @param {object} list + * @param {string} propertyName + */ + var pluck = function (list, propertyName) { + var output = []; + for (var i = 0, len = list.length; i < len; i++) { + var property = list[i][propertyName]; + if (output.indexOf(property) === -1) { + output.push(property); + } + } + return output; + }; -// Export -Validation.prototype.addAttribute('divisibleBy', divisibleByAttribute); + /** + * ReturnTrue + */ + var returnTrue = function () { + return true; + }; -/** - * Enum - */ -var enumAttribute = function(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * Some + * + * @param {object} arr + * @param {function} iterator + */ + var some = function (arr, iterator) { + return Array.prototype.some.apply(arr, [iterator]); + }; - if (attributeValue.indexOf(propertyValue) === -1) { - this.addError(); - } + (function () { - return callback(); + /** + * Validation + * + * @constructor + * @param {object} options + */ + var Validation = function (options) { -}; + // Save a reference to the ‘this’ + var self = this; -// Export -Validation.prototype.addAttribute('enum', enumAttribute); + var defaultOptions = { + singleError: true, + messages: errorMessages, + cache: false + }; -/** - * Except - */ -var exceptAttribute = function except(property, propertyValue, attributeValue, propertyAttributes, callback) { + each(defaultOptions, function (key, value) { - if (attributeValue.indexOf(propertyValue) !== -1) { - this.addError(); - } + if (isObject(value) && options[key]) { + self[key] = merge(options[key], defaultOptions[key]); - return callback(); + } else if (isObject(value) && !options[key]) { + self[key] = merge({}, defaultOptions[key]); -}; + } else { + self[key] = (isDefined(options[key])) ? options[key] : defaultOptions[key]; + } -// Export -Validation.prototype.addAttribute('except', exceptAttribute); + }); -/** - * Format - */ -Validation.prototype.addAttributeConstructor('format', function formatConstructor() { - - // Uložíme si referenci na this - var self = this; + this.errors = new ValidationError(this); - /** - * Formats - */ - var formats = { + }; /** - * date-time - * - * This should be a date in ISO 8601 format of YYYY-MM-DDThh:mm:ssZ in UTC - * time. This is the recommended form of date/timestamp. + * Attributes + * -------------------- */ - 'date-time': { - type: 'string', - pattern: /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ - }, + Validation.prototype.attributes = {}; /** - * date + * AddAttribute * - * This should be a date in the format of YYYY-MM-DD. It is recommended that you - * use the "date-time" format instead of "date" unless you need to transfer only the date part. + * @param {string} attributeName + * @param {function} attributeFn */ - date: function(input) { - if (isString(input)) { - return input.match(/^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/); - } - if (isObject(input)) { - return Object.prototype.toString.call(input) === '[object Date]'; - } - return false; - }, + Validation.prototype.addAttribute = function (attributeName, attributeFn) { + return Validation.prototype.attributes[attributeName] = attributeFn; + }; /** - * time + * AddAttributeConstructor * - * This should be a time in the format of hh:mm:ss. + * @param {string} attributeName + * @param {function} attributeConstructor */ - 'time': { - type: 'string', - pattern: /^\d{2}:\d{2}:\d{2}$/ - }, + Validation.prototype.addAttributeConstructor = function (attributeName, attributeConstructor) { + return Validation.prototype.attributes[attributeName] = attributeConstructor(); + }; /** - * utc-milisec - * - * This should be the difference, measured in milliseconds, between the specified - * time and midnight, 00:00 of January 1, 1970 UTC. The value - * should be a number (integer or float). + * AdditionalProperties */ - 'utc-milisec': { - type: 'number' - }, + var additionalPropertiesAttribute = function additionalProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { + + var self = this; + + /** + * { + * additionalProperties: true, + * ... + * } + */ + if (attributeValue === true) { + return callback(); + } - /** - * regex - * - * This should be a time in the format of hh:mm:ss. - */ - regex: function(input) { - return input && input.test && input.exec; - }, + // Filter the forbidden properties + var propertyKeys = keys(propertyValue); + var forbiddenProperties = filter(propertyKeys, function (key) { + return !propertyAttributes.properties[key]; + }); - /** - * color - * - * This is a CSS color (like "#FF0000" or "red"), based on CSS 2.1. - */ - 'color': { - type: 'string' - }, + if (isEmpty(forbiddenProperties)) { + return callback(); + } - /** - * style - * - * This is a CSS style definition (like "color: red; background-color:#FFF"), based on CSS 2.1. - */ - 'style': { - type: 'string' - }, + /** + * { + * additionalProperties: false, + * ... + * } + */ + if (attributeValue === false) { - /** - * phone - * - * This should be a phone number. - */ - 'phone': { - type: 'number' - }, + forbiddenProperties.forEach(function (forbiddenProperty) { + this.addError({ + property: this.joinPath(property, forbiddenProperty), + propertyValue: propertyValue[forbiddenProperty] + }); + }, this); - /** - * uri - * - * This value should be a URI. - */ - 'uri': { - type: 'string', - pattern: /^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/ - }, + return callback(); - /** - * email - * - * This should be an email address. - */ - 'email': { - type: 'string', - pattern: /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/ - }, + } - /** - * ip-address - * - * This should be an ip version 4 address. - */ - 'ip-address': { - type: 'string', - pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ - }, + /** + * { + * additionalProperties: { + * type: 'string', + * ... + * }, + * ... + * } + */ + if (isObject(attributeValue)) { + return each(forbiddenProperties, function (index, key, callback) { + return self.validateSchema( + propertyValue[key], attributeValue, property + key, callback); + }, callback); + } - /** - * ipv6 - * - * This should be an ip version 6 address. - */ - 'ipv6': { - type: 'string', - pattern: /(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-f\d]{1,4}:)*[a-f\d]{1,4})?::(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)/ - }, + }; + + // Export + Validation.prototype.addAttribute('additionalProperties', additionalPropertiesAttribute); /** - * host-name - * - * This should be a host-name. + * DivisibleBy */ - 'host-name': { - type: 'string' - } + var divisibleByAttribute = function divisibleBy(property, propertyValue, attributeValue, propertyAttributes, callback) { - }; + if (attributeValue === 0) { + throw new Error('The value of this attribute should not be 0.'); + } - /** - * CustomFormats - * -------------------- - */ - formats.alpha = { - required: true, - type: 'string', - pattern: /^[a-zA-Z]+$/ - }; + if (isNumber(propertyValue) && (propertyValue % attributeValue !== 0)) { + this.addError(); + } - formats.alphanumeric = { - required: true, - type: ['string', 'number'], - pattern: /^[a-zA-Z0-9]+$/ - }; + return callback(); - formats.decimal = function(input) { - if (!isNumber(input)) return false; - return (input + '').match(/^[0-9]+(\.[0-9]{1,2})?$/); - }; + }; - formats.percentage = { - required: true, - type: ['string', 'number'], - pattern: /^-?[0-9]{0,2}(\.[0-9]{1,2})?$|^-?(100)(\.[0]{1,2})?$/, - minimum: -100, - maximum: 100 - }; + // Export + Validation.prototype.addAttribute('divisibleBy', divisibleByAttribute); - formats.port = { - required: true, - type: ['string', 'number'], - pattern: /\:\d+/ - }; + /** + * Enum + */ + var enumAttribute = function (property, propertyValue, attributeValue, propertyAttributes, callback) { - /** - * Aliases - * -------------------- - */ - var aliases = { - url: 'uri', - ip: 'ip-address', - ipv4: 'ip-address', - host: 'host-name', - hostName: 'host-name' - }; + if (attributeValue.indexOf(propertyValue) === -1) { + this.addError(); + } - // Apply aliases - each(aliases, function(alias, format) { - formats[alias] = formats[format]; - }); + return callback(); - // Export - return function format(property, propertyValue, attributeValue, propertyAttributes, callback) { + }; - /** - * { - * format: { - * type: 'string', - * pattern: /abc/ - * ... - * } - * ... - * } - */ - if (isObject(attributeValue)) { - return this.validateProperty(property, propertyValue, attributeValue, callback); - } + // Export + Validation.prototype.addAttribute('enum', enumAttribute); /** - * { - * format: 'lorem ipsum dolor', - * ... - * } + * Except */ - if (isString(attributeValue) && !hasProperty(formats, attributeValue)) { - throw new Error('The format ‘' + attributeValue + '’ is not supported.'); - } + var exceptAttribute = function except(property, propertyValue, attributeValue, propertyAttributes, callback) { - /** - * { - * format: 'phone', - * ... - * } - */ - if (isString(attributeValue)) { - - var fn = formats[attributeValue]; - - if (isFunction(fn)) { - var noError = fn(propertyValue); - if (!noError) { + if (attributeValue.indexOf(propertyValue) !== -1) { this.addError(); } + return callback(); - } - if (isObject(fn)) { - return this.validateProperty(property, propertyValue, fn, callback); - } + }; - } + // Export + Validation.prototype.addAttribute('except', exceptAttribute); - }; + /** + * Format + */ + Validation.prototype.addAttributeConstructor('format', function formatConstructor() { -}); + // Uložíme si referenci na this + var self = this; -/** - * Length - */ -var lengthAttribute = function length(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * Formats + */ + var formats = { + + /** + * date-time + * + * This should be a date in ISO 8601 format of YYYY-MM-DDThh:mm:ssZ in UTC + * time. This is the recommended form of date/timestamp. + */'date-time': { + type: 'string', + pattern: /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ + }, + + /** + * date + * + * This should be a date in the format of YYYY-MM-DD. It is recommended that you + * use the "date-time" format instead of "date" unless you need to transfer only the date part. + */ + date: function (input) { + if (isString(input)) { + return input.match(/^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/); + } + if (isObject(input)) { + return Object.prototype.toString.call(input) === '[object Date]'; + } + return false; + }, + + /** + * time + * + * This should be a time in the format of hh:mm:ss. + */'time': { + type: 'string', + pattern: /^\d{2}:\d{2}:\d{2}$/ + }, + + /** + * utc-milisec + * + * This should be the difference, measured in milliseconds, between the specified + * time and midnight, 00:00 of January 1, 1970 UTC. The value + * should be a number (integer or float). + */'utc-milisec': { + type: 'number' + }, + + /** + * regex + * + * This should be a time in the format of hh:mm:ss. + */ + regex: function (input) { + return input && input.test && input.exec; + }, + + /** + * color + * + * This is a CSS color (like "#FF0000" or "red"), based on CSS 2.1. + */'color': { + type: 'string' + }, + + /** + * style + * + * This is a CSS style definition (like "color: red; background-color:#FFF"), based on CSS 2.1. + */'style': { + type: 'string' + }, + + /** + * phone + * + * This should be a phone number. + */'phone': { + type: 'number' + }, + + /** + * uri + * + * This value should be a URI. + */'uri': { + type: 'string', + pattern: /^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|cat|coop|int|pro|tel|xxx|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2})?)|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/ + }, + + /** + * email + * + * This should be an email address. + */'email': { + type: 'string', + pattern: /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/ + }, + + /** + * ip-address + * + * This should be an ip version 4 address. + */'ip-address': { + type: 'string', + pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ + }, + + /** + * ipv6 + * + * This should be an ip version 6 address. + */'ipv6': { + type: 'string', + pattern: /(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-f\d]{1,4}:)*[a-f\d]{1,4})?::(?:(?:[a-f\d]{1,4}:)*(?:[a-f\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)/ + }, + + /** + * host-name + * + * This should be a host-name. + */'host-name': { + type: 'string' + } - if (isString(propertyValue) && propertyValue.length !== attributeValue) { - this.addError(); - } + }; - return callback(); + /** + * CustomFormats + * -------------------- + */ + formats.alpha = { + required: true, + type: 'string', + pattern: /^[a-zA-Z]+$/ + }; -}; + formats.alphanumeric = { + required: true, + type: ['string', 'number'], + pattern: /^[a-zA-Z0-9]+$/ + }; -// Export -Validation.prototype.addAttribute('length', lengthAttribute); + formats.decimal = function (input) { + if (!isNumber(input)) return false; + return (input + '').match(/^[0-9]+(\.[0-9]{1,2})?$/); + }; -/** - * Maximum - */ -var maximumAttribute = function maximum(property, propertyValue, attributeValue, propertyAttributes, callback) { + formats.percentage = { + required: true, + type: ['string', 'number'], + pattern: /^-?[0-9]{0,2}(\.[0-9]{1,2})?$|^-?(100)(\.[0]{1,2})?$/, + minimum: -100, + maximum: 100 + }; - if (isNumber(propertyValue)) { - if ((propertyAttributes.exclusiveMaximum && propertyValue >= attributeValue) || (propertyValue > attributeValue)) { - this.addError(); - } - } + formats.port = { + required: true, + type: ['string', 'number'], + pattern: /\:\d+/ + }; - return callback(); + /** + * Aliases + * -------------------- + */ + var aliases = { + url: 'uri', + ip: 'ip-address', + ipv4: 'ip-address', + host: 'host-name', + hostName: 'host-name' + }; -}; + // Apply aliases + each(aliases, function (alias, format) { + formats[alias] = formats[format]; + }); -// Export -Validation.prototype.addAttribute('maximum', maximumAttribute); + // Export + return function format(property, propertyValue, attributeValue, propertyAttributes, callback) { + + /** + * { + * format: { + * type: 'string', + * pattern: /abc/ + * ... + * } + * ... + * } + */ + if (isObject(attributeValue)) { + return this.validateProperty(property, propertyValue, attributeValue, callback); + } -/** - * MaxItems - */ -var maxItemsAttribute = function maxItems(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * { + * format: 'lorem ipsum dolor', + * ... + * } + */ + if (isString(attributeValue) && !hasProperty(formats, attributeValue))  { + throw new Error('The format ‘' + attributeValue + '’ is not supported.'); + } - if (isArray(propertyValue) && propertyValue.length > attributeValue) { - this.addError(); - } + /** + * { + * format: 'phone', + * ... + * } + */ + if (isString(attributeValue)) { - return callback(); + var fn = formats[attributeValue]; -}; + if (isFunction(fn)) { + var noError = fn(propertyValue); + if (!noError) { + this.addError(); + } + return callback(); + } -// Export -Validation.prototype.addAttribute('maxItems', maxItemsAttribute); + if (isObject(fn)) { + return this.validateProperty(property, propertyValue, fn, callback); + } -/** - * MaxLength - */ -var maxLengthAttribute = function maxLength(property, propertyValue, attributeValue, propertyAttributes, callback) { + } - if (isString(propertyValue) && propertyValue.length > attributeValue) { - this.addError(); - } + }; - return callback(); + }); -}; + /** + * Length + */ + var lengthAttribute = function length(property, propertyValue, attributeValue, propertyAttributes, callback) { -// Export -Validation.prototype.addAttribute('maxLength', maxLengthAttribute); + if (isString(propertyValue) && propertyValue.length !== attributeValue) { + this.addError(); + } -/** - * Minimum - */ -var minimumAttribute = function minimum(property, propertyValue, attributeValue, propertyAttributes, callback) { + return callback(); - if (isNumber(propertyValue)) { - if ((propertyAttributes.exclusiveMinimum && propertyValue <= attributeValue) || (propertyValue < attributeValue)) { - this.addError(); - } - } + }; - return callback(); + // Export + Validation.prototype.addAttribute('length', lengthAttribute); -}; + /** + * Maximum + */ + var maximumAttribute = function maximum(property, propertyValue, attributeValue, propertyAttributes, callback) { -// Export -Validation.prototype.addAttribute('minimum', minimumAttribute); + if (isNumber(propertyValue)) { + if ((propertyAttributes.exclusiveMaximum && propertyValue >= attributeValue) || (propertyValue > attributeValue)) { + this.addError(); + } + } + return callback(); -/** - * MinItems - */ -var minItems = function minItems(property, propertyValue, attributeValue, propertyAttributes, callback) { + }; - if (isArray(propertyValue) && propertyValue.length < attributeValue) { - this.addError(); - } + // Export + Validation.prototype.addAttribute('maximum', maximumAttribute); - return callback(); + /** + * MaxItems + */ + var maxItemsAttribute = function maxItems(property, propertyValue, attributeValue, propertyAttributes, callback) { -}; + if (isArray(propertyValue) && propertyValue.length > attributeValue) { + this.addError(); + } -// Export -Validation.prototype.addAttribute('minItems', minItems); + return callback(); + + }; -/** - * MinLength - */ -var minLengthAttribute = function minLength(property, propertyValue, attributeValue, propertyAttributes, callback) { + // Export + Validation.prototype.addAttribute('maxItems', maxItemsAttribute); - if (isString(propertyValue) && propertyValue.length < attributeValue) { - this.addError(); - } + /** + * MaxLength + */ + var maxLengthAttribute = function maxLength(property, propertyValue, attributeValue, propertyAttributes, callback) { + + if (isString(propertyValue) && propertyValue.length > attributeValue) { + this.addError(); + } - return callback(); + return callback(); -}; + }; -// Export -Validation.prototype.addAttribute('minLength', minLengthAttribute); + // Export + Validation.prototype.addAttribute('maxLength', maxLengthAttribute); -/** - * Pattern - */ -var patternAttribute = function pattern(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * Minimum + */ + var minimumAttribute = function minimum(property, propertyValue, attributeValue, propertyAttributes, callback) { - if (isString(propertyValue) && !propertyValue.match(attributeValue)) { - this.addError(); - } + if (isNumber(propertyValue)) { + if ((propertyAttributes.exclusiveMinimum && propertyValue <= attributeValue) || (propertyValue < attributeValue)) { + this.addError(); + } + } - return callback(); + return callback(); -}; + }; -// Export -Validation.prototype.addAttribute('pattern', patternAttribute); + // Export + Validation.prototype.addAttribute('minimum', minimumAttribute); -(function() { - /** - * PatternProperties - */ - var attribute = function patternProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { + /** + * MinItems + */ + var minItems = function minItems(property, propertyValue, attributeValue, propertyAttributes, callback) { - // Saves a reference to ‘this’ - var self = this; + if (isArray(propertyValue) && propertyValue.length < attributeValue) { + this.addError(); + } - // Skip - if (isEmpty(attributeValue)) { - return callback(); - } + return callback(); - var matches = {}; - var patterns = keys(attributeValue); + }; + + // Export + Validation.prototype.addAttribute('minItems', minItems); - each(propertyValue, function(key, value) { + /** + * MinLength + */ + var minLengthAttribute = function minLength(property, propertyValue, attributeValue, propertyAttributes, callback) { - each(patterns, function(index, pattern) { - if (key.match(new RegExp(pattern))) { - matches[key] = attributeValue[pattern]; + if (isString(propertyValue) && propertyValue.length < attributeValue) { + this.addError(); } - }); - }); + return callback(); - if (isEmpty(matches)) { - return callback(); - } + }; - each(matches, function(propertyName, propertySchema, callback) { - return self.validateSchema( - propertyValue[propertyName], - propertySchema, - self.joinPath(property, propertyName), - callback - ); - }, callback); + // Export + Validation.prototype.addAttribute('minLength', minLengthAttribute); - }; + /** + * Pattern + */ + var patternAttribute = function pattern(property, propertyValue, attributeValue, propertyAttributes, callback) { + + if (isString(propertyValue) && !propertyValue.match(attributeValue)) { + this.addError(); + } + + return callback(); - // Export - Validation.prototype.addAttribute('patternProperties', attribute); + }; -}()); + // Export + Validation.prototype.addAttribute('pattern', patternAttribute); -/** - * Required - */ -var requiredAttribute = function required(property, propertyValue, attributeValue, propertyAttributes, callback) { + (function () { - if (attributeValue) { + /** + * PatternProperties + */ + var attribute = function patternProperties(property, propertyValue, attributeValue, propertyAttributes, callback) { - var undefinedCondition = isUndefined(propertyValue); - var emptyCondition = (isString(propertyValue) || isArray(propertyValue) || isObject(propertyValue)) && isEmpty(propertyValue); + // Saves a reference to ‘this’ + var self = this; - if (undefinedCondition || emptyCondition) { - this.addError(); - } + // Skip + if (isEmpty(attributeValue)) { + return callback(); + } - } + var matches = {}; + var patterns = keys(attributeValue); + + each(propertyValue, function (key, value) { - return callback(); + each(patterns, function (index, pattern) { + if (key.match(new RegExp(pattern))) { + matches[key] = attributeValue[pattern]; + } + }); -}; + }); -// Export -Validation.prototype.addAttribute('required', requiredAttribute); + if (isEmpty(matches)) { + return callback(); + } -/** - * Type - */ -var typeConstructor = function typeConstructor() { + each(matches, function (propertyName, propertySchema, callback) { + return self.validateSchema( + propertyValue[propertyName], propertySchema, self.joinPath(property, propertyName), callback); + }, callback); - /** - * Types - */ - var types = { - 'string': isString, - 'number': isNumber, - 'function': isFunction, - 'boolean': isBoolean, - 'object': isObject, - 'array': isArray, - 'integer': isInteger, - 'int': isInteger, - 'null': isNull, - 'any': returnTrue - }; + }; + + // Export + Validation.prototype.addAttribute('patternProperties', attribute); - // Export - return function type(property, propertyValue, attributeValue, propertyAttributes, callback) { + }()); /** - * { - * type: ['string', 'number'] - * } + * Required */ - if (isArray(attributeValue)) { + var requiredAttribute = function required(property, propertyValue, attributeValue, propertyAttributes, callback) { - var noError = attributeValue.some(function(type) { + if (attributeValue) { - if (!hasProperty(types, type)) { - throw new Error('Type ‘' + attributeValue + '’ is not supported.'); - } + var undefinedCondition = isUndefined(propertyValue); + var emptyCondition = (isString(propertyValue) || isArray(propertyValue) || isObject(propertyValue)) && isEmpty(propertyValue); - return types[type](propertyValue); + if (undefinedCondition || emptyCondition) { + this.addError(); + } - }); + } - if (!noError) { - this.errors.addError(); - } + return callback(); + + }; - return callback(); + // Export + Validation.prototype.addAttribute('required', requiredAttribute); /** - * { - * type: 'string' - * } + * Type */ - } else { + var typeConstructor = function typeConstructor() { + + /** + * Types + */ + var types = { + 'string': isString, + 'number': isNumber, + 'function': isFunction, + 'boolean': isBoolean, + 'object': isObject, + 'array': isArray, + 'integer': isInteger, + 'int': isInteger, + 'null': isNull, + 'any': returnTrue + }; - if (!hasProperty(types, attributeValue)) { - throw new Error('Type ‘' + attributeValue + '’ is not supported.'); - } + // Export + return function type(property, propertyValue, attributeValue, propertyAttributes, callback) { - if (!types[attributeValue](propertyValue)) { - this.addError(); - } + /** + * { + * type: ['string', 'number'] + * } + */ + if (isArray(attributeValue)) { - return callback(); + var noError = attributeValue.some(function (type) { - } + if (!hasProperty(types, type)) { + throw new Error('Type ‘' + attributeValue + '’ is not supported.'); + } - }; + return types[type](propertyValue); -}; + }); -// Export -Validation.prototype.addAttributeConstructor('type', typeConstructor); - -(function() { + if (!noError) { + this.errors.addError(); + } - /** - * UniqueItems - */ - var attribute = function uniqueItems(property, propertyValue, attributeValue, propertyAttributes, callback) { + return callback(); - var self = this; + /** + * { + * type: 'string' + * } + */ + } else { - each(propertyValue, function(index, value) { + if (!hasProperty(types, attributeValue)) { + throw new Error('Type ‘' + attributeValue + '’ is not supported.'); + } - if (isString(value)) { - if ((propertyValue.indexOf(value) < index)) { - self.addError(); - } - } + if (!types[attributeValue](propertyValue)) { + this.addError(); + } - if (isObject(value) || isArray(value)) { - propertyValue.forEach(function(subValue, subIndex) { + return callback(); - if (subIndex !== index) { - if (isEqual(value, subValue)) { - self.addError({ - property: self.joinPath(property, subIndex) - }); - } } - }); - } + }; - }); + }; - return callback(); + // Export + Validation.prototype.addAttributeConstructor('type', typeConstructor); - }; + (function () { - // Export - Validation.prototype.addAttribute('uniqueItems', attribute); + /** + * UniqueItems + */ + var attribute = function uniqueItems(property, propertyValue, attributeValue, propertyAttributes, callback) { -}()); + var self = this; -/** - * Error - * - * @constructor - */ -var ValidationError = function(parent) { + each(propertyValue, function (index, value) { - this.length = 0; - - this.errorMessages = parent.messages; + if (isString(value)) { + if ((propertyValue.indexOf(value) < index)) { + self.addError(); + } + } -}; + if (isObject(value) || isArray(value)) { + propertyValue.forEach(function (subValue, subIndex) { -ValidationError.prototype.renderErrorMessage = function(error) { + if (subIndex !== index) { + if (isEqual(value, subValue))  { + self.addError({ + property: self.joinPath(property, subIndex) + }); + } + } - var errorMessage = this.errorMessages[error.attributeName]; + }); + } - if (errorMessage && isFunction(errorMessage)) { - return errorMessage( - error.property, - error.propertyValue, - error.attributeValue - ); - } + }); - if (errorMessage && isString(errorMessage)) { + return callback(); - [ - 'property', - 'propertyValue', - 'attributeValue' - ].forEach(function(placeholder) { - errorMessage = errorMessage.replace(new RegExp('{{' + placeholder + '}}', 'g'), error[placeholder]); - }); + }; - // Deprecated - errorMessage = errorMessage.replace(/{{validator}}/g, error['attributeValue']); + // Export + Validation.prototype.addAttribute('uniqueItems', attribute); - return errorMessage.replace(/\s+/g, ' '); + }()); - } + /** + * Error + * + * @constructor + */ + var ValidationError = function (parent) { - return error.message; + this.length = 0; -}; + this.errorMessages = parent.messages; -ValidationError.prototype.push = function(error) { + }; - this[this.length] = { + ValidationError.prototype.renderErrorMessage = function (error) { - property: error.property, - propertyValue: error.propertyValue, - attributeName: error.attributeName, - attributeValue: error.attributeValue, - message: this.renderErrorMessage(error), + var errorMessage = this.errorMessages[error.attributeName]; - // Deprecated - validator: error.attributeName, - validatorName: error.attributeName, - validatorValue: error.attributeValue + if (errorMessage && isFunction(errorMessage)) { + return errorMessage( + error.property, error.propertyValue, error.attributeValue); + } - }; + if (errorMessage && isString(errorMessage)) { - this.length += 1; + [ + 'property', + 'propertyValue', + 'attributeValue' + ].forEach(function (placeholder) { + errorMessage = errorMessage.replace(new RegExp('{{' + placeholder + '}}', 'g'), error[placeholder]); + }); -}; + // Deprecated + errorMessage = errorMessage.replace(/{{validator}}/g, error['attributeValue']); -/** - * GetProperties - */ -ValidationError.prototype.getProperties = function() { - return pluck(this, 'property'); -}; + return errorMessage.replace(/\s+/g, ' '); -/** - * GetMessages - */ -ValidationError.prototype.getMessages = function() { - return pluck(this, 'message'); -}; + } -/** - * Messages - * -------------------- - */ -var errorMessages = { + return error.message; - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - required: function(property, propertyValue, attributeValue) { - return 'The ‘' + property + '’ property is required.'; - }, + }; - /** - * MinLength - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - minLength: function(property, propertyValue, attributeValue) { - return [ - 'The ' + property + ' property must be at least ' + attributeValue + ' characters.', - 'The length of the property is ' + propertyValue.length + '.' - ].join(' '); - }, + ValidationError.prototype.push = function (error) { - /** - * MaxLength - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - maxLength: function(property, propertyValue, attributeValue) { - return [ - 'The ' + property + ' property must not exceed ' + attributeValue + ' characters.', - 'The length of the property is ' + propertyValue.length + '.' - ].join(' '); - }, + this[this.length] = { - /** - * MaxLength - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - length: function(property, propertyValue, attributeValue) { - return [ - 'The ' + property + ' property must be exactly ' + attributeValue + ' characters.', - 'The length of the property is ' + propertyValue.length + '.' - ].join(' '); - }, - - /** - * Format - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - format: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', - 'The current value of the property is ‘' + propertyValue + '’' - ].join(' '); - }, + property: error.property, + propertyValue: error.propertyValue, + attributeName: error.attributeName, + attributeValue: error.attributeValue, + message: this.renderErrorMessage(error), - /** - * Type - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - type: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', - 'The type of the property is ‘' + detectType(propertyValue) + '’' - ].join(' '); - }, + // Deprecated + validator: error.attributeName, + validatorName: error.attributeName, + validatorValue: error.attributeValue - /** - * Except - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - except: function(property, propertyValue, attributeValue) { - return; - }, + }; - /** - * Minimum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - minimum: function(property, propertyValue, attributeValue) { - return [ - 'The minimum value of the ‘' + property + '’ must be ' + attributeValue + '.', - 'The current value of the property is ‘' + propertyValue + '’' - ].join(' '); - }, + this.length += 1; - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - maximum: function(property, propertyValue, attributeValue) { - return [ - 'The maximum value of the ‘' + property + '’ must be ' + attributeValue + '.', - 'The current value of the property is ‘' + propertyValue + '’.' - ].join(' '); - }, + }; - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - pattern: function(property, propertyValue, attributeValue) { - return 'The ‘' + property + '’ does not match the ‘' + attributeValue + '’ pattern.'; - }, + /** + * GetProperties + */ + ValidationError.prototype.getProperties = function () { + return pluck(this, 'property'); + }; - /** - * MaxItems - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - maxItems: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must not contain more than ‘' + attributeValue + '’ items.', - 'Currently it contains ‘' + propertyValue.items + '’ items.' - ].join(' '); - }, + /** + * GetMessages + */ + ValidationError.prototype.getMessages = function () { + return pluck(this, 'message'); + }; - /** - * MinItems - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - minItems: function(property, propertyValue, attributeValue) { - return [ - 'The ‘' + property + '’ property must contain at least ‘' + attributeValue + '’ items.', - 'Currently it contains ‘' + propertyValue.items + '’ items.' - ].join(' '); - }, + /** + * Messages + * -------------------- + */ + var errorMessages = { - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - divisibleBy: function(property, propertyValue, attributeValue) { - return 'The ‘' + property + '’ is not divisible by ‘' + attributeValue + '’.'; - }, + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + required: function (property, propertyValue, attributeValue) { + return 'The ‘' + property + '’ property is required.'; + }, - /** - * Maximum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - uniqueItems: function(property, propertyValue, attributeValue) { - return 'All items in the ‘' + property + '’ property must be unique.'; - }, + /** + * MinLength + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + minLength: function (property, propertyValue, attributeValue) { + return ['The ' + property + ' property must be at least ' + attributeValue + ' characters.', 'The length of the property is ' + propertyValue.length + '.'].join(' '); + }, - /** - * Enum - * - * @param {string} property - * @param {any} propertyValue - * @param {string} attributeValue - */ - 'enum': function(property, propertyValue, attributeValue) { - return 'Value of the ‘' + property + '’ must be ' + attributeValue.join(' or ') + '.'; - } + /** + * MaxLength + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + maxLength: function (property, propertyValue, attributeValue) { + return ['The ' + property + ' property must not exceed ' + attributeValue + ' characters.', 'The length of the property is ' + propertyValue.length + '.'].join(' '); + }, -}; - -/** - * GetProperty - * - * @param {string} property - * @param {object} source - */ -Validation.prototype.getProperty = function(property, source) { - var tree = property.match(/([a-zA-Z0-9\s]+)/g); - return tree.reduce(function(previousValue, currentValue, index) { - return (previousValue && previousValue[currentValue]) ? previousValue[currentValue] : undefined; - }, source); -}; - -/** - * JoinPath - * - * @param {string} path - * @param {string} property - */ -Validation.prototype.joinPath = function(path, property) { - - // If the ‘path’ is undefined (object), convert the path to a string - path = path || ''; - - // Converts the ‘property’ to a string - property = property + ''; - - if (property.match(/^[a-zA-Z]+$/)) { - return (path) ? (path + '.' + property) : property; - } else if (property.match(/\d+/)) { - return path + '[' + property + ']'; - } else { - return path + '["' + property + '"]'; - } + /** + * MaxLength + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + length: function (property, propertyValue, attributeValue) { + return ['The ' + property + ' property must be exactly ' + attributeValue + ' characters.', 'The length of the property is ' + propertyValue.length + '.'].join(' '); + }, -}; + /** + * Format + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + format: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', 'The current value of the property is ‘' + propertyValue + '’'].join(' '); + }, -/** - * Validation.validate - * - * @param {object} instance - * @param {object} schema - * @param {boolean} singleError - * @param {function} callback - */ -Validation.prototype.validate = function(instance, schema, callback) { + /** + * Type + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + type: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must be a/an ‘' + attributeValue + '’.', 'The type of the property is ‘' + detectType(propertyValue) + '’'].join(' '); + }, - // Save a reference to the ‘this’ - var self = this; + /** + * Except + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + except: function (property, propertyValue, attributeValue) { + return; + }, - this.instance = instance; - this.schema = schema; + /** + * Minimum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + minimum: function (property, propertyValue, attributeValue) { + return ['The minimum value of the ‘' + property + '’ must be ' + attributeValue + '.', 'The current value of the property is ‘' + propertyValue + '’'].join(' '); + }, - /** - * Basic Types - */ - var basicTypes = [ - 'string', - 'number', - 'function', - 'boolean', - 'integer', - 'int', - 'null' - ]; + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + maximum: function (property, propertyValue, attributeValue) { + return ['The maximum value of the ‘' + property + '’ must be ' + attributeValue + '.', 'The current value of the property is ‘' + propertyValue + '’.'].join(' '); + }, - /** - * Object Types - */ - var objectTypes = [ - 'object', - 'array' - ]; + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + pattern: function (property, propertyValue, attributeValue) { + return 'The ‘' + property + '’ does not match the ‘' + attributeValue + '’ pattern.'; + }, - /** - * CallbackProxy - */ - var callbackProxy = function() { - if (self.errors.length !== 0) { - return callback(self.errors); - } else { - return callback(); - } - }; + /** + * MaxItems + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + maxItems: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must not contain more than ‘' + attributeValue + '’ items.', 'Currently it contains ‘' + propertyValue.items + '’ items.'].join(' '); + }, - /** - * { - * type: 'string', - * ... - * } - */ - if (basicTypes.indexOf(schema.type) !== -1) { - return this.validateProperty(undefined, instance, schema, callbackProxy); - } + /** + * MinItems + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + minItems: function (property, propertyValue, attributeValue) { + return ['The ‘' + property + '’ property must contain at least ‘' + attributeValue + '’ items.', 'Currently it contains ‘' + propertyValue.items + '’ items.'].join(' '); + }, - /** - * { - * type: 'object', - * ... - * } - */ - if (objectTypes.indexOf(schema.type) !== -1) { + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + divisibleBy: function (property, propertyValue, attributeValue) { + return 'The ‘' + property + '’ is not divisible by ‘' + attributeValue + '’.'; + }, - if (isString(instance)) { - try { - instance = JSON.parse(instance); - } catch(parseError) { + /** + * Maximum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */ + uniqueItems: function (property, propertyValue, attributeValue) { + return 'All items in the ‘' + property + '’ property must be unique.'; + }, + /** + * Enum + * + * @param {string} property + * @param {any} propertyValue + * @param {string} attributeValue + */'enum': function (property, propertyValue, attributeValue) { + return 'Value of the ‘' + property + '’ must be ' + attributeValue.join(' or ') + '.'; } - } - return this.validateSchema(instance, schema, '', callbackProxy); + }; - } + /** + * GetProperty + * + * @param {string} property + * @param {object} source + */ + Validation.prototype.getProperty = function (property, source) { + var tree = property.match(/([a-zA-Z0-9\s]+)/g); + return tree.reduce(function (previousValue, currentValue, index) { + return (previousValue && isDefined(previousValue[currentValue])) ? previousValue[currentValue] : undefined; + }, source); + }; - /** - * { - * type: ???, - * ... - * } - */ - if (schema.type === 'any' || !schema.type) { + /** + * JoinPath + * + * @param {string} path + * @param {string} property + */ + Validation.prototype.joinPath = function (path, property) { - if (isString(instance)) { - try { - instance = JSON.parse(instance); - return this.validateSchema(instance, schema, '', callbackProxy); - } catch(parseError2) { + // If the ‘path’ is undefined (object), convert the path to a string + path = path || ''; + + // Converts the ‘property’ to a string + property = property + ''; + if (property.match(/^[a-zA-Z]+$/)) { + return (path) ? (path + '.' + property) : property; + } else if (property.match(/\d+/)) { + return path + '[' + property + ']'; + } else { + return path + '["' + property + '"]'; } - } - if (isObject(instance) || isArray (instance)) { - return this.validateSchema(instance, schema, '', callbackProxy); - } + }; - return this.validateProperty(undefined, instance, schema, callbackProxy); + /** + * Validation.validate + * + * @param {object} instance + * @param {object} schema + * @param {boolean} singleError + * @param {function} callback + */ + Validation.prototype.validate = function (instance, schema, callback) { - } + // Save a reference to the ‘this’ + var self = this; -}; + this.instance = instance; + this.schema = schema; -/** - * Validation.validateItems - * - * @param {object} instance - * @param {object} schema - * @param {string} path - * @param {function} callback - */ -Validation.prototype.validateItems = function(instance, schema, path, callback) { + /** + * Basic Types + */ + var basicTypes = [ + 'string', + 'number', + 'function', + 'boolean', + 'integer', + 'int', + 'null' + ]; - // Save a reference to the ‘this’ - var self = this; + /** + * Object Types + */ + var objectTypes = [ + 'object', + 'array' + ]; - /** - * { - * type: 'array' - * items: [ - * { - * type: 'string' - * }, - * { - * type: 'number' - * }, - * ... - * ], - * ... - * } - */ - if (isArray(schema.items)) { - - // Additional items are allowed - if (isUndefined(schema.additionalItems) || schema.additionalItems === true) { - return each(schema.items, function(itemIndex, itemSchema, callback) { - return self.validateSchema( - instance[itemIndex], - itemSchema, - self.joinPath(path, itemIndex), - callback - ); - }, callback); - } - - return each(instance, function(itemIndex, itemValue, callback) { - - // The ‘additionalItems’ attribute is a schema that defines - // the schema of the additional items - if (schema.items[itemIndex] || isObject(schema.additionalItems)) { - return self.validateSchema( - itemValue, - schema.items[itemIndex], - self.joinPath(path, itemIndex), - callback - ); - } + /** + * CallbackProxy + */ + var callbackProxy = function () { + if (self.errors.length !== 0) { + return callback(self.errors); + } else { + return callback(); + } + }; - // Additional items are disallowed - if (schema.additionalItems === false) { - self.errors.push({ - property: self.joinPath(path, itemIndex), - propertyValue: itemValue, - attributeName: 'additionalItems', - attributeValue: false - }); - return callback(); + /** + * { + * type: 'string', + * ... + * } + */ + if (basicTypes.indexOf(schema.type) !== -1) { + return this.validateProperty(undefined, instance, schema, callbackProxy); } - }, callback); + /** + * { + * type: 'object', + * ... + * } + */ + if (objectTypes.indexOf(schema.type) !== -1) { - } + if (isString(instance)) { + try { + instance = JSON.parse(instance); + } catch (parseError) { - /** - * { - * type: 'array' - * items: { - * type: 'string' - * }, - * ... - * } - */ - if (isObject(schema.items) && instance && !isEmpty(instance)) { - return each(instance, function(itemIndex, itemValue, callback) { - return self.validateSchema( - instance[itemIndex], - schema.items, - self.joinPath(path, itemIndex), - callback - ); - }, callback); - } else { - return callback(); - } + } + } -}; + return this.validateSchema(instance, schema, '', callbackProxy); -/** - * Validation.validateProperties - * - * @param {object} instance - * @param {object} schema - * @param {string} path - * @param {function} callback - */ -Validation.prototype.validateProperties = function(instance, schema, path, callback) { - - // Save a reference to the ‘this’ - var self = this; + } - // Goes - return each(schema.properties, function(property, propertyAttributes, callback) { + /** + * { + * type: ???, + * ... + * } + */ + if (schema.type === 'any' || !schema.type) { + + if (isString(instance)) { + try { + instance = JSON.parse(instance); + return this.validateSchema(instance, schema, '', callbackProxy); + } catch (parseError2) { + + } + } + + if (isObject(instance) || isArray(instance)) { + return this.validateSchema(instance, schema, '', callbackProxy); + } - var isObject = propertyAttributes.type === 'object' && propertyAttributes.properties, - isArray = propertyAttributes.type === 'array'; + return this.validateProperty(undefined, instance, schema, callbackProxy); - // Get the value of property (instance[property]) - var propertyValue = self.getProperty(property, instance); - var propertyPath = self.joinPath(path, property); + } + + }; /** - * { - * type: 'object', - * properties: { - * user: { - * type: 'object', - * properties: { - * ... - * } - * } - * } - * } + * Validation.validateItems + * + * @param {object} instance + * @param {object} schema + * @param {string} path + * @param {function} callback */ - if (isObject || isArray) { - return self.validateSchema( - propertyValue, - schema.properties[property], - propertyPath, - callback - ); - } else { - return self.validateProperty( - propertyPath, - propertyValue, - propertyAttributes, - callback - ); - } - - }, callback); - -}; - -/** - * Validation.validateProperty - * - * @param {string} propertyName - * @param {object} propertyAttributes - * @param {string|object} propertyValue - * @param {boolean} singleError - * @param {function} callback - */ -Validation.prototype.validateProperty = function(property, propertyValue, propertyAttributes, callback) { - - // Save a reference to the ‘this’ - var self = this; - - var context = {}; - - [ - 'validateItems', - 'validateProperties', - 'validateSchema', - 'validateProperty', - 'getProperty', - 'attributes', - 'errors', - 'joinPath' - ].forEach(function(key) { - context[key] = this[key]; - }, self); + Validation.prototype.validateItems = function (instance, schema, path, callback) { - /** - * Iterator - * - * @param {string} attributeName - * @param {function} attributeFn - * @param {function} callback - */ - var iterator = function(attributeName, attributeFn, callback) { + // Save a reference to the ‘this’ + var self = this; - var lastLength = self.errors.length; + /** + * { + * type: 'array' + * items: [ + * { + * type: 'string' + * }, + * { + * type: 'number' + * }, + * ... + * ], + * ... + * } + */ + if (isArray(schema.items)) { + + // Additional items are allowed + if (isUndefined(schema.additionalItems) || schema.additionalItems === true) { + return each(schema.items, function (itemIndex, itemSchema, callback) { + return self.validateSchema( + instance[itemIndex], itemSchema, self.joinPath(path, itemIndex), callback); + }, callback); + } - // Overwrite the ‘addError’ method - context.addError = function(message) { + return each(instance, function (itemIndex, itemValue, callback) { + + // The ‘additionalItems’ attribute is a schema that defines + // the schema of the additional items + if (schema.items[itemIndex] || isObject(schema.additionalItems)) { + return self.validateSchema( + itemValue, schema.items[itemIndex], self.joinPath(path, itemIndex), callback); + } + + // Additional items are disallowed + if (schema.additionalItems === false) { + self.errors.push({ + property: self.joinPath(path, itemIndex), + propertyValue: itemValue, + attributeName: 'additionalItems', + attributeValue: false + }); + return callback(); + } + + }, callback); - if (isObject(message)) { - return self.errors.push({ - property: message.property || property, - propertyValue: message.propertyValue || propertyValue, - attributeName: message.attributeName || attributeName, - attributeValue: message.attributeValue || propertyAttributes[attributeName], - message: message.message || undefined - }); } - return self.errors.push({ - property: property, - propertyValue: propertyValue, - attributeName: attributeName, - attributeValue: propertyAttributes[attributeName], - message: message - }); + /** + * { + * type: 'array' + * items: { + * type: 'string' + * }, + * ... + * } + */ + if (isObject(schema.items) && instance && !isEmpty(instance)) { + return each(instance, function (itemIndex, itemValue, callback) { + return self.validateSchema( + instance[itemIndex], schema.items, self.joinPath(path, itemIndex), callback); + }, callback); + } else { + return callback(); + } }; /** - * OnComplete + * Validation.validateProperties + * + * @param {object} instance + * @param {object} schema + * @param {string} path + * @param {function} callback */ - var onComplete = function(error) { - - // Deprecated - if (error === true || isString(error)) { - context.addError(error); - return callback(true); - }; + Validation.prototype.validateProperties = function (instance, schema, path, callback) { + + // Save a reference to the ‘this’ + var self = this; + + // Goes + return each(schema.properties, function (property, propertyAttributes, callback) { + + var isObject = propertyAttributes.type === 'object' && propertyAttributes.properties, + isArray = propertyAttributes.type === 'array'; + + // Get the value of property (instance[property]) + var propertyValue = self.getProperty(property, instance); + var propertyPath = self.joinPath(path, property); + + /** + * { + * type: 'object', + * properties: { + * user: { + * type: 'object', + * properties: { + * ... + * } + * } + * } + * } + */ + if (isObject || isArray) { + return self.validateSchema( + propertyValue, schema.properties[property], propertyPath, callback); + } else { + return self.validateProperty( + propertyPath, propertyValue, propertyAttributes, callback); + } - if (self.errors.length > lastLength && self.singleError) { - return callback(true); - } else { - return callback(); - } + }, callback); }; - if (isDefined(propertyAttributes[attributeName])) { - return attributeFn.apply(context, [ - property, - propertyValue, - propertyAttributes[attributeName], - propertyAttributes, - onComplete - ]); - } else { - return callback(); - } + /** + * Validation.validateProperty + * + * @param {string} propertyName + * @param {object} propertyAttributes + * @param {string|object} propertyValue + * @param {boolean} singleError + * @param {function} callback + */ + Validation.prototype.validateProperty = function (property, propertyValue, propertyAttributes, callback) { + + // Save a reference to the ‘this’ + var self = this; + + var context = {}; + + [ + 'validateItems', + 'validateProperties', + 'validateSchema', + 'validateProperty', + 'getProperty', + 'attributes', + 'errors', + 'joinPath' + ].forEach(function (key) { + context[key] = this[key]; + }, self); + + /** + * Iterator + * + * @param {string} attributeName + * @param {function} attributeFn + * @param {function} callback + */ + var iterator = function (attributeName, attributeFn, callback) { - }; + var lastLength = self.errors.length; - // If it's not a required param and it's empty, skip - if (propertyAttributes.required !== true && isUndefined(propertyValue)) { - return callback(); - } - - // Validate the property - return each(self.attributes, iterator, callback); + // Overwrite the ‘addError’ method + context.addError = function (message) { -}; + if (isObject(message)) { + return self.errors.push({ + property: message.property || property, + propertyValue: message.propertyValue || propertyValue, + attributeName: message.attributeName || attributeName, + attributeValue: message.attributeValue || propertyAttributes[attributeName], + message: message.message || undefined + }); + } -/** - * Validation.validateSchema - * - * @param {object} instance - * @param {object} schema - * @param {string} path - * @param {function} callback - */ -Validation.prototype.validateSchema = function(instance, schema, path, callback) { + return self.errors.push({ + property: property, + propertyValue: propertyValue, + attributeName: attributeName, + attributeValue: propertyAttributes[attributeName], + message: message + }); + + }; + + /** + * OnComplete + */ + var onComplete = function (error) { + + // Deprecated + if (error === true || isString(error)) { + context.addError(error); + return callback(true); + }; + + if (self.errors.length > lastLength && self.singleError) { + return callback(true); + } else { + return callback(); + } + + }; + + if (isDefined(propertyAttributes[attributeName])) { + return attributeFn.apply(context, [ + property, + propertyValue, + propertyAttributes[attributeName], + propertyAttributes, + onComplete + ]); + } else { + return callback(); + } - var self = this; + }; - return self.validateProperty(path, instance, schema, function(error) { + // If it's not a required param and it's empty, skip + if (propertyAttributes.required !== true && isUndefined(propertyValue)) { + return callback(); + } - /** - * { - * type: 'object', - * properties: { - * ... - * } - * } - */ - if (schema.properties) { - return self.validateProperties( - instance, - schema, - path, - callback - ); + // Validate the property + return each(self.attributes, iterator, callback); - /** - * { - * type: 'array', - * items: { - * type: 'string' - * ... - * } - * } - */ - } else if (schema.items) { - return self.validateItems( - instance, - schema, - path, - callback - ); + }; /** - * { - * type: 'array' - * } - * — or — - * { - * type: 'object' - * } + * Validation.validateSchema + * + * @param {object} instance + * @param {object} schema + * @param {string} path + * @param {function} callback */ - } else { - return callback(); - } + Validation.prototype.validateSchema = function (instance, schema, path, callback) { + + var self = this; + + return self.validateProperty(path, instance, schema, function (error) { + + /** + * { + * type: 'object', + * properties: { + * ... + * } + * } + */ + if (schema.properties) { + return self.validateProperties( + instance, schema, path, callback); + + /** + * { + * type: 'array', + * items: { + * type: 'string' + * ... + * } + * } + */ + } else if (schema.items) { + return self.validateItems( + instance, schema, path, callback); + + /** + * { + * type: 'array' + * } + * — or — + * { + * type: 'object' + * } + */ + } else { + return callback(); + } - }); + }); -}; + }; - - /** - * Export - * -------------------- - */ - engines.json = (function() { /** - * Cache + * Export + * -------------------- */ - var cache = []; - var cacheIndex = {}; - - return { + engines.json = (function () { /** - * Validate - * - * @param {object} instance - * @param {object} schema - * @param {object} options - * @param {function} callback + * Cache */ - validate: function(instance, schema, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; + var cache = []; + var cacheIndex = {}; + + return { + + /** + * Validate + * + * @param {object} instance + * @param {object} schema + * @param {object} options + * @param {function} callback + */ + validate: function (instance, schema, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + return (new Validation(options)).validate(instance, schema, callback); + }, + + /** + * AddAttribute + * + * @param {string} attributeName + * @param {function} attributeFn + */ + addAttribute: function (attributeName, attributeFn) { + return Validation.prototype.addAttribute.apply(Validation, arguments); + }, + + /** + * AddAttributeConstructor + * + * @param {string} attributeName + * @param {function} attributeConstructor + */ + addAttributeConstructor: function (attributeName, attributeConstructor) { + return Validation.prototype.addAttributeConstructor.apply(Validation, arguments); } - return (new Validation(options)).validate(instance, schema, callback); - }, - /** - * AddAttribute - * - * @param {string} attributeName - * @param {function} attributeFn - */ - addAttribute: function(attributeName, attributeFn) { - return Validation.prototype.addAttribute.apply(Validation, arguments); - }, + }; - /** - * AddAttributeConstructor - * - * @param {string} attributeName - * @param {function} attributeConstructor - */ - addAttributeConstructor: function(attributeName, attributeConstructor) { - return Validation.prototype.addAttributeConstructor.apply(Validation, arguments); + }()); + + }()); + + var amanda = function (engine) { + + if (!hasProperty(engines, engine)) { + throw new Error('The ‘' + engine + '’ engine is not supported. Please use a different one.'); } - }; + return engines[engine]; - }()); + }; -}()); + /** + * Amanda.validate + * + * This method is deprecated, please use ‘amanda('json').validate’ instead. + */ + amanda.validate = function (instance, schema, options, callback) { + var json = engines.json; + return json.validate.apply(json, arguments); + }; -var amanda = function(engine) { + /** + * Amanda.addValidator + * + * This method is deprecated, please use ‘amanda('json').addValidator’ instead. + */ + amanda.addValidator = function (attributeName, attributeFn) { + var json = engines.json; + return json.addAttribute.apply(json, arguments); + }; - if (!hasProperty(engines, engine)) { - throw new Error('The ‘' + engine + '’ engine is not supported. Please use a different one.'); - } + /** + * Amanda.addAttribute + * + * This method is deprecated, please use ‘amanda('json').addAttribute’ instead. + */ + amanda.addAttribute = function (attributeName, attributeFn) { + var json = engines.json; + return json.addAttribute.apply(json, arguments); + }; - return engines[engine]; - -}; - -/** - * Amanda.validate - * - * This method is deprecated, please use ‘amanda('json').validate’ instead. - */ -amanda.validate = function(instance, schema, options, callback) { - var json = engines.json; - return json.validate.apply(json, arguments); -}; - -/** - * Amanda.addValidator - * - * This method is deprecated, please use ‘amanda('json').addValidator’ instead. - */ -amanda.addValidator = function(attributeName, attributeFn) { - var json = engines.json; - return json.addAttribute.apply(json, arguments); -}; - -/** - * Amanda.addAttribute - * - * This method is deprecated, please use ‘amanda('json').addAttribute’ instead. - */ -amanda.addAttribute = function(attributeName, attributeFn) { - var json = engines.json; - return json.addAttribute.apply(json, arguments); -}; - -/** - * Amanda.addAttributeConstructor - * - * This method is deprecated, please use ‘amanda('json').addAttributeConstructor’ instead. - */ -amanda.addAttributeConstructor = function(attributeName, attributeConstructor) { - var json = engines.json; - return json.addAttributeConstructor.apply(json, arguments); -}; + /** + * Amanda.addAttributeConstructor + * + * This method is deprecated, please use ‘amanda('json').addAttributeConstructor’ instead. + */ + amanda.addAttributeConstructor = function (attributeName, attributeConstructor) { + var json = engines.json; + return json.addAttributeConstructor.apply(json, arguments); + }; /** * Export @@ -2004,7 +1926,7 @@ amanda.addAttributeConstructor = function(attributeName, attributeConstructor) { if (typeof module !== 'undefined' && module.exports) { module.exports = amanda; } else if (typeof define !== 'undefined') { - define(function() { + define(function () { return amanda; }); } else { diff --git a/releases/latest/amanda.min.js b/releases/latest/amanda.min.js index f08569f..e49bdd0 100644 --- a/releases/latest/amanda.min.js +++ b/releases/latest/amanda.min.js @@ -1,26 +1,26 @@ -(function(){var p={},n=function(a,g,h){var f=function(b,f,a){var c=[],j=function(b,i){var j=c.length+1;c.push(function(){return f(b,i,function(b){var f=c[j];return!b&&f?f():!b&&!f?a():a(b)})})};if(k(b)&&!o(b))for(var i=0,g=b.length;i=a||b>a)&&this.addError();return c()});a.prototype.addAttribute("maxItems",function(f,b,a,e,c){k(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("maxLength",function(f,b,a,e,c){m(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("minimum",function(f,b,a,e,c){q(b)&&(e.exclusiveMinimum&&b<=a||b=a||b>a)&&this.addError();return c()});a.prototype.addAttribute("maxItems",function(f,b,a,e,c){l(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("maxLength",function(f,b,a,e,c){m(b)&&b.length>a&&this.addError();return c()});a.prototype.addAttribute("minimum",function(f,b,a,e,c){q(b)&&(e.exclusiveMinimum&&b<=a||bn&&c.singleError?k(!0):k()}]):k()},e)};a.prototype.validateSchema=function(a,b,d,e){var c=this;return c.validateProperty(d,a,b,function(){return b.properties? -c.validateProperties(a,b,d,e):b.items?c.validateItems(a,b,d,e):e()})};p.json=function(){return{validate:function(f,b,d,e){"function"===typeof d&&(e=d,d={});return(new a(d)).validate(f,b,e)},addAttribute:function(f,b){return a.prototype.addAttribute.apply(a,arguments)},addAttributeConstructor:function(f,b){return a.prototype.addAttributeConstructor.apply(a,arguments)}}}()})();var r=function(a){if(!Object.prototype.hasOwnProperty.apply(p,[a]))throw Error("The \u2018"+a+"\u2019 engine is not supported. Please use a different one."); +a+"\u2019 must be "+d.join(" or ")+"."}};a.prototype.getProperty=function(a,b){return a.match(/([a-zA-Z0-9\s]+)/g).reduce(function(a,b){return a&&s(a[b])?a[b]:void 0},b)};a.prototype.joinPath=function(a,b){a=a||"";b+="";return b.match(/^[a-zA-Z]+$/)?a?a+"."+b:b:b.match(/\d+/)?a+"["+b+"]":a+'["'+b+'"]'};a.prototype.validate=function(a,b,d){var e=this;this.instance=a;this.schema=b;var c=function(){return 0!==e.errors.length?d(e.errors):d()};if(-1!=="string,number,function,boolean,integer,int,null".split(",").indexOf(b.type))return this.validateProperty(void 0, +a,b,c);if(-1!==["object","array"].indexOf(b.type)){if(m(a))try{a=JSON.parse(a)}catch(j){}return this.validateSchema(a,b,"",c)}if("any"===b.type||!b.type){if(m(a))try{return a=JSON.parse(a),this.validateSchema(a,b,"",c)}catch(i){}return k(a)||l(a)?this.validateSchema(a,b,"",c):this.validateProperty(void 0,a,b,c)}};a.prototype.validateItems=function(a,b,d,e){var c=this;return l(b.items)?u(b.additionalItems)||!0===b.additionalItems?n(b.items,function(b,e,g){return c.validateSchema(a[b],e,c.joinPath(d, +b),g)},e):n(a,function(a,f,e){if(b.items[a]||k(b.additionalItems))return c.validateSchema(f,b.items[a],c.joinPath(d,a),e);if(!1===b.additionalItems)return c.errors.push({property:c.joinPath(d,a),propertyValue:f,attributeName:"additionalItems",attributeValue:!1}),e()},e):k(b.items)&&a&&!o(a)?n(a,function(e,i,g){return c.validateSchema(a[e],b.items,c.joinPath(d,e),g)},e):e()};a.prototype.validateProperties=function(a,b,d,e){var c=this;return n(b.properties,function(e,i,g){var h="object"===i.type&&i.properties, +k="array"===i.type,m=c.getProperty(e,a),v=c.joinPath(d,e);return h||k?c.validateSchema(m,b.properties[e],v,g):c.validateProperty(v,m,i,g)},e)};a.prototype.validateProperty=function(a,b,d,e){var c=this,g={};"validateItems,validateProperties,validateSchema,validateProperty,getProperty,attributes,errors,joinPath".split(",").forEach(function(a){g[a]=this[a]},c);return!0!==d.required&&u(b)?e():n(c.attributes,function(e,h,l){var n=c.errors.length;g.addError=function(g){return k(g)?c.errors.push({property:g.property|| +a,propertyValue:g.propertyValue||b,attributeName:g.attributeName||e,attributeValue:g.attributeValue||d[e],message:g.message||void 0}):c.errors.push({property:a,propertyValue:b,attributeName:e,attributeValue:d[e],message:g})};var o=function(a){return!0===a||m(a)?(g.addError(a),l(!0)):c.errors.length>n&&c.singleError?l(!0):l()};return s(d[e])?h.apply(g,[a,b,d[e],d,o]):l()},e)};a.prototype.validateSchema=function(a,b,d,e){var c=this;return c.validateProperty(d,a,b,function(){return b.properties?c.validateProperties(a, +b,d,e):b.items?c.validateItems(a,b,d,e):e()})};p.json=function(){return{validate:function(f,b,d,e){"function"===typeof d&&(e=d,d={});return(new a(d)).validate(f,b,e)},addAttribute:function(f,b){return a.prototype.addAttribute.apply(a,arguments)},addAttributeConstructor:function(f,b){return a.prototype.addAttributeConstructor.apply(a,arguments)}}}()})();var r=function(a){if(!Object.prototype.hasOwnProperty.apply(p,[a]))throw Error("The \u2018"+a+"\u2019 engine is not supported. Please use a different one."); return p[a]};r.validate=function(a,g,h,f){var b=p.json;return b.validate.apply(b,arguments)};r.addValidator=function(a,g){var h=p.json;return h.addAttribute.apply(h,arguments)};r.addAttribute=function(a,g){var h=p.json;return h.addAttribute.apply(h,arguments)};r.addAttributeConstructor=function(a,g){var h=p.json;return h.addAttributeConstructor.apply(h,arguments)};"undefined"!==typeof module&&module.exports?module.exports=r:"undefined"!==typeof define?define(function(){return r}):this.amanda=r})(); \ No newline at end of file diff --git a/src/engines/json/getProperty.js b/src/engines/json/getProperty.js index 27d83d1..9601e1a 100644 --- a/src/engines/json/getProperty.js +++ b/src/engines/json/getProperty.js @@ -7,6 +7,6 @@ Validation.prototype.getProperty = function(property, source) { var tree = property.match(/([a-zA-Z0-9\s]+)/g); return tree.reduce(function(previousValue, currentValue, index) { - return (previousValue && previousValue[currentValue]) ? previousValue[currentValue] : undefined; + return (previousValue && isDefined(previousValue[currentValue])) ? previousValue[currentValue] : undefined; }, source); }; \ No newline at end of file