293 lines
8.9 KiB
JavaScript
Executable File
293 lines
8.9 KiB
JavaScript
Executable File
"use strict";
|
|
/**
|
|
* @fileOverview Enables a schema and validation feature set to your document or other object.
|
|
* @module documents/schema
|
|
* @requires base
|
|
* @requires jjv
|
|
* @require lodash
|
|
*/
|
|
var sys = require( "lodash" );
|
|
var Validator = require( "jjv" );
|
|
var Base = require( "../base" );
|
|
/**
|
|
* The validator mixin provides access to the features of the JSON validation system
|
|
* @exports documents/schema
|
|
* @mixin
|
|
*/
|
|
var Schema = Base.compose( [Base], /** @lends documents/schema# */{
|
|
constructor : function () {
|
|
/**
|
|
* The schema that defines the validation rules. This should probably be defined at the prototype for each
|
|
* object or model classification. It can be an anonymous schema defined right here, or this can be
|
|
* registered schema names to use, or just a single name
|
|
*
|
|
* @type {object}
|
|
* @memberOf documents/schema#
|
|
* @name schema
|
|
*/
|
|
|
|
/**
|
|
* If you want to register multiple schemas, use this property instead
|
|
*
|
|
* @type {object}
|
|
* @memberOf documents/schema#
|
|
* @name schemas
|
|
*/
|
|
|
|
/**
|
|
* The validation environment
|
|
* @private
|
|
* @type {jjv}
|
|
*/
|
|
var env = new Validator();
|
|
|
|
/**
|
|
* The default name of the scheman when you use anonymous schemas. You can define this at the prototype for classified
|
|
* schemas. The can also
|
|
*
|
|
* @type {string|function():{string}}
|
|
* @memberOf documents/schema#
|
|
* @name _defaultSchemaName
|
|
*/
|
|
this._defaultSchemaName = sys.result( this, "_defaultSchemaName" ) || sys.uniqueId( "schema" );
|
|
|
|
/**
|
|
* The options to pass to the validator when it runs
|
|
* @type {object|function():{object}}
|
|
* @name validationOptions
|
|
* @memberOf documents/schema#
|
|
*/
|
|
this.validationOptions = sys.defaults( {}, sys.result( this, 'validationOptions' ), {checkRequired : true} );
|
|
|
|
/**
|
|
* Validate an object against the schema
|
|
* @returns {object?}
|
|
* @method
|
|
* @name validate
|
|
* @memberOf documents/schema#
|
|
* @param {object=} record The record to validate
|
|
* @param {string|object=} schemaName The name of a previously registered schema
|
|
* @param {object=} options Options to pass to the validator
|
|
* @example
|
|
* // This supports these signatures:
|
|
*
|
|
* instance.validate(record, schemaName, options);
|
|
*
|
|
*
|
|
* instance.validate(); // this, this._defaultSchemaName, this.validationOptions
|
|
* instance.validate(record); // record, this._defaultSchemaName, this.validationOptions
|
|
* instance.validate(schemaName); //this, schemaName, this.validationOptions
|
|
* instance.validate(record, schemaName); //record, schemaName, this.validationOptions
|
|
* instance.validate(schemaName, options); //this, schemaName, this.validationOptions
|
|
*/
|
|
this.validate = function ( record, schemaName, options ) {
|
|
if ( arguments.length === 0 ) {
|
|
record = this;
|
|
schemaName = this._defaultSchemaName;
|
|
options = this.validationOptions;
|
|
} else {
|
|
if ( sys.isString( record ) ) {
|
|
schemaName = record;
|
|
record = this;
|
|
}
|
|
if ( sys.isEmpty( options ) ) {
|
|
options = this.validationOptions;
|
|
}
|
|
}
|
|
|
|
return env.validate( schemaName, record, options );
|
|
};
|
|
|
|
/**
|
|
* Initialize the schema collection by registering the with the handler. You can call this at any time and as often as you like. It will be called once
|
|
* by the constructor on any instance schemas
|
|
* @method
|
|
* @name registerSchemas
|
|
* @memberOf documents/schema#
|
|
* @param {hash} schemas A hash of schemas where the key is the name of the schema
|
|
*/
|
|
this.registerSchemas = function ( schemas ) {
|
|
var schema = sys.result( this, "schema" );
|
|
var schemas = schemas || sys.result( this, "schemas" );
|
|
if ( !sys.isEmpty( schema ) ) {
|
|
env.addSchema( this._defaultSchemaName, schema );
|
|
}
|
|
if ( !sys.isEmpty( schemas ) ) {
|
|
sys.each( schemas, function ( val, key ) {
|
|
env.addSchema( val, key );
|
|
} );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Extracts only the elements of the object that are defined in the schema
|
|
* @memberOf documents/schema#
|
|
* @name extract
|
|
* @param {object=} record The record to extract from
|
|
* @param {string=} schema The name of the schema to attach
|
|
* @method
|
|
*/
|
|
this.extract = function ( record, schema ) {
|
|
if ( arguments.length === 0 ) {
|
|
record = this;
|
|
schema = this._defaultSchemaName;
|
|
}
|
|
if ( sys.isString( record ) ) {
|
|
schema = record;
|
|
record = this;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a type to be used in your schemas to define new validators
|
|
* @memberOf documents/schema#
|
|
* @name addType
|
|
* @method
|
|
* @param {string} name The name of the type
|
|
* @param {function(object)} operation What to do with the type.
|
|
* @param {object} operation.value The value to validation
|
|
* @returns {boolean}
|
|
*/
|
|
this.addType = env.addType;
|
|
|
|
/**
|
|
* It is also possible to add support for additional string formats through the addFormat function.
|
|
* @memberOf documents/schema#
|
|
* @name addFormat
|
|
* @method
|
|
* @param {string} name The name of the formatter
|
|
* @param {function(object)} formatter How to format it
|
|
* @param {object} formatter.value The value to format
|
|
* @returns {boolean}
|
|
*/
|
|
this.addFormat = env.addFormat;
|
|
|
|
/**
|
|
* It is possible to add support for custom checks (i.e., minItems, maxItems, minLength, maxLength, etc.) through the addCheck function
|
|
* @memberOf documents/schema#
|
|
* @name addCheck
|
|
* @method
|
|
* @param {string} name The name of the check
|
|
* @param {function(...object)} formatter Perform the check
|
|
* @param {object} formatter.value The value to check followed by any parameters from the schema
|
|
* @returns {boolean}
|
|
*/
|
|
this.addCheck = env.addCheck;
|
|
|
|
/**
|
|
* Custom coercion rules
|
|
*
|
|
* @memberOf documents/schema#
|
|
* @name addTypeCoercion
|
|
* @method
|
|
* @param {string} name The name of the coercion
|
|
* @param {function(object)} coercer Perform the coercion
|
|
* @param {object} coercer.value The value to coerce
|
|
* @returns {boolean}
|
|
*/
|
|
this.addTypeCoercion = env.addTypeCoercion;
|
|
|
|
/**
|
|
* Get a registered schema by name
|
|
* @param {string=} schemaName
|
|
* @returns {object?}
|
|
* @memberOf documents/schema#
|
|
* @name getSchema
|
|
* @method
|
|
*/
|
|
this.getSchema = function ( schemaName ) {
|
|
if ( sys.isEmpty( schemaName ) || !sys.isString() ) {
|
|
schemaName = this._defaultSchemaName;
|
|
}
|
|
return env.schema[schemaName];
|
|
}
|
|
},
|
|
/**
|
|
* This method will create a new object that contains only the fields and no methods or other artifacts. This is useful
|
|
* for creating objects to pass over the wire or save in a table. This is not deeply copied, so changes made to the
|
|
* extracted object will be represented in this class for reference objects.
|
|
*
|
|
* @param {string=} schema The schema name to use
|
|
* @param {object=} src The object to extract fields from
|
|
* @return {object} Data-only version of the class instance.
|
|
*/
|
|
extract : function ( schemaName, src ) {
|
|
if ( sys.isObject( schemaName ) ) {
|
|
src = schema;
|
|
schemaName = this._defaultSchemaName;
|
|
}
|
|
|
|
if ( sys.isEmpty( src ) ) {
|
|
src = this;
|
|
}
|
|
|
|
if ( sys.isFunction( src.toJSON ) ) {
|
|
src = src.toJSON();
|
|
}
|
|
var schema = this.getSchema( schemaName ) || {};
|
|
var newobj = {};
|
|
sys.each( schema.properties, function ( prop, propname ) {
|
|
if ( prop.properties && !sys.isUndefined( src[ propname ] ) ) {
|
|
newobj[ propname ] = this.extract( prop, src[propname] );
|
|
} else if ( !sys.isUndefined( src[ propname ] ) ) {
|
|
newobj[ propname ] = src[ propname ];
|
|
}
|
|
}, this );
|
|
|
|
return newobj;
|
|
},
|
|
/**
|
|
* Builds a default document based on the schema. What this does is create a document from schema and for each property
|
|
* that has a default value or is required, the resultant object will contain that property. It is useful for extending
|
|
* values from some source that may be incomplete, like options or some such.
|
|
* @param {json-schema} schema A schema to use to create the default document
|
|
* @returns {object?}
|
|
* @name defaultDoc
|
|
* @memberOf documents/schema#
|
|
* @method
|
|
*/
|
|
defaultDoc : function ( schemaName ) {
|
|
if ( sys.isEmpty( schemaName ) ) {
|
|
schemaName = this._defaultSchemaName;
|
|
}
|
|
var newdoc = {};
|
|
var schema;
|
|
|
|
if ( sys.isObject( schemaName ) ) {
|
|
schema = schemaName;
|
|
} else {
|
|
schema = this.getSchema( schemaName ) || {};
|
|
}
|
|
sys.each( schema.properties, function ( val, key ) {
|
|
|
|
var def = val[ "default" ]; // keyword and all that
|
|
if ( val.type === "object" && !sys.isEmpty( val.properties ) ) {
|
|
newdoc[ key ] = this.defaultDoc( val );
|
|
} else {
|
|
if ( sys.isFunction( def ) || sys.isBoolean( def ) || sys.isNumber( def ) || !sys.isEmpty( def ) ) {
|
|
|
|
if ( sys.isFunction( def ) ) {
|
|
newdoc[ key ] = def( schema );
|
|
} else {
|
|
newdoc[ key ] = def;
|
|
}
|
|
} else if ( val.required ) {
|
|
if ( val.type === 'string' ) {
|
|
newdoc[ key ] = null;
|
|
} else if ( val.type === 'object' ) {
|
|
newdoc[ key ] = {};
|
|
} else if ( val.type === 'array' ) {
|
|
newdoc[ key ] = [];
|
|
} else {
|
|
newdoc[ key ] = null;
|
|
}
|
|
}
|
|
}
|
|
}, this );
|
|
|
|
return newdoc;
|
|
}
|
|
} );
|
|
|
|
module.exports = Schema;
|