Skip to content

Instantly share code, notes, and snippets.

@alunny
Forked from bswerd/checkParams.js
Created December 1, 2010 23:48
Show Gist options
  • Save alunny/724470 to your computer and use it in GitHub Desktop.
Save alunny/724470 to your computer and use it in GitHub Desktop.
/**
* Contains utility functions to be used in various places.
*/
var util = {};
/**
* Checks the types and sanitizes arguments for functions with optional
* parameters and default values.
*
* @author Brad Swerdfeger
*
* @param {Array}
* args The arguments array from within the function whose arguments
* are to be checked.
* @param {Array}
* argFormat An object that specifies the format of the arguments for
* the function.
*
* Given a specification such as: doSomething(callback, [failure], options)
*
* where:
*
* callback {function} : required. A function to call when doSomething finishes.
*
* [failure] {function} : optional. A function to call when a failure occurs.
* Default is function(){}.
*
* options {object} : required. An options parameter with the following properties:
* url {string} : required. The url of something. Default is "http://www.google.com"
* obj {object} : optional. An object with the following properties:
* prop1 {string} : optional. A property. Default is "Hi".
* prop2 {int} : optional. A number. Default is 0.
*
* in this case argFormat is:
* [ {
* name : 'callback',
* type : 'function',
* required : true
* }, {
* name : 'failure',
* type : 'function',
* required : false,
* 'default' : function() {
* }
* }, {
* name : 'options',
* type : 'object',
* required : false,
* 'default' : { url : 'http://www.google.com' },
* contents : {
* url : {
* type : 'string',
* required : true
* },
* obj : {
* type : 'object',
* required : false,
* 'default' : {},
* contents : {
* prop1 : {
* type : 'string',
* required : false,
* 'default' : 'Hi'
* },
* prop2 : {
* type : 'number',
* required : false,
* 'default' : 0
* }
* }
* }
* }
* } ]
*
*
* @returns {Array} A sanitized and fully populated list of arguments. For
* instance, if the developer calls:
*
* doSomething(function(r) { console.log(r) }, { url: 'http://mysite.com'})
*
* the returned array will be:
* [
* function(r) { console.log(r) },
* function() {},
* { url : 'http://mysite.com', { prop1 : 'Hi', prop2 : 0} }
* ]
*
*/
util.checkArguments = function(args, argFormat) {
// before we do anything, if there are too many arguments provided, we fail.
if (args.length > argFormat.length)
throw new Error('Too many arguments provided. Provide at most '
+ argFormat.length + '.');
// now, check the number of arguments: count the number of required
// arguments and
// make sure we have at least that many.
var reqCount = 0;
// while we're doing that, we want to rearrange the possible parameters so
// that the
// required ones come first while maintaining order
var requiredArgs = [];
var optionalArgs = [];
// keep track of the distribution of optional and required args
var reqArray = [];
for ( var i in argFormat) {
if (argFormat[i].required) {
reqCount++;
requiredArgs.push(argFormat[i]);
reqArray.push(1);
} else {
optionalArgs.push(argFormat[i]);
reqArray.push(0);
}
}
// if the wrong number of arguments is provided:
if (args.length < reqCount)
throw new Error('Did not provide all required parameters. There are '
+ reqCount + '.');
// create a new arguments array that is properly defaulted we will return
// this at the end
var newArgs = [];
// figure out how many optional arguments are specified:
var optionalAllowed = args.length - reqCount;
// go through the arguments, using up optionals if we have them:
var i = 0; // counts how many args we've used total;
var j = 0; // counts how many of the user's args we've used
while ((requiredArgs.length > 0) || (optionalArgs.length > 0)) {
var required = reqArray[i];
var thisArgFormat = {}; // the format of the argument that we're
// checking
if (required) {
thisArgFormat = requiredArgs.shift();
} else {
thisArgFormat = optionalArgs.shift();
}
// if it's required, just use their arg
if (thisArgFormat.required) {
var theirArg = args[j];
newArgs.push(theirArg);
j++;
} else {
// if there are no optionals remaining, use the default
if (optionalAllowed > 0) {
optionalAllowed--;
var theirArg = args[j];
newArgs.push(theirArg);
j++;
} else {
newArgs.push(thisArgFormat['default']);
}
}
// check the type
if (typeof newArgs[i] != thisArgFormat.type) {
throw new Error('Invalid type for argument ' + thisArgFormat.name
+ '. Provided ' + typeof newArgs[i] + '. Should be '
+ thisArgFormat.type);
}
// if it is an object, check its properties
if ((typeof newArgs[i] == 'object')
&& (thisArgFormat.contents !== undefined)) {
newArgs[i] = util.checkObjectContents(newArgs[i],
thisArgFormat.contents);
}
i++;
if (i >= argFormat.length)
break;
}
return newArgs;
};
/**
* Checks the format and sanitizes an object based on a specification.
*
* @author Brad Swerdfeger
*
* @param {object} userObj The object supplied by the developer.
* @param {object} objFormat The format specification of the object
*
* @returns {object} The sanitized and fully populated object.
*
* This function works recursively. You can see the usage for in the
* above function for the arguments of type 'object'
*/
util.checkObjectContents = function(userObj, objFormat) {
// go through each of the properties in objFormat, checking against the
// user's
var newObj = {}; // the return object with sanitized values
for ( var prop in objFormat) {
var thisPropFormat = objFormat[prop];
// if the property is required and does not exist in the user's object,
// throw an error.
if ((thisPropFormat.required) && (userObj[prop] === undefined)) {
throw new Error('Did not specify required property ' + prop
+ ' with type ' + thisPropFormat.type + '.');
// if it's optional and the user did not specify anything
} else if (userObj[prop] === undefined) {
newObj[prop] = thisPropFormat['default'];
} else {
// either it is required and given or it is optional and given, just
// check the type.
if (typeof userObj[prop] != thisPropFormat.type) {
throw new Error('Invalid type for property ' + prop
+ '. Provided ' + typeof userObj[prop]
+ '. Should be ' + thisPropFormat.type);
}
newObj[prop] = userObj[prop];
}
// if it's an object with contents specified, we need to check it as
// well.
if ((typeof newObj[prop] == 'object')
&& (thisPropFormat.contents !== undefined)) {
newObj[prop] = util.checkObjectContents(newObj[prop],
thisPropFormat.contents);
}
}
return newObj;
};
/**
*
* Creates a new function based on a given function and a spec
* If the arguments meet the spec, calls the given function with those args
* otherwise throws an error
*
* @author Andrew Lunny
*
* @param {function} userObj A function with associated argument speciication
* @param {object} argFormat The format specification of the function
*
* @returns {function}
* A new function that checks the args, then calls the original
* function if they meet the spec
* Throws an error if the args don't meet spec
*
* Usage is something like
* some.newFunctionToDefine = util.checkedFunction(function (a, b, c) {
* // actual function code
* }, [{
* name: "a",
* type: "string",
* required: true
* },{
* name: "b",
* type: "object",
* required: false
* },{
* name: "c",
* type: "object",
* required: true
* }]);
*
*/
util.checkedFunction = function (fun, spec) {
// compile spec into an arg checker
var argChecker = checkArgumentsFunction(spec);
return function () {
return fun.apply(this, argChecker(Array.prototype.slice(arguments)));
}
// internal function to curry util.checkArguments
function checkArgumentsFunction (spec) {
return function () {
return util.checkArguments(Array.prototype.slice(arguments), spec);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment