ember-z-schema
Version:
JSON Schema validator for Ember apps
1,380 lines (1,225 loc) • 111 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ZSchema = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// shim for using process in browser
var process = module.exports = {};
// cached from whatever global is present so that test runners that stub it
// don't break things. But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals. It's inside a
// function because try/catches deoptimize in certain engines.
var cachedSetTimeout;
var cachedClearTimeout;
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
throw new Error('clearTimeout has not been defined');
}
(function () {
try {
if (typeof setTimeout === 'function') {
cachedSetTimeout = setTimeout;
} else {
cachedSetTimeout = defaultSetTimout;
}
} catch (e) {
cachedSetTimeout = defaultSetTimout;
}
try {
if (typeof clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
} else {
cachedClearTimeout = defaultClearTimeout;
}
} catch (e) {
cachedClearTimeout = defaultClearTimeout;
}
} ())
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
//normal enviroments in sane situations
return setTimeout(fun, 0);
}
// if setTimeout wasn't available but was latter defined
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedSetTimeout(fun, 0);
} catch(e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedSetTimeout.call(null, fun, 0);
} catch(e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
return cachedSetTimeout.call(this, fun, 0);
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
//normal enviroments in sane situations
return clearTimeout(marker);
}
// if clearTimeout wasn't available but was latter defined
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedClearTimeout(marker);
} catch (e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedClearTimeout.call(null, marker);
} catch (e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
return cachedClearTimeout.call(this, marker);
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
};
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}],2:[function(require,module,exports){
"use strict";
module.exports = {
INVALID_TYPE: "Expected type {0} but found type {1}",
INVALID_FORMAT: "Object didn't pass validation for format {0}: {1}",
ENUM_MISMATCH: "No enum match for: {0}",
ANY_OF_MISSING: "Data does not match any schemas from 'anyOf'",
ONE_OF_MISSING: "Data does not match any schemas from 'oneOf'",
ONE_OF_MULTIPLE: "Data is valid against more than one schema from 'oneOf'",
NOT_PASSED: "Data matches schema from 'not'",
// Array errors
ARRAY_LENGTH_SHORT: "Array is too short ({0}), minimum {1}",
ARRAY_LENGTH_LONG: "Array is too long ({0}), maximum {1}",
ARRAY_UNIQUE: "Array items are not unique (indexes {0} and {1})",
ARRAY_ADDITIONAL_ITEMS: "Additional items not allowed",
// Numeric errors
MULTIPLE_OF: "Value {0} is not a multiple of {1}",
MINIMUM: "Value {0} is less than minimum {1}",
MINIMUM_EXCLUSIVE: "Value {0} is equal or less than exclusive minimum {1}",
MAXIMUM: "Value {0} is greater than maximum {1}",
MAXIMUM_EXCLUSIVE: "Value {0} is equal or greater than exclusive maximum {1}",
// Object errors
OBJECT_PROPERTIES_MINIMUM: "Too few properties defined ({0}), minimum {1}",
OBJECT_PROPERTIES_MAXIMUM: "Too many properties defined ({0}), maximum {1}",
OBJECT_MISSING_REQUIRED_PROPERTY: "Missing required property: {0}",
OBJECT_ADDITIONAL_PROPERTIES: "Additional properties not allowed: {0}",
OBJECT_DEPENDENCY_KEY: "Dependency failed - key must exist: {0} (due to key: {1})",
// String errors
MIN_LENGTH: "String is too short ({0} chars), minimum {1}",
MAX_LENGTH: "String is too long ({0} chars), maximum {1}",
PATTERN: "String does not match pattern {0}: {1}",
// Schema validation errors
KEYWORD_TYPE_EXPECTED: "Keyword '{0}' is expected to be of type '{1}'",
KEYWORD_UNDEFINED_STRICT: "Keyword '{0}' must be defined in strict mode",
KEYWORD_UNEXPECTED: "Keyword '{0}' is not expected to appear in the schema",
KEYWORD_MUST_BE: "Keyword '{0}' must be {1}",
KEYWORD_DEPENDENCY: "Keyword '{0}' requires keyword '{1}'",
KEYWORD_PATTERN: "Keyword '{0}' is not a valid RegExp pattern: {1}",
KEYWORD_VALUE_TYPE: "Each element of keyword '{0}' array must be a '{1}'",
UNKNOWN_FORMAT: "There is no validation function for format '{0}'",
CUSTOM_MODE_FORCE_PROPERTIES: "{0} must define at least one property if present",
// Remote errors
REF_UNRESOLVED: "Reference has not been resolved during compilation: {0}",
UNRESOLVABLE_REFERENCE: "Reference could not be resolved: {0}",
SCHEMA_NOT_REACHABLE: "Validator was not able to read schema with uri: {0}",
SCHEMA_TYPE_EXPECTED: "Schema is expected to be of type 'object'",
SCHEMA_NOT_AN_OBJECT: "Schema is not an object: {0}",
ASYNC_TIMEOUT: "{0} asynchronous task(s) have timed out after {1} ms",
PARENT_SCHEMA_VALIDATION_FAILED: "Schema failed to validate against its parent schema, see inner errors for details.",
REMOTE_NOT_VALID: "Remote reference didn't compile successfully: {0}"
};
},{}],3:[function(require,module,exports){
/*jshint maxlen: false*/
var validator = window.validator;
var FormatValidators = {
"date": function (date) {
if (typeof date !== "string") {
return true;
}
// full-date from http://tools.ietf.org/html/rfc3339#section-5.6
var matches = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.exec(date);
if (matches === null) {
return false;
}
// var year = matches[1];
// var month = matches[2];
// var day = matches[3];
if (matches[2] < "01" || matches[2] > "12" || matches[3] < "01" || matches[3] > "31") {
return false;
}
return true;
},
"date-time": function (dateTime) {
if (typeof dateTime !== "string") {
return true;
}
// date-time from http://tools.ietf.org/html/rfc3339#section-5.6
var s = dateTime.toLowerCase().split("t");
if (!FormatValidators.date(s[0])) {
return false;
}
var matches = /^([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$/.exec(s[1]);
if (matches === null) {
return false;
}
// var hour = matches[1];
// var minute = matches[2];
// var second = matches[3];
// var fraction = matches[4];
// var timezone = matches[5];
if (matches[1] > "23" || matches[2] > "59" || matches[3] > "59") {
return false;
}
return true;
},
"email": function (email) {
if (typeof email !== "string") {
return true;
}
return validator.isEmail(email, { "require_tld": true });
},
"hostname": function (hostname) {
if (typeof hostname !== "string") {
return true;
}
/*
http://json-schema.org/latest/json-schema-validation.html#anchor114
A string instance is valid against this attribute if it is a valid
representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
http://tools.ietf.org/html/rfc1034#section-3.5
<digit> ::= any one of the ten digits 0 through 9
var digit = /[0-9]/;
<letter> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
var letter = /[a-zA-Z]/;
<let-dig> ::= <letter> | <digit>
var letDig = /[0-9a-zA-Z]/;
<let-dig-hyp> ::= <let-dig> | "-"
var letDigHyp = /[-0-9a-zA-Z]/;
<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
var ldhStr = /[-0-9a-zA-Z]+/;
<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
var label = /[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?/;
<subdomain> ::= <label> | <subdomain> "." <label>
var subdomain = /^[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?(\.[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?)*$/;
<domain> ::= <subdomain> | " "
var domain = null;
*/
var valid = /^[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?(\.[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?)*$/.test(hostname);
if (valid) {
// the sum of all label octets and label lengths is limited to 255.
if (hostname.length > 255) { return false; }
// Each node has a label, which is zero to 63 octets in length
var labels = hostname.split(".");
for (var i = 0; i < labels.length; i++) { if (labels[i].length > 63) { return false; } }
}
return valid;
},
"host-name": function (hostname) {
return FormatValidators.hostname.call(this, hostname);
},
"ipv4": function (ipv4) {
if (typeof ipv4 !== "string") { return true; }
return validator.isIP(ipv4, 4);
},
"ipv6": function (ipv6) {
if (typeof ipv6 !== "string") { return true; }
return validator.isIP(ipv6, 6);
},
"regex": function (str) {
try {
RegExp(str);
return true;
} catch (e) {
return false;
}
},
"uri": function (uri) {
if (this.options.strictUris) {
return FormatValidators["strict-uri"].apply(this, arguments);
}
// https://github.com/zaggino/z-schema/issues/18
// RegExp from http://tools.ietf.org/html/rfc3986#appendix-B
return typeof uri !== "string" || RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?").test(uri);
},
"strict-uri": function (uri) {
return typeof uri !== "string" || validator.isURL(uri);
}
};
module.exports = FormatValidators;
},{}],4:[function(require,module,exports){
"use strict";
var FormatValidators = require("./FormatValidators"),
Report = require("./Report"),
Utils = require("./Utils");
var JsonValidators = {
multipleOf: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.1.2
if (typeof json !== "number") {
return;
}
if (Utils.whatIs(json / schema.multipleOf) !== "integer") {
report.addError("MULTIPLE_OF", [json, schema.multipleOf], null, schema.description);
}
},
maximum: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.2.2
if (typeof json !== "number") {
return;
}
if (schema.exclusiveMaximum !== true) {
if (json > schema.maximum) {
report.addError("MAXIMUM", [json, schema.maximum], null, schema.description);
}
} else {
if (json >= schema.maximum) {
report.addError("MAXIMUM_EXCLUSIVE", [json, schema.maximum], null, schema.description);
}
}
},
exclusiveMaximum: function () {
// covered in maximum
},
minimum: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.3.2
if (typeof json !== "number") {
return;
}
if (schema.exclusiveMinimum !== true) {
if (json < schema.minimum) {
report.addError("MINIMUM", [json, schema.minimum], null, schema.description);
}
} else {
if (json <= schema.minimum) {
report.addError("MINIMUM_EXCLUSIVE", [json, schema.minimum], null, schema.description);
}
}
},
exclusiveMinimum: function () {
// covered in minimum
},
maxLength: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.1.2
if (typeof json !== "string") {
return;
}
if (Utils.ucs2decode(json).length > schema.maxLength) {
report.addError("MAX_LENGTH", [json.length, schema.maxLength], null, schema.description);
}
},
minLength: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.2.2
if (typeof json !== "string") {
return;
}
if (Utils.ucs2decode(json).length < schema.minLength) {
report.addError("MIN_LENGTH", [json.length, schema.minLength], null, schema.description);
}
},
pattern: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.3.2
if (typeof json !== "string") {
return;
}
if (RegExp(schema.pattern).test(json) === false) {
report.addError("PATTERN", [schema.pattern, json], null, schema.description);
}
},
additionalItems: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.1.2
if (!Array.isArray(json)) {
return;
}
// if the value of "additionalItems" is boolean value false and the value of "items" is an array,
// the json is valid if its size is less than, or equal to, the size of "items".
if (schema.additionalItems === false && Array.isArray(schema.items)) {
if (json.length > schema.items.length) {
report.addError("ARRAY_ADDITIONAL_ITEMS", null, null, schema.description);
}
}
},
items: function () { /*report, schema, json*/
// covered in additionalItems
},
maxItems: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.2.2
if (!Array.isArray(json)) {
return;
}
if (json.length > schema.maxItems) {
report.addError("ARRAY_LENGTH_LONG", [json.length, schema.maxItems], null, schema.description);
}
},
minItems: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.3.2
if (!Array.isArray(json)) {
return;
}
if (json.length < schema.minItems) {
report.addError("ARRAY_LENGTH_SHORT", [json.length, schema.minItems], null, schema.description);
}
},
uniqueItems: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.4.2
if (!Array.isArray(json)) {
return;
}
if (schema.uniqueItems === true) {
var matches = [];
if (Utils.isUniqueArray(json, matches) === false) {
report.addError("ARRAY_UNIQUE", matches, null, schema.description);
}
}
},
maxProperties: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.1.2
if (Utils.whatIs(json) !== "object") {
return;
}
var keysCount = Object.keys(json).length;
if (keysCount > schema.maxProperties) {
report.addError("OBJECT_PROPERTIES_MAXIMUM", [keysCount, schema.maxProperties], null, schema.description);
}
},
minProperties: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.2.2
if (Utils.whatIs(json) !== "object") {
return;
}
var keysCount = Object.keys(json).length;
if (keysCount < schema.minProperties) {
report.addError("OBJECT_PROPERTIES_MINIMUM", [keysCount, schema.minProperties], null, schema.description);
}
},
required: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.3.2
if (Utils.whatIs(json) !== "object") {
return;
}
var idx = schema.required.length;
while (idx--) {
var requiredPropertyName = schema.required[idx];
if (json[requiredPropertyName] === undefined) {
report.addError("OBJECT_MISSING_REQUIRED_PROPERTY", [requiredPropertyName], null, schema.description);
}
}
},
additionalProperties: function (report, schema, json) {
// covered in properties and patternProperties
if (schema.properties === undefined && schema.patternProperties === undefined) {
return JsonValidators.properties.call(this, report, schema, json);
}
},
patternProperties: function (report, schema, json) {
// covered in properties
if (schema.properties === undefined) {
return JsonValidators.properties.call(this, report, schema, json);
}
},
properties: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.4.2
if (Utils.whatIs(json) !== "object") {
return;
}
var properties = schema.properties !== undefined ? schema.properties : {};
var patternProperties = schema.patternProperties !== undefined ? schema.patternProperties : {};
if (schema.additionalProperties === false) {
// The property set of the json to validate.
var s = Object.keys(json);
// The property set from "properties".
var p = Object.keys(properties);
// The property set from "patternProperties".
var pp = Object.keys(patternProperties);
// remove from "s" all elements of "p", if any;
s = Utils.difference(s, p);
// for each regex in "pp", remove all elements of "s" which this regex matches.
var idx = pp.length;
while (idx--) {
var regExp = RegExp(pp[idx]),
idx2 = s.length;
while (idx2--) {
if (regExp.test(s[idx2]) === true) {
s.splice(idx2, 1);
}
}
}
// Validation of the json succeeds if, after these two steps, set "s" is empty.
if (s.length > 0) {
// assumeAdditional can be an array of allowed properties
var idx3 = this.options.assumeAdditional.length;
if (idx3) {
while (idx3--) {
var io = s.indexOf(this.options.assumeAdditional[idx3]);
if (io !== -1) {
s.splice(io, 1);
}
}
}
if (s.length > 0) {
report.addError("OBJECT_ADDITIONAL_PROPERTIES", [s], null, schema.description);
}
}
}
},
dependencies: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.4.5.2
if (Utils.whatIs(json) !== "object") {
return;
}
var keys = Object.keys(schema.dependencies),
idx = keys.length;
while (idx--) {
// iterate all dependencies
var dependencyName = keys[idx];
if (json[dependencyName]) {
var dependencyDefinition = schema.dependencies[dependencyName];
if (Utils.whatIs(dependencyDefinition) === "object") {
// if dependency is a schema, validate against this schema
exports.validate.call(this, report, dependencyDefinition, json);
} else { // Array
// if dependency is an array, object needs to have all properties in this array
var idx2 = dependencyDefinition.length;
while (idx2--) {
var requiredPropertyName = dependencyDefinition[idx2];
if (json[requiredPropertyName] === undefined) {
report.addError("OBJECT_DEPENDENCY_KEY", [requiredPropertyName, dependencyName], null, schema.description);
}
}
}
}
}
},
enum: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.1.2
var match = false,
idx = schema.enum.length;
while (idx--) {
if (Utils.areEqual(json, schema.enum[idx])) {
match = true;
break;
}
}
if (match === false) {
report.addError("ENUM_MISMATCH", [json], null, schema.description);
}
},
/*
type: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.2.2
// type is handled before this is called so ignore
},
*/
allOf: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.3.2
var idx = schema.allOf.length;
while (idx--) {
var validateResult = exports.validate.call(this, report, schema.allOf[idx], json);
if (this.options.breakOnFirstError && validateResult === false) {
break;
}
}
},
anyOf: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.4.2
var subReports = [],
passed = false,
idx = schema.anyOf.length;
while (idx-- && passed === false) {
var subReport = new Report(report);
subReports.push(subReport);
passed = exports.validate.call(this, subReport, schema.anyOf[idx], json);
}
if (passed === false) {
report.addError("ANY_OF_MISSING", undefined, subReports, schema.description);
}
},
oneOf: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.5.2
var passes = 0,
subReports = [],
idx = schema.oneOf.length;
while (idx--) {
var subReport = new Report(report, { maxErrors: 1 });
subReports.push(subReport);
if (exports.validate.call(this, subReport, schema.oneOf[idx], json) === true) {
passes++;
}
}
if (passes === 0) {
report.addError("ONE_OF_MISSING", undefined, subReports, schema.description);
} else if (passes > 1) {
report.addError("ONE_OF_MULTIPLE", null, null, schema.description);
}
},
not: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.6.2
var subReport = new Report(report);
if (exports.validate.call(this, subReport, schema.not, json) === true) {
report.addError("NOT_PASSED", null, null, schema.description);
}
},
definitions: function () { /*report, schema, json*/
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.7.2
// nothing to do here
},
format: function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.7.2
var formatValidatorFn = FormatValidators[schema.format];
if (typeof formatValidatorFn === "function") {
if (formatValidatorFn.length === 2) {
// async
report.addAsyncTask(formatValidatorFn, [json], function (result) {
if (result !== true) {
report.addError("INVALID_FORMAT", [schema.format, json], null, schema.description);
}
});
} else {
// sync
if (formatValidatorFn.call(this, json) !== true) {
report.addError("INVALID_FORMAT", [schema.format, json], null, schema.description);
}
}
} else if (this.options.ignoreUnknownFormats !== true) {
report.addError("UNKNOWN_FORMAT", [schema.format], null, schema.description);
}
}
};
var recurseArray = function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.2
var idx = json.length;
// If "items" is an array, this situation, the schema depends on the index:
// if the index is less than, or equal to, the size of "items",
// the child instance must be valid against the corresponding schema in the "items" array;
// otherwise, it must be valid against the schema defined by "additionalItems".
if (Array.isArray(schema.items)) {
while (idx--) {
// equal to doesnt make sense here
if (idx < schema.items.length) {
report.path.push(idx.toString());
exports.validate.call(this, report, schema.items[idx], json[idx]);
report.path.pop();
} else {
// might be boolean, so check that it's an object
if (typeof schema.additionalItems === "object") {
report.path.push(idx.toString());
exports.validate.call(this, report, schema.additionalItems, json[idx]);
report.path.pop();
}
}
}
} else if (typeof schema.items === "object") {
// If items is a schema, then the child instance must be valid against this schema,
// regardless of its index, and regardless of the value of "additionalItems".
while (idx--) {
report.path.push(idx.toString());
exports.validate.call(this, report, schema.items, json[idx]);
report.path.pop();
}
}
};
var recurseObject = function (report, schema, json) {
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.3
// If "additionalProperties" is absent, it is considered present with an empty schema as a value.
// In addition, boolean value true is considered equivalent to an empty schema.
var additionalProperties = schema.additionalProperties;
if (additionalProperties === true || additionalProperties === undefined) {
additionalProperties = {};
}
// p - The property set from "properties".
var p = schema.properties ? Object.keys(schema.properties) : [];
// pp - The property set from "patternProperties". Elements of this set will be called regexes for convenience.
var pp = schema.patternProperties ? Object.keys(schema.patternProperties) : [];
// m - The property name of the child.
var keys = Object.keys(json),
idx = keys.length;
while (idx--) {
var m = keys[idx],
propertyValue = json[m];
// s - The set of schemas for the child instance.
var s = [];
// 1. If set "p" contains value "m", then the corresponding schema in "properties" is added to "s".
if (p.indexOf(m) !== -1) {
s.push(schema.properties[m]);
}
// 2. For each regex in "pp", if it matches "m" successfully, the corresponding schema in "patternProperties" is added to "s".
var idx2 = pp.length;
while (idx2--) {
var regexString = pp[idx2];
if (RegExp(regexString).test(m) === true) {
s.push(schema.patternProperties[regexString]);
}
}
// 3. The schema defined by "additionalProperties" is added to "s" if and only if, at this stage, "s" is empty.
if (s.length === 0 && additionalProperties !== false) {
s.push(additionalProperties);
}
// we are passing tests even without this assert because this is covered by properties check
// if s is empty in this stage, no additionalProperties are allowed
// report.expect(s.length !== 0, 'E001', m);
// Instance property value must pass all schemas from s
idx2 = s.length;
while (idx2--) {
report.path.push(m);
exports.validate.call(this, report, s[idx2], propertyValue);
report.path.pop();
}
}
};
exports.validate = function (report, schema, json) {
report.commonErrorMessage = "JSON_OBJECT_VALIDATION_FAILED";
// check if schema is an object
var to = Utils.whatIs(schema);
if (to !== "object") {
report.addError("SCHEMA_NOT_AN_OBJECT", [to], null, schema.description);
return false;
}
// check if schema is empty, everything is valid against empty schema
var keys = Object.keys(schema);
if (keys.length === 0) {
return true;
}
// this method can be called recursively, so we need to remember our root
var isRoot = false;
if (!report.rootSchema) {
report.rootSchema = schema;
isRoot = true;
}
// follow schema.$ref keys
if (schema.$ref !== undefined) {
// avoid infinite loop with maxRefs
var maxRefs = 99;
while (schema.$ref && maxRefs > 0) {
if (!schema.__$refResolved) {
report.addError("REF_UNRESOLVED", [schema.$ref], null, schema.description);
break;
} else if (schema.__$refResolved === schema) {
break;
} else {
schema = schema.__$refResolved;
keys = Object.keys(schema);
}
maxRefs--;
}
if (maxRefs === 0) {
throw new Error("Circular dependency by $ref references!");
}
}
// type checking first
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.5.2.2
var jsonType = Utils.whatIs(json);
if (schema.type) {
if (typeof schema.type === "string") {
if (jsonType !== schema.type && (jsonType !== "integer" || schema.type !== "number")) {
report.addError("INVALID_TYPE", [schema.type, jsonType], null, schema.description);
if (this.options.breakOnFirstError) {
return false;
}
}
} else {
if (schema.type.indexOf(jsonType) === -1 && (jsonType !== "integer" || schema.type.indexOf("number") === -1)) {
report.addError("INVALID_TYPE", [schema.type, jsonType], null, schema.description);
if (this.options.breakOnFirstError) {
return false;
}
}
}
}
// now iterate all the keys in schema and execute validation methods
var idx = keys.length;
while (idx--) {
if (JsonValidators[keys[idx]]) {
JsonValidators[keys[idx]].call(this, report, schema, json);
if (report.errors.length && this.options.breakOnFirstError) { break; }
}
}
if (report.errors.length === 0 || this.options.breakOnFirstError === false) {
if (jsonType === "array") {
recurseArray.call(this, report, schema, json);
} else if (jsonType === "object") {
recurseObject.call(this, report, schema, json);
}
}
if (typeof this.options.customValidator === "function") {
this.options.customValidator(report, schema, json);
}
// we don't need the root pointer anymore
if (isRoot) {
report.rootSchema = undefined;
}
// return valid just to be able to break at some code points
return report.errors.length === 0;
};
},{"./FormatValidators":3,"./Report":6,"./Utils":10}],5:[function(require,module,exports){
// Number.isFinite polyfill
// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isfinite
if (typeof Number.isFinite !== "function") {
Number.isFinite = function isFinite(value) {
// 1. If Type(number) is not Number, return false.
if (typeof value !== "number") {
return false;
}
// 2. If number is NaN, +∞, or −∞, return false.
if (value !== value || value === Infinity || value === -Infinity) {
return false;
}
// 3. Otherwise, return true.
return true;
};
}
},{}],6:[function(require,module,exports){
(function (process){
"use strict";
var get = _.get;
var Errors = require("./Errors");
var Utils = require("./Utils");
function Report(parentOrOptions, reportOptions) {
this.parentReport = parentOrOptions instanceof Report ?
parentOrOptions :
undefined;
this.options = parentOrOptions instanceof Report ?
parentOrOptions.options :
parentOrOptions || {};
this.reportOptions = reportOptions || {};
this.errors = [];
this.path = [];
this.asyncTasks = [];
}
Report.prototype.isValid = function () {
if (this.asyncTasks.length > 0) {
throw new Error("Async tasks pending, can't answer isValid");
}
return this.errors.length === 0;
};
Report.prototype.addAsyncTask = function (fn, args, asyncTaskResultProcessFn) {
this.asyncTasks.push([fn, args, asyncTaskResultProcessFn]);
};
Report.prototype.processAsyncTasks = function (timeout, callback) {
var validationTimeout = timeout || 2000,
tasksCount = this.asyncTasks.length,
idx = tasksCount,
timedOut = false,
self = this;
function finish() {
process.nextTick(function () {
var valid = self.errors.length === 0,
err = valid ? undefined : self.errors;
callback(err, valid);
});
}
function respond(asyncTaskResultProcessFn) {
return function (asyncTaskResult) {
if (timedOut) { return; }
asyncTaskResultProcessFn(asyncTaskResult);
if (--tasksCount === 0) {
finish();
}
};
}
if (tasksCount === 0 || this.errors.length > 0) {
finish();
return;
}
while (idx--) {
var task = this.asyncTasks[idx];
task[0].apply(null, task[1].concat(respond(task[2])));
}
setTimeout(function () {
if (tasksCount > 0) {
timedOut = true;
self.addError("ASYNC_TIMEOUT", [tasksCount, validationTimeout]);
callback(self.errors, false);
}
}, validationTimeout);
};
Report.prototype.getPath = function (returnPathAsString) {
var path = [];
if (this.parentReport) {
path = path.concat(this.parentReport.path);
}
path = path.concat(this.path);
if (returnPathAsString !== true) {
// Sanitize the path segments (http://tools.ietf.org/html/rfc6901#section-4)
path = "#/" + path.map(function (segment) {
if (Utils.isAbsoluteUri(segment)) {
return "uri(" + segment + ")";
}
return segment.replace(/\~/g, "~0").replace(/\//g, "~1");
}).join("/");
}
return path;
};
Report.prototype.getSchemaId = function () {
if (!this.rootSchema) {
return null;
}
// get the error path as an array
var path = [];
if (this.parentReport) {
path = path.concat(this.parentReport.path);
}
path = path.concat(this.path);
// try to find id in the error path
while (path.length > 0) {
var obj = get(this.rootSchema, path);
if (obj && obj.id) { return obj.id; }
path.pop();
}
// return id of the root
return this.rootSchema.id;
};
Report.prototype.hasError = function (errorCode, params) {
var idx = this.errors.length;
while (idx--) {
if (this.errors[idx].code === errorCode) {
// assume match
var match = true;
// check the params too
var idx2 = this.errors[idx].params.length;
while (idx2--) {
if (this.errors[idx].params[idx2] !== params[idx2]) {
match = false;
}
}
// if match, return true
if (match) { return match; }
}
}
return false;
};
Report.prototype.addError = function (errorCode, params, subReports, schemaDescription) {
if (!errorCode) { throw new Error("No errorCode passed into addError()"); }
this.addCustomError(errorCode, Errors[errorCode], params, subReports, schemaDescription);
};
Report.prototype.addCustomError = function (errorCode, errorMessage, params, subReports, schemaDescription) {
if (this.errors.length >= this.reportOptions.maxErrors) {
return;
}
if (!errorMessage) { throw new Error("No errorMessage known for code " + errorCode); }
params = params || [];
var idx = params.length;
while (idx--) {
var whatIs = Utils.whatIs(params[idx]);
var param = (whatIs === "object" || whatIs === "null") ? JSON.stringify(params[idx]) : params[idx];
errorMessage = errorMessage.replace("{" + idx + "}", param);
}
var err = {
code: errorCode,
params: params,
message: errorMessage,
path: this.getPath(this.options.reportPathAsArray),
schemaId: this.getSchemaId()
};
if (schemaDescription) {
err.description = schemaDescription;
}
if (subReports != null) {
if (!Array.isArray(subReports)) {
subReports = [subReports];
}
err.inner = [];
idx = subReports.length;
while (idx--) {
var subReport = subReports[idx],
idx2 = subReport.errors.length;
while (idx2--) {
err.inner.push(subReport.errors[idx2]);
}
}
if (err.inner.length === 0) {
err.inner = undefined;
}
}
this.errors.push(err);
};
module.exports = Report;
}).call(this,require('_process'))
},{"./Errors":2,"./Utils":10,"_process":1}],7:[function(require,module,exports){
"use strict";
var isequal = _.isEqual;
var Report = require("./Report");
var SchemaCompilation = require("./SchemaCompilation");
var SchemaValidation = require("./SchemaValidation");
var Utils = require("./Utils");
function decodeJSONPointer(str) {
// http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07#section-3
return decodeURIComponent(str).replace(/~[0-1]/g, function (x) {
return x === "~1" ? "/" : "~";
});
}
function getRemotePath(uri) {
var io = uri.indexOf("#");
return io === -1 ? uri : uri.slice(0, io);
}
function getQueryPath(uri) {
var io = uri.indexOf("#");
var res = io === -1 ? undefined : uri.slice(io + 1);
// WARN: do not slice slash, #/ means take root and go down from it
// if (res && res[0] === "/") { res = res.slice(1); }
return res;
}
function findId(schema, id) {
// process only arrays and objects
if (typeof schema !== "object" || schema === null) {
return;
}
// no id means root so return itself
if (!id) {
return schema;
}
if (schema.id) {
if (schema.id === id || schema.id[0] === "#" && schema.id.substring(1) === id) {
return schema;
}
}
var idx, result;
if (Array.isArray(schema)) {
idx = schema.length;
while (idx--) {
result = findId(schema[idx], id);
if (result) { return result; }
}
} else {
var keys = Object.keys(schema);
idx = keys.length;
while (idx--) {
var k = keys[idx];
if (k.indexOf("__$") === 0) {
continue;
}
result = findId(schema[k], id);
if (result) { return result; }
}
}
}
exports.cacheSchemaByUri = function (uri, schema) {
var remotePath = getRemotePath(uri);
if (remotePath) {
this.cache[remotePath] = schema;
}
};
exports.removeFromCacheByUri = function (uri) {
var remotePath = getRemotePath(uri);
if (remotePath) {
delete this.cache[remotePath];
}
};
exports.checkCacheForUri = function (uri) {
var remotePath = getRemotePath(uri);
return remotePath ? this.cache[remotePath] != null : false;
};
exports.getSchema = function (report, schema) {
if (typeof schema === "object") {
schema = exports.getSchemaByReference.call(this, report, schema);
}
if (typeof schema === "string") {
schema = exports.getSchemaByUri.call(this, report, schema);
}
return schema;
};
exports.getSchemaByReference = function (report, key) {
var i = this.referenceCache.length;
while (i--) {
if (isequal(this.referenceCache[i][0], key)) {
return this.referenceCache[i][1];
}
}
// not found
var schema = Utils.cloneDeep(key);
this.referenceCache.push([key, schema]);
return schema;
};
exports.getSchemaByUri = function (report, uri, root) {
var remotePath = getRemotePath(uri),
queryPath = getQueryPath(uri),
result = remotePath ? this.cache[remotePath] : root;
if (result && remotePath) {
// we need to avoid compiling schemas in a recursive loop
var compileRemote = result !== root;
// now we need to compile and validate resolved schema (in case it's not already)
if (compileRemote) {
report.path.push(remotePath);
var remoteReport = new Report(report);
if (SchemaCompilation.compileSchema.call(this, remoteReport, result)) {
SchemaValidation.validateSchema.call(this, remoteReport, result);
}
var remoteReportIsValid = remoteReport.isValid();
if (!remoteReportIsValid) {
report.addError("REMOTE_NOT_VALID", [uri], remoteReport);
}
report.path.pop();
if (!remoteReportIsValid) {
return undefined;
}
}
}
if (result && queryPath) {
var parts = queryPath.split("/");
for (var idx = 0, lim = parts.length; result && idx < lim; idx++) {
var key = decodeJSONPointer(parts[idx]);
if (idx === 0) { // it's an id
result = findId(result, key);
} else { // it's a path behind id
result = result[key];
}
}
}
return result;
};
exports.getRemotePath = getRemotePath;
},{"./Report":6,"./SchemaCompilation":8,"./SchemaValidation":9,"./Utils":10}],8:[function(require,module,exports){
"use strict";
var Report = require("./Report");
var SchemaCache = require("./SchemaCache");
var Utils = require("./Utils");
function mergeReference(scope, ref) {
if (Utils.isAbsoluteUri(ref)) {
return ref;
}
var joinedScope = scope.join(""),
isScopeAbsolute = Utils.isAbsoluteUri(joinedScope),
isScopeRelative = Utils.isRelativeUri(joinedScope),
isRefRelative = Utils.isRelativeUri(ref),
toRemove;
if (isScopeAbsolute && isRefRelative) {
toRemove = joinedScope.match(/\/[^\/]*$/);
if (toRemove) {
joinedScope = joinedScope.slice(0, toRemove.index + 1);
}
} else if (isScopeRelative && isRefRelative) {
joinedScope = "";
} else {
toRemove = joinedScope.match(/[^#/]+$/);
if (toRemove) {
joinedScope = joinedScope.slice(0, toRemove.index);
}
}
var res = joinedScope + ref;
res = res.replace(/##/, "#");
return res;
}
function collectReferences(obj, results, scope, path) {
results = results || [];
scope = scope || [];
path = path || [];
if (typeof obj !== "object" || obj === null) {
return results;
}
if (typeof obj.id === "string") {
scope.push(obj.id);
}
if (typeof obj.$ref === "string" && typeof obj.__$refResolved === "undefined") {
results.push({
ref: mergeReference(scope, obj.$ref),
key: "$ref",
obj: obj,
path: path.slice(0)
});
}
if (typeof obj.$schema === "string" && typeof obj.__$schemaResolved === "undefined") {
results.push({
ref: mergeReference(scope, obj.$schema),
key: "$schema",
obj: obj,
path: path.slice(0)
});
}
var idx;
if (Array.isArray(obj)) {
idx = obj.length;
while (idx--) {
path.push(idx.toString());
collectReferences(obj[idx], results, scope, path);
path.pop();
}
} else {
var keys = Object.keys(obj);
idx = keys.length;
while (idx--) {
// do not recurse through resolved references and other z-schema props
if (keys[idx].indexOf("__$") === 0) { continue; }
path.push(keys[idx]);
collectReferences(obj[