'use strict'; var urilib = require('url'); var attribute = require('./attribute'); var helpers = require('./helpers'); var ValidatorResult = helpers.ValidatorResult; var SchemaError = helpers.SchemaError; var SchemaContext = helpers.SchemaContext; /** * Creates a new Validator object * @name Validator * @constructor */ var Validator = function Validator () { // Allow a validator instance to override global custom formats or to have their // own custom formats. this.customFormats = Object.create(Validator.prototype.customFormats); this.schemas = {}; this.unresolvedRefs = []; // Use Object.create to make this extensible without Validator instances stepping on each other's toes. this.types = Object.create(types); this.attributes = Object.create(attribute.validators); }; // Allow formats to be registered globally. Validator.prototype.customFormats = {}; // Hint at the presence of a property Validator.prototype.schemas = null; Validator.prototype.types = null; Validator.prototype.attributes = null; Validator.prototype.unresolvedRefs = null; /** * Adds a schema with a certain urn to the Validator instance. * @param schema * @param urn * @return {Object} */ Validator.prototype.addSchema = function addSchema (schema, uri) { if (!schema) { return null; } var ourUri = uri || schema.id; this.addSubSchema(ourUri, schema); if (ourUri) { this.schemas[ourUri] = schema; } return this.schemas[ourUri]; }; Validator.prototype.addSubSchema = function addSubSchema(baseuri, schema) { if(!schema || typeof schema!='object') return; // Mark all referenced schemas so we can tell later which schemas are referred to, but never defined if(schema.$ref){ var resolvedUri = urilib.resolve(baseuri, schema.$ref); // Only mark unknown schemas as unresolved if (this.schemas[resolvedUri] === undefined) { this.schemas[resolvedUri] = null; this.unresolvedRefs.push(resolvedUri); } return; } var ourUri = schema.id && urilib.resolve(baseuri, schema.id); var ourBase = ourUri || baseuri; if (ourUri) { if(this.schemas[ourUri]){ if(!helpers.deepCompareStrict(this.schemas[ourUri], schema)){ throw new Error('Schema <'+schema+'> already exists with different definition'); } return this.schemas[ourUri]; } this.schemas[ourUri] = schema; var documentUri = ourUri.replace(/^([^#]*)#$/, '$1'); this.schemas[documentUri] = schema; } this.addSubSchemaArray(ourBase, ((schema.items instanceof Array)?schema.items:[schema.items])); this.addSubSchemaArray(ourBase, ((schema.extends instanceof Array)?schema.extends:[schema.extends])); this.addSubSchema(ourBase, schema.additionalItems); this.addSubSchemaObject(ourBase, schema.properties); this.addSubSchema(ourBase, schema.additionalProperties); this.addSubSchemaObject(ourBase, schema.definitions); this.addSubSchemaObject(ourBase, schema.patternProperties); this.addSubSchemaObject(ourBase, schema.dependencies); this.addSubSchemaArray(ourBase, schema.disallow); this.addSubSchemaArray(ourBase, schema.allOf); this.addSubSchemaArray(ourBase, schema.anyOf); this.addSubSchemaArray(ourBase, schema.oneOf); this.addSubSchema(ourBase, schema.not); return this.schemas[ourUri]; }; Validator.prototype.addSubSchemaArray = function addSubSchemaArray(baseuri, schemas) { if(!(schemas instanceof Array)) return; for(var i=0; i", schema); } var subschema = helpers.objectGetPath(ctx.schemas[document], fragment.substr(1)); if(subschema===undefined){ throw new SchemaError("no such schema " + fragment + " located in <" + document + ">", schema); } return {subschema: subschema, switchSchema: switchSchema}; }; /** * Tests whether the instance if of a certain type. * @private * @param instance * @param schema * @param options * @param ctx * @param type * @return {boolean} */ Validator.prototype.testType = function validateType (instance, schema, options, ctx, type) { if (typeof this.types[type] == 'function') { return this.types[type].call(this, instance); } if (type && typeof type == 'object') { var res = this.validateSchema(instance, type, options, ctx); return res === undefined || !(res && res.errors.length); } // Undefined or properties not on the list are acceptable, same as not being defined return true; }; var types = Validator.prototype.types = {}; types.string = function testString (instance) { return typeof instance == 'string'; }; types.number = function testNumber (instance) { // isFinite returns false for NaN, Infinity, and -Infinity return typeof instance == 'number' && isFinite(instance); }; types.integer = function testInteger (instance) { return (typeof instance == 'number') && instance % 1 === 0; }; types.boolean = function testBoolean (instance) { return typeof instance == 'boolean'; }; types.array = function testArray (instance) { return Array.isArray(instance); }; types['null'] = function testNull (instance) { return instance === null; }; types.date = function testDate (instance) { return instance instanceof Date; }; types.any = function testAny (instance) { return true; }; types.object = function testObject (instance) { // TODO: fix this - see #15 return instance && (typeof instance) === 'object' && !(instance instanceof Array) && !(instance instanceof Date); }; module.exports = Validator;