/**
* @flow
*/
/**
* @private
*/
var Compiler = require('./Compiler');
var logger = require('./logger');
var patterns = require('./patterns');
/**
* Get a schema definition based on a possible reference
*
* @private
*
* @param {Mixed} ref
* @param {Object} schemas
*
* @return {Mixed}
*/
function getSchemaByRef(ref, schemas) {
var schema = ref;
while (typeof schema === 'string' && schemas[schema]) {
schema = schemas[schema];
}
return schema;
}
/**
* Dereferences a schema definition
*
* @private
*
* @param {Mixed} schema
* @param {Object} schemas [description]
*
* @return {Mixed}
*/
function dereferenceSchema(schema, schemas) {
var schemaData = getSchemaByRef(schema, schemas);
var dereferenced;
if (Array.isArray(schemaData)) {
dereferenced = schemaData.map(function(item) {
return dereferenceSchema(item, schemas);
});
} else if (isPlainObject(schemaData)) {
dereferenced = {};
Object.keys(schemaData).forEach(function(key) {
dereferenced[key] = dereferenceSchema(schemaData[key], schemas);
});
} else {
dereferenced = schemaData;
}
return dereferenced;
}
/**
* Determines if the input data is a plain object
*
* @private
*
* @param {Mixed} data
*
* @return {Boolean}
*/
function isPlainObject(data) {
return (
data != null &&
typeof data === 'object' &&
!Array.isArray(data) &&
data.constructor === Object
);
}
/**
* The main validator class
*
* @constructor
*/
function JSONDValidator() {
this.schemas = {};
this.compiled = {};
}
JSONDValidator.prototype = {
/**
* Adds a schema definition for references lookup
*
* @example
* var validator = new JSONDValidator();
* validator.addSchema('http://foo.com/id.json', 'integer')
* // With lazy mode, schema compilation is done on the first use
* validator.addSchema('http://foo.com/id.json', 'integer', true)
*
* @param {String} schemaID Schema identifier
* @param {Mixed} schema An arbitrary JSOND text
*/
addSchema: function(schemaID, schema) {
_DEBUG_ && logger('addSchema', schemaID, schema);
if (
schemaID === patterns.boolean ||
schemaID === patterns.integer ||
schemaID === patterns.number ||
schemaID === patterns.string
) {
throw new Error('Schema IDs should not match any JSOND types');
}
this.schemas[schemaID] = schema;
return this;
},
/**
* Returns the dereferenced schema for the given ID.
* The dereferenced schema contains the resolved references based on
* the interal schema map.
*
* @param {String} schemaID A JSOND schema identifier
*
* @return {Object}
*/
getDereferencedSchema: function(schemaID) {
return dereferenceSchema(this.schemas[schemaID], this.schemas);
},
/**
* Validates a given data object based on a schema definition or identifier.
* Returns an object with the following properties:
*
* - `valid` - boolean result of the validation
* - `errors` - list of errors detected at validation
*
* @example
* var validator = new JSONDValidator();
* validator.addSchema('http://foo.com/id.json', 'integer');
* var result = validator.validate(id, 'http://foo.com/id.json');
*
* @param {Mixed} data An arbitrary data object
* @param {String} schemaID Schema identifier
*
* @return {Object} { valid: Boolean, errors: Array<Object> }
*/
validate: function(data, schemaID) {
_DEBUG_ && logger('validate', data, schemaID);
if (!this.schemas[schemaID]) {
return {
valid: false,
errors: [
{
code: 'NO_SCHEMA',
path: ['$'],
},
],
};
}
if (!this.compiled[schemaID]) {
var dereferencedSchema = dereferenceSchema(
this.schemas[schemaID],
this.schemas
);
this.compiled[schemaID] = Compiler.compile(dereferencedSchema);
}
var errors = this.compiled[schemaID](data);
_DEBUG_ && logger('validate.errors', errors);
return {
valid: errors.length === 0,
errors: errors,
};
},
};
module.exports = JSONDValidator;