Show:

File: ..\Scripts\IBlade\a05_assertParam.js

/**
 @module core
 **/

var Param = (function () {
    // The %1 parameter 
    // is required
    // must be a %2
    // must be an instance of %2
    // must be an instance of the %2 enumeration
    // must have a %2 property
    // must be an array where each element  
    // is optional or 

    var ctor = function(v, name) {
        this.v = v;
        this.name = name;
        this._contexts = [null];

    };
    var proto = ctor.prototype;

    proto.isObject = function() {
        return this.isTypeOf("object");
    };

    proto.isBoolean = function() {
        return this.isTypeOf('boolean');
    };

    proto.isString = function() {
        return this.isTypeOf('string');
    };

    proto.isNonEmptyString = function() {
        return addContext(this, {
            fn: isNonEmptyString,
            msg: "must be a nonEmpty string"
        });
    };

    function isNonEmptyString(context, v) {
        if (v == null) return false;
        return (typeof(v) === 'string') && v.length > 0;
    }

    proto.isNumber = function() {
        return this.isTypeOf('number');
    };

    proto.isFunction = function() {
        return this.isTypeOf('function');
    };


    proto.isTypeOf = function(typeName) {
        return addContext(this, {
            fn: isTypeOf,
            typeName: typeName,
            msg: __formatString("must be a '%1'", typeName)
        });
    };

    function isTypeOf(context, v) {
        if (v == null) return false;
        if (typeof(v) === context.typeName) return true;
        return false;
    }

    proto.isInstanceOf = function (type, typeName) {
        typeName = typeName || type.prototype._$typeName;
        return addContext(this, {
            fn: isInstanceOf,
            type: type,
            typeName: typeName,
            msg: __formatString("must be an instance of '%1'", typeName)
        });
    };

    function isInstanceOf(context, v) {
        if (v == null) return false;
        return (v instanceof context.type);
    }

    proto.hasProperty = function(propertyName) {
        return addContext(this, {
            fn: hasProperty,
            propertyName: propertyName,
            msg: __formatString("must have a '%1' property ", propertyName)
        });
    };

    function hasProperty(context, v) {
        if (v == null) return false;
        return (v[context.propertyName] !== undefined);
    }

    proto.isEnumOf = function(enumType) {
        return addContext(this, {
            fn: isEnumOf,
            enumType: enumType,
            msg: __formatString("must be an instance of the '%1' enumeration", enumType.name)
        });
    };

    function isEnumOf(context, v) {
        if (v == null) return false;
        return context.enumType.contains(v);
    }

    proto.isRequired = function(allowNull) {
        return addContext(this, {
            fn: isRequired,
            allowNull: allowNull,
            msg: "is required"
        });
    };

    function isRequired(context, v) {
        if (context.allowNull) {
            return v !== undefined;
        } else {
            return v != null;
        }
    }

    // combinable methods.

    proto.isOptional = function() {
        var context = {
            fn: isOptional,
            prevContext: null,
            msg: isOptionalMessage
        };
        return addContext(this, context);
    };

    function isOptional(context, v) {
        if (v == null) return true;
        var prevContext = context.prevContext;
        if (prevContext) {
            return prevContext.fn(prevContext, v);
        } else {
            return true;
        }
    }

    function isOptionalMessage(context, v) {
        var prevContext = context.prevContext;
        var element = prevContext ? " or it " + getMessage(prevContext, v) : "";
        return "is optional" + element;
    }

    proto.isNonEmptyArray = function() {
        return this.isArray(true);
    };

    proto.isArray = function(mustNotBeEmpty) {
        var context = {
            fn: isArray,
            mustNotBeEmpty: mustNotBeEmpty,
            prevContext: null,
            msg: isArrayMessage
        };
        return addContext(this, context);
    };


    function isArray(context, v) {
        if (!Array.isArray(v)) {
            return false;
        }
        if (context.mustNotBeEmpty) {
            if (v.length === 0) return false;
        }
        // allow standalone is array call.
        var prevContext = context.prevContext;
        if (!prevContext) return true;

        return v.every(function(v1) {
            return prevContext.fn(prevContext, v1);
        });
    }

    function isArrayMessage(context, v) {
        var arrayDescr = context.mustNotBeEmpty ? "a nonEmpty array" : "an array";
        var prevContext = context.prevContext;
        var element = prevContext ? " where each element " + getMessage(prevContext, v) : "";
        return " must be " + arrayDescr + element;
    }

    function getMessage(context, v) {
        var msg = context.msg;
        if (typeof(msg) === "function") {
            msg = msg(context, v);
        }
        return msg;
    }

    proto.or = function() {
        this._contexts.push(null);
        this._context = null;
        return this;
    };

    proto.check = function(defaultValue) {
        var ok = exec(this);
        if (ok === undefined) return;
        if (!ok) {
            throw new Error(this.getMessage());
        }

        if (this.v !== undefined) {
            return this.v;
        } else {
            return defaultValue;
        }
    };

    // called from outside this file.
    proto._addContext = function(context) {
        return addContext(this, context);
    };

    function addContext(that, context) {
        if (that._context) {
            var curContext = that._context;

            while (curContext.prevContext != null) {
                curContext = curContext.prevContext;
            }

            if (curContext.prevContext === null) {
                curContext.prevContext = context;
                // just update the prevContext but don't change the curContext.
                return that;
            } else if (context.prevContext == null) {
                context.prevContext = that._context;
            } else {
                throw new Error("Illegal construction - use 'or' to combine checks");
            }
        }
        return setContext(that, context);
    }

    function setContext(that, context) {
        that._contexts[that._contexts.length - 1] = context;
        that._context = context;
        return that;
    }


    function exec(self) {
        // clear off last one if null 
        var contexts = self._contexts;
        if (contexts[contexts.length - 1] == null) {
            contexts.pop();
        }
        if (contexts.length === 0) {
            return undefined;
        }
        return contexts.some(function(context) {
            return context.fn(context, self.v);
        });
    }


    proto.getMessage = function() {
        var that = this;
        var message = this._contexts.map(function(context) {
            return getMessage(context, that.v);
        }).join(", or it ");
        return __formatString(this.MESSAGE_PREFIX, this.name) + " " + message;
    };

    proto.withDefault = function(defaultValue) {
        this.defaultValue = defaultValue;
        return this;
    };

    proto.whereParam = function(propName) {
        return this.parent.whereParam(propName);
    };


    proto.applyAll = function (instance, checkOnly, allowUnknownProperty) {
        var parentTypeName = instance._$typeName;
        allowUnknownProperty = allowUnknownProperty || (parentTypeName && this.parent.config._$typeName === parentTypeName);
        
        var clone = __extend({}, this.parent.config);
        this.parent.params.forEach(function(p) {
            if (!allowUnknownProperty) delete clone[p.name];
            try {
                p.check();
            } catch(e) {
                throwConfigError(instance, e.message);
            }
            (!checkOnly) && p._applyOne(instance);
        });
        // should be no properties left in the clone
        if (!allowUnknownProperty) {
            for (var key in clone) {
                // allow props with an undefined value
                if (clone[key] !== undefined) {
                    throwConfigError(instance, __formatString("Unknown property: '%1'.", key));
                }
            }
        }
    };
    
    function throwConfigError(instance, message) {
        throw new Error(__formatString("Error configuring an instance of '%1'. %2", (instance && instance._$typeName) || "object", message));
    }

    proto._applyOne = function(instance) {
        if (this.v !== undefined) {
            instance[this.name] = this.v;
        } else {
            if (this.defaultValue !== undefined) {
                instance[this.name] = this.defaultValue;
            }
        }
    };

    proto.MESSAGE_PREFIX = "The '%1' parameter ";
    return ctor;
})();

var assertParam = function (v, name) {
    return new Param(v, name);
};

var ConfigParam = (function() {
    var ctor = function(config) {
        if (typeof(config) !== "object") {
            throw new Error("Configuration parameter should be an object, instead it is a: " + typeof(config));
        }
        this.config = config;
        this.params = [];
    };
    var proto = ctor.prototype;

    proto.whereParam = function(propName) {
        var param = new Param(this.config[propName], propName);
        param.parent = this;
        this.params.push(param);
        return param;
    };
    return ctor;
})();

var assertConfig = function(config) {
    return new ConfigParam(config);
};

// Param is exposed so that additional 'is' methods can be added to the prototype.
core.Param = Param;
core.assertParam = assertParam;
core.assertConfig = assertConfig;