bbc/flashheart

View on GitHub
docconfig/template/fixtures/documents/schema.js

Summary

Maintainability
B
6 hrs
Test Coverage
"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;