/Website3Hydra/Scripts/tv4/source/validate.js
JavaScript | 397 lines | 381 code | 13 blank | 3 comment | 138 complexity | adf54dd40a76c3e95bfb0a1b73a166f9 MD5 | raw file
- var ValidatorContext = function ValidatorContext(parent, collectMultiple, errorReporter, checkRecursive, trackUnknownProperties) {
- this.missing = [];
- this.missingMap = {};
- this.formatValidators = parent ? Object.create(parent.formatValidators) : {};
- this.schemas = parent ? Object.create(parent.schemas) : {};
- this.collectMultiple = collectMultiple;
- this.errors = [];
- this.handleError = collectMultiple ? this.collectError : this.returnError;
- if (checkRecursive) {
- this.checkRecursive = true;
- this.scanned = [];
- this.scannedFrozen = [];
- this.scannedFrozenSchemas = [];
- this.scannedFrozenValidationErrors = [];
- this.validatedSchemasKey = 'tv4_validation_id';
- this.validationErrorsKey = 'tv4_validation_errors_id';
- }
- if (trackUnknownProperties) {
- this.trackUnknownProperties = true;
- this.knownPropertyPaths = {};
- this.unknownPropertyPaths = {};
- }
- this.errorReporter = errorReporter || defaultErrorReporter('en');
- if (typeof this.errorReporter === 'string') {
- throw new Error('debug');
- }
- this.definedKeywords = {};
- if (parent) {
- for (var key in parent.definedKeywords) {
- this.definedKeywords[key] = parent.definedKeywords[key].slice(0);
- }
- }
- };
- ValidatorContext.prototype.defineKeyword = function (keyword, keywordFunction) {
- this.definedKeywords[keyword] = this.definedKeywords[keyword] || [];
- this.definedKeywords[keyword].push(keywordFunction);
- };
- ValidatorContext.prototype.createError = function (code, messageParams, dataPath, schemaPath, subErrors, data, schema) {
- var error = new ValidationError(code, messageParams, dataPath, schemaPath, subErrors);
- error.message = this.errorReporter(error, data, schema);
- return error;
- };
- ValidatorContext.prototype.returnError = function (error) {
- return error;
- };
- ValidatorContext.prototype.collectError = function (error) {
- if (error) {
- this.errors.push(error);
- }
- return null;
- };
- ValidatorContext.prototype.prefixErrors = function (startIndex, dataPath, schemaPath) {
- for (var i = startIndex; i < this.errors.length; i++) {
- this.errors[i] = this.errors[i].prefixWith(dataPath, schemaPath);
- }
- return this;
- };
- ValidatorContext.prototype.banUnknownProperties = function (data, schema) {
- for (var unknownPath in this.unknownPropertyPaths) {
- var error = this.createError(ErrorCodes.UNKNOWN_PROPERTY, {path: unknownPath}, unknownPath, "", null, data, schema);
- var result = this.handleError(error);
- if (result) {
- return result;
- }
- }
- return null;
- };
- ValidatorContext.prototype.addFormat = function (format, validator) {
- if (typeof format === 'object') {
- for (var key in format) {
- this.addFormat(key, format[key]);
- }
- return this;
- }
- this.formatValidators[format] = validator;
- };
- ValidatorContext.prototype.resolveRefs = function (schema, urlHistory) {
- if (schema['$ref'] !== undefined) {
- urlHistory = urlHistory || {};
- if (urlHistory[schema['$ref']]) {
- return this.createError(ErrorCodes.CIRCULAR_REFERENCE, {urls: Object.keys(urlHistory).join(', ')}, '', '', null, undefined, schema);
- }
- urlHistory[schema['$ref']] = true;
- schema = this.getSchema(schema['$ref'], urlHistory);
- }
- return schema;
- };
- ValidatorContext.prototype.getSchema = function (url, urlHistory) {
- var schema;
- if (this.schemas[url] !== undefined) {
- schema = this.schemas[url];
- return this.resolveRefs(schema, urlHistory);
- }
- var baseUrl = url;
- var fragment = "";
- if (url.indexOf('#') !== -1) {
- fragment = url.substring(url.indexOf("#") + 1);
- baseUrl = url.substring(0, url.indexOf("#"));
- }
- if (typeof this.schemas[baseUrl] === 'object') {
- schema = this.schemas[baseUrl];
- var pointerPath = decodeURIComponent(fragment);
- if (pointerPath === "") {
- return this.resolveRefs(schema, urlHistory);
- } else if (pointerPath.charAt(0) !== "/") {
- return undefined;
- }
- var parts = pointerPath.split("/").slice(1);
- for (var i = 0; i < parts.length; i++) {
- var component = parts[i].replace(/~1/g, "/").replace(/~0/g, "~");
- if (schema[component] === undefined) {
- schema = undefined;
- break;
- }
- schema = schema[component];
- }
- if (schema !== undefined) {
- return this.resolveRefs(schema, urlHistory);
- }
- }
- if (this.missing[baseUrl] === undefined) {
- this.missing.push(baseUrl);
- this.missing[baseUrl] = baseUrl;
- this.missingMap[baseUrl] = baseUrl;
- }
- };
- ValidatorContext.prototype.searchSchemas = function (schema, url) {
- if (Array.isArray(schema)) {
- for (var i = 0; i < schema.length; i++) {
- this.searchSchemas(schema[i], url);
- }
- } else if (schema && typeof schema === "object") {
- if (typeof schema.id === "string") {
- if (isTrustedUrl(url, schema.id)) {
- if (this.schemas[schema.id] === undefined) {
- this.schemas[schema.id] = schema;
- }
- }
- }
- for (var key in schema) {
- if (key !== "enum") {
- if (typeof schema[key] === "object") {
- this.searchSchemas(schema[key], url);
- } else if (key === "$ref") {
- var uri = getDocumentUri(schema[key]);
- if (uri && this.schemas[uri] === undefined && this.missingMap[uri] === undefined) {
- this.missingMap[uri] = uri;
- }
- }
- }
- }
- }
- };
- ValidatorContext.prototype.addSchema = function (url, schema) {
- //overload
- if (typeof url !== 'string' || typeof schema === 'undefined') {
- if (typeof url === 'object' && typeof url.id === 'string') {
- schema = url;
- url = schema.id;
- }
- else {
- return;
- }
- }
- if (url === getDocumentUri(url) + "#") {
- // Remove empty fragment
- url = getDocumentUri(url);
- }
- this.schemas[url] = schema;
- delete this.missingMap[url];
- normSchema(schema, url);
- this.searchSchemas(schema, url);
- };
- ValidatorContext.prototype.getSchemaMap = function () {
- var map = {};
- for (var key in this.schemas) {
- map[key] = this.schemas[key];
- }
- return map;
- };
- ValidatorContext.prototype.getSchemaUris = function (filterRegExp) {
- var list = [];
- for (var key in this.schemas) {
- if (!filterRegExp || filterRegExp.test(key)) {
- list.push(key);
- }
- }
- return list;
- };
- ValidatorContext.prototype.getMissingUris = function (filterRegExp) {
- var list = [];
- for (var key in this.missingMap) {
- if (!filterRegExp || filterRegExp.test(key)) {
- list.push(key);
- }
- }
- return list;
- };
- ValidatorContext.prototype.dropSchemas = function () {
- this.schemas = {};
- this.reset();
- };
- ValidatorContext.prototype.reset = function () {
- this.missing = [];
- this.missingMap = {};
- this.errors = [];
- };
- ValidatorContext.prototype.validateAll = function (data, schema, dataPathParts, schemaPathParts, dataPointerPath) {
- var topLevel;
- schema = this.resolveRefs(schema);
- if (!schema) {
- return null;
- } else if (schema instanceof ValidationError) {
- this.errors.push(schema);
- return schema;
- }
- var startErrorCount = this.errors.length;
- var frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;
- if (this.checkRecursive && data && typeof data === 'object') {
- topLevel = !this.scanned.length;
- if (data[this.validatedSchemasKey]) {
- var schemaIndex = data[this.validatedSchemasKey].indexOf(schema);
- if (schemaIndex !== -1) {
- this.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);
- return null;
- }
- }
- if (Object.isFrozen(data)) {
- frozenIndex = this.scannedFrozen.indexOf(data);
- if (frozenIndex !== -1) {
- var frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);
- if (frozenSchemaIndex !== -1) {
- this.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);
- return null;
- }
- }
- }
- this.scanned.push(data);
- if (Object.isFrozen(data)) {
- if (frozenIndex === -1) {
- frozenIndex = this.scannedFrozen.length;
- this.scannedFrozen.push(data);
- this.scannedFrozenSchemas.push([]);
- }
- scannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;
- this.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;
- this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];
- } else {
- if (!data[this.validatedSchemasKey]) {
- try {
- Object.defineProperty(data, this.validatedSchemasKey, {
- value: [],
- configurable: true
- });
- Object.defineProperty(data, this.validationErrorsKey, {
- value: [],
- configurable: true
- });
- } catch (e) {
- //IE 7/8 workaround
- data[this.validatedSchemasKey] = [];
- data[this.validationErrorsKey] = [];
- }
- }
- scannedSchemasIndex = data[this.validatedSchemasKey].length;
- data[this.validatedSchemasKey][scannedSchemasIndex] = schema;
- data[this.validationErrorsKey][scannedSchemasIndex] = [];
- }
- }
- var errorCount = this.errors.length;
- var error = this.validateBasic(data, schema, dataPointerPath)
- || this.validateNumeric(data, schema, dataPointerPath)
- || this.validateString(data, schema, dataPointerPath)
- || this.validateArray(data, schema, dataPointerPath)
- || this.validateObject(data, schema, dataPointerPath)
- || this.validateCombinations(data, schema, dataPointerPath)
- || this.validateHypermedia(data, schema, dataPointerPath)
- || this.validateFormat(data, schema, dataPointerPath)
- || this.validateDefinedKeywords(data, schema, dataPointerPath)
- || null;
- if (topLevel) {
- while (this.scanned.length) {
- var item = this.scanned.pop();
- delete item[this.validatedSchemasKey];
- }
- this.scannedFrozen = [];
- this.scannedFrozenSchemas = [];
- }
- if (error || errorCount !== this.errors.length) {
- while ((dataPathParts && dataPathParts.length) || (schemaPathParts && schemaPathParts.length)) {
- var dataPart = (dataPathParts && dataPathParts.length) ? "" + dataPathParts.pop() : null;
- var schemaPart = (schemaPathParts && schemaPathParts.length) ? "" + schemaPathParts.pop() : null;
- if (error) {
- error = error.prefixWith(dataPart, schemaPart);
- }
- this.prefixErrors(errorCount, dataPart, schemaPart);
- }
- }
- if (scannedFrozenSchemaIndex !== null) {
- this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);
- } else if (scannedSchemasIndex !== null) {
- data[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);
- }
- return this.handleError(error);
- };
- ValidatorContext.prototype.validateFormat = function (data, schema) {
- if (typeof schema.format !== 'string' || !this.formatValidators[schema.format]) {
- return null;
- }
- var errorMessage = this.formatValidators[schema.format].call(null, data, schema);
- if (typeof errorMessage === 'string' || typeof errorMessage === 'number') {
- return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage}, '', '/format', null, data, schema);
- } else if (errorMessage && typeof errorMessage === 'object') {
- return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage.message || "?"}, errorMessage.dataPath || '', errorMessage.schemaPath || "/format", null, data, schema);
- }
- return null;
- };
- ValidatorContext.prototype.validateDefinedKeywords = function (data, schema, dataPointerPath) {
- for (var key in this.definedKeywords) {
- if (typeof schema[key] === 'undefined') {
- continue;
- }
- var validationFunctions = this.definedKeywords[key];
- for (var i = 0; i < validationFunctions.length; i++) {
- var func = validationFunctions[i];
- var result = func(data, schema[key], schema, dataPointerPath);
- if (typeof result === 'string' || typeof result === 'number') {
- return this.createError(ErrorCodes.KEYWORD_CUSTOM, {key: key, message: result}, '', '', null, data, schema).prefixWith(null, key);
- } else if (result && typeof result === 'object') {
- var code = result.code;
- if (typeof code === 'string') {
- if (!ErrorCodes[code]) {
- throw new Error('Undefined error code (use defineError): ' + code);
- }
- code = ErrorCodes[code];
- } else if (typeof code !== 'number') {
- code = ErrorCodes.KEYWORD_CUSTOM;
- }
- var messageParams = (typeof result.message === 'object') ? result.message : {key: key, message: result.message || "?"};
- var schemaPath = result.schemaPath || ("/" + key.replace(/~/g, '~0').replace(/\//g, '~1'));
- return this.createError(code, messageParams, result.dataPath || null, schemaPath, null, data, schema);
- }
- }
- }
- return null;
- };
- function recursiveCompare(A, B) {
- if (A === B) {
- return true;
- }
- if (A && B && typeof A === "object" && typeof B === "object") {
- if (Array.isArray(A) !== Array.isArray(B)) {
- return false;
- } else if (Array.isArray(A)) {
- if (A.length !== B.length) {
- return false;
- }
- for (var i = 0; i < A.length; i++) {
- if (!recursiveCompare(A[i], B[i])) {
- return false;
- }
- }
- } else {
- var key;
- for (key in A) {
- if (B[key] === undefined && A[key] !== undefined) {
- return false;
- }
- }
- for (key in B) {
- if (A[key] === undefined && B[key] !== undefined) {
- return false;
- }
- }
- for (key in A) {
- if (!recursiveCompare(A[key], B[key])) {
- return false;
- }
- }
- }
- return true;
- }
- return false;
- }