/*
* USAGE
*
* var Argument = require('./argument-check.js');
*
* var check;
* var value = ...;
* var msg = 'Wrong argument!';
*
* // Single usage:
* value = Argument.check(value).for(VALUE_NAME).asString(msg);
* value = Argument.inConstructor(CLASS_NAME).check(value).for(VALUE_NAME).asString(msg);
* value = Argument.inMethod(CLASS_NAME, METHOD_NAME).check(value).for(VALUE_NAME).asString(msg);
* value = Argument.inProperty(CLASS_NAME, PROPERTY_NAME).check(value).for(VALUE_NAME).asString(msg);
*
* // Multiple usage:
* check = Argument(); // generic arguments
* check = Argument.inConstructor(CLASS_NAME); // constructor arguments
* check = Argument.inMethod(CLASS_NAME, METHOD_NAME); // method arguments
* check = Argument.inProperty(CLASS_NAME, PROPERTY_NAME); // property arguments
*
* value = check(value).for(VALUE_NAME).asString(msg); // any or special argument
* value = check(value).forOptional(VALUE_NAME).asString(msg); // optional argument
* value = check(value).forMandatory(VALUE_NAME).asString(msg); // mandatory argument
*
* value = check(value).forOptional(VALUE_NAME).asType([ CollectionBase, ModelBase ], msg); // additional attribute
* value = check(value).forMandatory(VALUE_NAME).asType(UserInfo, msg); // additional attribute
*
* value = check(value).for(VALUE_NAME).asEnumMember(Action, Action.Save, msg); // two additional attributes
*/
var ArgumentError = require('./argument-error.js');
var ConstructorError = require('./constructor-error.js');
var MethodError = require('./method-error.js');
var PropertyError = require('./property-error.js');
//region Argument group
var ArgumentGroup = {
General: 0,
Constructor: 1,
Method: 2,
Property: 3
};
//endregion
//region Argument check
/**
* Creates an argument check instance for the given value.
*
* @memberof bo.system
* @constructor
* @param {*} [value] - The value to check.
* @returns {bo.system.ArgumentCheck} The argument check instance.
*/
function ArgumentCheck (value) {
this.value = value;
return this;
}
//endregion
//region Argument check builder
//region For
/**
* Sets the name of the argument.
*
* @function bo.system.ArgumentCheck.for
* @param {string} [argumentName] - The name of the argument.
* @returns {bo.system.ArgumentCheck} The argument check instance.
*/
function forGeneric (argumentName) {
this.argumentName = argumentName || '';
this.isMandatory = undefined;
return this;
}
/**
* Sets the name of the optional argument.
*
* @function bo.system.ArgumentCheck.forOptional
* @param {string} [argumentName] - The name of the optional argument.
* @returns {bo.system.ArgumentCheck} The argument check instance.
*/
function forOptional (argumentName) {
this.argumentName = argumentName || '';
this.isMandatory = false;
return this;
}
/**
* Sets the name of the mandatory argument.
*
* @function bo.system.ArgumentCheck.forMandatory
* @param {string} [argumentName] - The name of the mandatory argument.
* @returns {bo.system.ArgumentCheck} The argument check instance.
*/
function forMandatory (argumentName) {
this.argumentName = argumentName || '';
this.isMandatory = true;
return this;
}
//endregion
//region Exception
function exception (defaultMessage, typeArgument, userArguments) {
var error, type;
var message = defaultMessage;
var parameters = [];
if (userArguments && userArguments.length) {
parameters = Array.prototype.slice.call(userArguments);
message = parameters.shift() || defaultMessage;
}
var args = [null, message || 'default'];
switch (this.argumentGroup) {
case ArgumentGroup.Property:
type = PropertyError;
args.push(this.className || '<class>', this.propertyName || '<property>');
break;
case ArgumentGroup.Method:
type = MethodError;
args.push(this.className || '<class>', this.methodName || '<method>');
break;
case ArgumentGroup.Constructor:
type = ConstructorError;
args.push(this.className || '<class>');
break;
case ArgumentGroup.General:
default:
type = ArgumentError;
break;
}
args.push(this.argumentName);
if (typeArgument)
args.push(typeArgument);
if (parameters.length)
parameters.forEach(function (parameter) {
args.push(parameter);
});
error = type.bind.apply(type, args);
throw new error();
}
//endregion
//region General
/**
* for: Checks if value is not undefined.
*
* @function bo.system.ArgumentCheck.asDefined
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {*} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be supplied.
*/
function asDefined () {
if (this.value === undefined)
this.exception('defined', null, arguments);
return this.value;
}
/**
* for: Checks if value is not undefined and is not null.
*
* @function bo.system.ArgumentCheck.hasValue
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {*} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument is required.
*/
function hasValue () {
if (this.value === null || this.value === undefined)
this.exception('required', null, arguments);
return this.value;
}
//endregion
//region String
/**
* for: Checks if value is a string.<br/>
* forOptional: Checks if value is a string or null.<br/>
* forMandatory: Checks if value is a non-empty string.
*
* @function bo.system.ArgumentCheck.asString
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(string|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a string value.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a string value or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a non-empty string.
*/
function asString () {
switch (this.isMandatory) {
case true:
if (typeof this.value !== 'string' && !(this.value instanceof String) || this.value.trim().length === 0)
this.exception('manString', null, arguments);
break;
case false:
if (this.value === undefined)
this.value = null;
if (this.value !== null && typeof this.value !== 'string' && !(this.value instanceof String))
this.exception('optString', null, arguments);
break;
default:
if (typeof this.value !== 'string' && !(this.value instanceof String))
this.exception('string', null, arguments);
break;
}
return this.value;
}
//endregion
//region Number
/**
* forOptional: Checks if value is a number or null.<br/>
* forMandatory: Checks if value is a number.
*
* @function bo.system.ArgumentCheck.asNumber
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(number|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a number value or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a number value.
*/
function asNumber () {
if (this.isMandatory) {
if (typeof this.value !== 'number' && !(this.value instanceof Number))
this.exception('manNumber', null, arguments);
} else {
if (this.value === undefined)
this.value = null;
if (this.value !== null && typeof this.value !== 'number' && !(this.value instanceof Number))
this.exception('optNumber', null, arguments);
}
return this.value;
}
//endregion
//region Integer
/**
* forOptional: Checks if value is an integer or null.<br/>
* forMandatory: Checks if value is an integer.
*
* @function bo.system.ArgumentCheck.asInteger
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(number|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be an integer value or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be an integer value.
*/
function asInteger () {
if (this.isMandatory) {
if (typeof this.value !== 'number' && !(this.value instanceof Number) || this.value % 1 !== 0)
this.exception('manInteger', null, arguments);
} else {
if (this.value === undefined)
this.value = null;
if (this.value !== null && (typeof this.value !== 'number' &&
!(this.value instanceof Number) || this.value % 1 !== 0))
this.exception('optInteger', null, arguments);
}
return this.value;
}
//endregion
//region Boolean
/**
* forOptional: Checks if value is a Boolean or null.<br/>
* forMandatory: Checks if value is a Boolean.
*
* @function bo.system.ArgumentCheck.asBoolean
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(boolean|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a Boolean value or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a Boolean value.
*/
function asBoolean () {
if (this.isMandatory) {
if (typeof this.value !== 'boolean' && !(this.value instanceof Boolean))
this.exception('manBoolean', null, arguments);
} else {
if (this.value === undefined)
this.value = null;
if (this.value !== null && typeof this.value !== 'boolean' && !(this.value instanceof Boolean))
this.exception('optBoolean', null, arguments);
}
return this.value;
}
//endregion
//region Object
/**
* forOptional: Checks if value is an object or null.<br/>
* forMandatory: Checks if value is an object.
*
* @function bo.system.ArgumentCheck.asObject
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(object|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be an object or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be an object.
*/
function asObject () {
if (this.isMandatory) {
if (typeof this.value !== 'object' || this.value === null)
this.exception('manObject', null, arguments);
} else {
if (this.value === undefined)
this.value = null;
if (typeof this.value !== 'object')
this.exception('optObject', null, arguments);
}
return this.value;
}
//endregion
//region Function
/**
* forOptional: Checks if value is a function or null.<br/>
* forMandatory: Checks if value is a function.
*
* @function bo.system.ArgumentCheck.asFunction
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(function|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a function or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a function.
*/
function asFunction () {
if (this.isMandatory) {
if (typeof this.value !== 'function')
this.exception('manFunction', null, arguments);
} else {
if (this.value === undefined)
this.value = null;
if (this.value !== null && typeof this.value !== 'function')
this.exception('optFunction', null, arguments);
}
return this.value;
}
//endregion
//region Type
function typeNames (types) {
var list = '<< no types >>';
if (types.length) {
list = types.map(function (type) {
return type.name ? type.name : '-unknown-'
}).join(' | ');
}
return list;
}
/**
* forOptional: Checks if value is a given type or null.<br/>
* forMandatory: Checks if value is a given type.
*
* @function bo.system.ArgumentCheck.asType
* @param {(constructor|Array.<constructor>)} type - The type that value must inherit.
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(object|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a TYPE object or null.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a TYPE object.
*/
function asType () {
var args = Array.prototype.slice.call(arguments);
var type = args.shift();
var types = type instanceof Array ? type : [ type ];
var self = this;
if (this.isMandatory) {
if (!(types.some(function (option) {
return self.value && (self.value instanceof option || self.value.super_ === option);
})))
this.exception('manType', typeNames(types), args);
} else {
if (this.value === undefined)
this.value = null;
if (this.value !== null && !(types.some(function (option) {
return self.value && (self.value instanceof option || self.value.super_ === option);
})))
this.exception('optType', typeNames(types), args);
}
return this.value;
}
//endregion
//region Model
/**
* for: Checks if value is an instance of a given model type.
*
* @function bo.system.ArgumentCheck.asModelType
* @param {(constructor|Array.<constructor>)} model - The model type that value must be an instance of.
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {object} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be a model type.
*/
function asModelType () {
var args = Array.prototype.slice.call(arguments);
var model = args.shift();
var models = model instanceof Array ? model : [ model ];
var self = this;
if (!(models.some(function (modelType) {
return self.value && self.value.constructor && self.value.constructor.modelType === modelType;
})))
this.exception('modelType', models.join(' | '), args);
return this.value;
}
//endregion
//region Enumeration
/**
* for: Checks if value is member of a given enumeration.
*
* @function bo.system.ArgumentCheck.asEnumMember
* @param {constructor} type - The type of the enumeration.
* @param {number} [defaultValue] - The type of the enumeration.
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {number} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}: Type is not an enumeration type.
* @throws {@link bo.system.ArgumentError Argument error}: The argument must be an enumeration type item.
*/
function asEnumMember () {
var args = Array.prototype.slice.call(arguments);
var type = args.shift();
var defaultValue = args.shift();
if (!(type && type.hasMember && type.constructor &&
type.constructor.super_ && type.constructor.super_.name === 'Enumeration'))
this.exception('enumType', type, args);
if ((this.value === null || this.value === undefined) && typeof defaultValue === 'number')
this.value = defaultValue;
if (!type.hasMember(this.value))
this.exception('enumMember', type.$name, args);
return this.value;
}
//endregion
//region Array
/**
* forOptional: Checks if value is an array of a given type or null.<br/>
* forMandatory: Checks if value is an array of a given type.
*
* @function bo.system.ArgumentCheck.asArray
* @param {*} type - The type of the array items - a primitive type or a constructor.
* @param {string} [message] - Human-readable description of the error.
* @param {...*} [messageParams] - Optional interpolation parameters of the message.
* @returns {(Array.<type>|null)} The checked value.
*
* @throws {@link bo.system.ArgumentError Argument error}:
* The argument must be an array of TYPE values, or a single TYPE value or null.
* @throws {@link bo.system.ArgumentError Argument error}:
* The argument must be an array of TYPE objects, or a single TYPE object or null.
* @throws {@link bo.system.ArgumentError Argument error}:
* The argument must be an array of TYPE values, or a single TYPE value.
* @throws {@link bo.system.ArgumentError Argument error}:
* The argument must be an array of TYPE objects, or a single TYPE object.
*/
function asArray () {
if (!this.isMandatory) {
if (this.value === undefined || this.value === null)
return [];
}
var msgKey;
var args = Array.prototype.slice.call(arguments);
var type = args.shift();
if (type === String || type === Number || type === Boolean) {
msgKey = this.isMandatory ? 'manArrayPrim' : 'optArrayPrim';
var typeName = type.name.toLowerCase();
if (typeof this.value === typeName || this.value instanceof type)
return [this.value];
if (this.value instanceof Array && (!this.value.length || this.value.every(function (item) {
return typeof item === typeName || item instanceof type;
})))
return this.value;
} else {
msgKey = this.isMandatory ? 'manArray' : 'optArray';
if (this.value instanceof type)
return [this.value];
if (this.value instanceof Array && (!this.value.length || this.value.every(function (item) {
return item instanceof type;
})))
return this.value;
}
this.exception(msgKey, type, args);
}
//endregion
function ArgumentCheckBuilder (argumentGroup, className, methodName, propertyName) {
var builderBase = {
argumentGroup: argumentGroup || ArgumentGroup.General,
className: className || '',
methodName: methodName || '',
propertyName: propertyName || '',
argumentName: '',
isMandatory: undefined,
for: forGeneric,
forOptional: forOptional,
forMandatory: forMandatory,
exception: exception,
asDefined: asDefined,
hasValue: hasValue,
asString: asString,
asNumber: asNumber,
asInteger: asInteger,
asBoolean: asBoolean,
asObject: asObject,
asFunction: asFunction,
asType: asType,
asModelType: asModelType,
asEnumMember: asEnumMember,
asArray: asArray
};
var fnCheck = ArgumentCheck.bind(builderBase);
fnCheck.check = function (value) {
return this(value);
};
return fnCheck;
}
//endregion
//region Argument check factory
/**
* Creates a general argument check object.
* @function bo.system.Argument
*/
function ArgumentCheckFactory() {
return ArgumentCheckBuilder(ArgumentGroup.General, '', '', '');
}
/**
* Creates a general argument check object.
* @function bo.system.Argument.check
* @param {*} value - The value to check.
* @returns {bo.system.ArgumentCheck} - Argument check object.
*/
ArgumentCheckFactory.check = function(value) {
return ArgumentCheckBuilder(ArgumentGroup.General, '', '', '')(value);
};
/**
* Creates a constructor argument check object.
* @function bo.system.Argument.inConstructor
* @param {string} className - The name of the class of the constructor.
* @returns {bo.system.ArgumentCheck} - Argument check object.
*/
ArgumentCheckFactory.inConstructor = function (className) {
return ArgumentCheckBuilder(ArgumentGroup.Constructor, className, '', '');
};
/**
* Creates a method argument check object.
* @function bo.system.Argument.inMethod
* @param {string} className - The name of the class of the method.
* @param {string} methodName - The name of the method.
* @returns {bo.system.ArgumentCheck} - Argument check object.
*/
ArgumentCheckFactory.inMethod = function (className, methodName) {
return ArgumentCheckBuilder(ArgumentGroup.Method, className, methodName, '');
};
/**
* Creates a property argument check object.
* @function bo.system.Argument.inProperty
* @param {string} className - The name of the class of the property.
* @param {string} propertyName - The name of the property.
* @param propertyName
* @returns {bo.system.ArgumentCheck} - Argument check object.
*/
ArgumentCheckFactory.inProperty = function (className, propertyName) {
return ArgumentCheckBuilder(ArgumentGroup.Property, className, '', propertyName);
};
//endregion
module.exports = ArgumentCheckFactory;