diff options
Diffstat (limited to 'node_modules/revalidator/lib/revalidator.js')
-rw-r--r-- | node_modules/revalidator/lib/revalidator.js | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/node_modules/revalidator/lib/revalidator.js b/node_modules/revalidator/lib/revalidator.js new file mode 100644 index 0000000..ad65814 --- /dev/null +++ b/node_modules/revalidator/lib/revalidator.js @@ -0,0 +1,427 @@ +(function (exports) { + exports.validate = validate; + exports.mixin = mixin; + + // + // ### function validate (object, schema, options) + // #### {Object} object the object to validate. + // #### {Object} schema (optional) the JSON Schema to validate against. + // #### {Object} options (optional) options controlling the validation + // process. See {@link #validate.defaults) for details. + // Validate <code>object</code> against a JSON Schema. + // If <code>object</code> is self-describing (i.e. has a + // <code>$schema</code> property), it will also be validated + // against the referenced schema. [TODO]: This behaviour bay be + // suppressed by setting the {@link #validate.options.???} + // option to <code>???</code>.[/TODO] + // + // If <code>schema</code> is not specified, and <code>object</code> + // is not self-describing, validation always passes. + // + // <strong>Note:</strong> in order to pass options but no schema, + // <code>schema</code> <em>must</em> be specified in the call to + // <code>validate()</code>; otherwise, <code>options</code> will + // be interpreted as the schema. <code>schema</code> may be passed + // as <code>null</code>, <code>undefinded</code>, or the empty object + // (<code>{}</code>) in this case. + // + function validate(object, schema, options) { + options = mixin({}, options, validate.defaults); + var errors = []; + + validateObject(object, schema, options, errors); + + // + // TODO: self-described validation + // if (! options.selfDescribing) { ... } + // + + return { + valid: !(errors.length), + errors: errors + }; + }; + + /** + * Default validation options. Defaults can be overridden by + * passing an 'options' hash to {@link #validate}. They can + * also be set globally be changing the values in + * <code>validate.defaults</code> directly. + */ + validate.defaults = { + /** + * <p> + * Enforce 'format' constraints. + * </p><p> + * <em>Default: <code>true</code></em> + * </p> + */ + validateFormats: true, + /** + * <p> + * When {@link #validateFormats} is <code>true</code>, + * treat unrecognized formats as validation errors. + * </p><p> + * <em>Default: <code>false</code></em> + * </p> + * + * @see validation.formats for default supported formats. + */ + validateFormatsStrict: false, + /** + * <p> + * When {@link #validateFormats} is <code>true</code>, + * also validate formats defined in {@link #validate.formatExtensions}. + * </p><p> + * <em>Default: <code>true</code></em> + * </p> + */ + validateFormatExtensions: true + }; + + /** + * Default messages to include with validation errors. + */ + validate.messages = { + required: "is required", + allowEmpty: "must not be empty", + minLength: "is too short (minimum is %{expected} characters)", + maxLength: "is too long (maximum is %{expected} characters)", + pattern: "invalid input", + minimum: "must be greater than or equal to %{expected}", + maximum: "must be less than or equal to %{expected}", + exclusiveMinimum: "must be greater than %{expected}", + exclusiveMaximum: "must be less than %{expected}", + divisibleBy: "must be divisible by %{expected}", + minItems: "must contain more than %{expected} items", + maxItems: "must contain less than %{expected} items", + uniqueItems: "must hold a unique set of values", + format: "is not a valid %{expected}", + conform: "must conform to given constraint", + type: "must be of %{expected} type" + }; + validate.messages['enum'] = "must be present in given enumerator"; + + /** + * + */ + validate.formats = { + 'email': /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i, + 'ip-address': /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i, + 'ipv6': /^([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}$/, + 'date-time': /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d{1,3})?Z$/, + 'date': /^\d{4}-\d{2}-\d{2}$/, + 'time': /^\d{2}:\d{2}:\d{2}$/, + 'color': /^#[a-z0-9]{6}|#[a-z0-9]{3}|(?:rgb\(\s*(?:[+-]?\d+%?)\s*,\s*(?:[+-]?\d+%?)\s*,\s*(?:[+-]?\d+%?)\s*\))aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow$/i, + //'style': (not supported) + //'phone': (not supported) + //'uri': (not supported) + 'host-name': /^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])/, + 'utc-millisec': { + test: function (value) { + return typeof(value) === 'number' && value >= 0; + } + }, + 'regex': { + test: function (value) { + try { new RegExp(value) } + catch (e) { return false } + + return true; + } + } + }; + + /** + * + */ + validate.formatExtensions = { + 'url': /^(https?|ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i + }; + + function mixin(obj) { + var sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + var source = sources.shift(); + if (!source) { continue } + + if (typeof(source) !== 'object') { + throw new TypeError('mixin non-object'); + } + + for (var p in source) { + if (source.hasOwnProperty(p)) { + obj[p] = source[p]; + } + } + } + + return obj; + }; + + function validateObject(object, schema, options, errors) { + var props, allProps = Object.keys(object), + visitedProps = []; + + // see 5.2 + if (schema.properties) { + props = schema.properties; + for (var p in props) { + if (props.hasOwnProperty(p)) { + visitedProps.push(p); + validateProperty(object, object[p], p, props[p], options, errors); + } + } + } + + // see 5.3 + if (schema.patternProperties) { + props = schema.patternProperties; + for (var p in props) { + if (props.hasOwnProperty(p)) { + var re = new RegExp(p); + + // Find all object properties that are matching `re` + for (var k in object) { + if (object.hasOwnProperty(k)) { + visitedProps.push(k); + if (re.exec(k) !== null) { + validateProperty(object, object[k], p, props[p], options, errors); + } + } + } + } + } + } + + // see 5.4 + if (undefined !== schema.additionalProperties) { + var i, l; + + var unvisitedProps = allProps.filter(function(k){ + return -1 === visitedProps.indexOf(k); + }); + + // Prevent additional properties; each unvisited property is therefore an error + if (schema.additionalProperties === false && unvisitedProps.length > 0) { + for (i = 0, l = unvisitedProps.length; i < l; i++) { + error("additionalProperties", unvisitedProps[i], object[unvisitedProps[i]], false, errors); + } + } + // additionalProperties is a schema and validate unvisited properties against that schema + else if (typeof schema.additionalProperties == "object" && unvisitedProps.length > 0) { + for (i = 0, l = unvisitedProps.length; i < l; i++) { + validateProperty(object, object[unvisitedProps[i]], unvisitedProps[i], schema.unvisitedProperties, options, errors); + } + } + } + + }; + + function validateProperty(object, value, property, schema, options, errors) { + var format, + valid, + spec, + type; + + function constrain(name, value, assert) { + if (schema[name] !== undefined && !assert(value, schema[name])) { + error(name, property, value, schema, errors); + } + } + + if (value === undefined) { + if (schema.required && schema.type !== 'any') { + return error('required', property, undefined, schema, errors); + } else { + return; + } + } + + if (options.cast) { + if (('integer' === schema.type || 'number' === schema.type) && value == +value) { + value = +value; + object[property] = value; + } + + if ('boolean' === schema.type) { + if ('true' === value || '1' === value || 1 === value) { + value = true; + object[property] = value; + } + + if ('false' === value || '0' === value || 0 === value) { + value = false; + object[property] = value; + } + } + } + + if (schema.format && options.validateFormats) { + format = schema.format; + + if (options.validateFormatExtensions) { spec = validate.formatExtensions[format] } + if (!spec) { spec = validate.formats[format] } + if (!spec) { + if (options.validateFormatsStrict) { + return error('format', property, value, schema, errors); + } + } + else { + if (!spec.test(value)) { + return error('format', property, value, schema, errors); + } + } + } + + if (schema['enum'] && schema['enum'].indexOf(value) === -1) { + error('enum', property, value, schema, errors); + } + + // Dependencies (see 5.8) + if (typeof schema.dependencies === 'string' && + object[schema.dependencies] === undefined) { + error('dependencies', property, null, schema, errors); + } + + if (isArray(schema.dependencies)) { + for (var i = 0, l = schema.dependencies.length; i < l; i++) { + if (object[schema.dependencies[i]] === undefined) { + error('dependencies', property, null, schema, errors); + } + } + } + + if (typeof schema.dependencies === 'object') { + validateObject(object, schema.dependencies, options, errors); + } + + checkType(value, schema.type, function(err, type) { + if (err) return error('type', property, typeof value, schema, errors); + + constrain('conform', value, function (a, e) { return e(a, object) }); + + switch (type || (isArray(value) ? 'array' : typeof value)) { + case 'string': + constrain('allowEmpty', value, function (a, e) { return e ? e : a !== '' }); + constrain('minLength', value.length, function (a, e) { return a >= e }); + constrain('maxLength', value.length, function (a, e) { return a <= e }); + constrain('pattern', value, function (a, e) { + e = typeof e === 'string' + ? e = new RegExp(e) + : e; + return e.test(a) + }); + break; + case 'integer': + case 'number': + constrain('minimum', value, function (a, e) { return a >= e }); + constrain('maximum', value, function (a, e) { return a <= e }); + constrain('exclusiveMinimum', value, function (a, e) { return a > e }); + constrain('exclusiveMaximum', value, function (a, e) { return a < e }); + constrain('divisibleBy', value, function (a, e) { + var multiplier = Math.max((a - Math.floor(a)).toString().length - 2, (e - Math.floor(e)).toString().length - 2); + multiplier = multiplier > 0 ? Math.pow(10, multiplier) : 1; + return (a * multiplier) % (e * multiplier) === 0 + }); + break; + case 'array': + constrain('items', value, function (a, e) { + for (var i = 0, l = a.length; i < l; i++) { + validateProperty(object, a[i], property, e, options, errors); + } + return true; + }); + constrain('minItems', value, function (a, e) { return a.length >= e }); + constrain('maxItems', value, function (a, e) { return a.length <= e }); + constrain('uniqueItems', value, function (a) { + var h = {}; + + for (var i = 0, l = a.length; i < l; i++) { + var key = JSON.stringify(a[i]); + if (h[key]) return false; + h[key] = true; + } + + return true; + }); + break; + case 'object': + // Recursive validation + if (schema.properties || schema.patternProperties || schema.additionalProperties) { + validateObject(value, schema, options, errors); + } + break; + } + }); + }; + + function checkType(val, type, callback) { + var result = false, + types = isArray(type) ? type : [type]; + + // No type - no check + if (type === undefined) return callback(null, type); + + // Go through available types + // And fine first matching + for (var i = 0, l = types.length; i < l; i++) { + type = types[i].toLowerCase().trim(); + if (type === 'string' ? typeof val === 'string' : + type === 'array' ? isArray(val) : + type === 'object' ? val && typeof val === 'object' && + !isArray(val) : + type === 'number' ? typeof val === 'number' : + type === 'integer' ? typeof val === 'number' && ~~val === val : + type === 'null' ? val === null : + type === 'boolean'? typeof val === 'boolean' : + type === 'date' ? isDate(val) : + type === 'any' ? typeof val !== 'undefined' : false) { + return callback(null, type); + } + }; + + callback(true); + }; + + function error(attribute, property, actual, schema, errors) { + var lookup = { expected: schema[attribute], actual: actual, attribute: attribute, property: property }; + var message = schema.messages && schema.messages[attribute] || schema.message || validate.messages[attribute] || "no default message"; + message = message.replace(/%\{([a-z]+)\}/ig, function (_, match) { return lookup[match.toLowerCase()] || ''; }); + errors.push({ + attribute: attribute, + property: property, + expected: schema[attribute], + actual: actual, + message: message + }); + }; + + function isArray(value) { + var s = typeof value; + if (s === 'object') { + if (value) { + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length')) && + typeof value.splice === 'function') { + return true; + } + } + } + return false; + } + + function isDate(value) { + var s = typeof value; + if (s === 'object') { + if (value) { + if (typeof value.getTime === 'function') { + return true; + } + } + } + + return false; + } + +})(typeof module === 'object' && module && module.exports ? module.exports : window); |