Created
December 1, 2010 23:04
-
-
Save bswerd/724407 to your computer and use it in GitHub Desktop.
Checks the parameters of a function with a specification that involves optional parameters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment