I often waste a lot of time tracking down stupid bugs that are to do with passing the wrong kind of argument to a function. As you know, JavaScript is not strongly typed, and this leads to much sloppiness on my part. While writing RipDB – scriptDB emulator, which is a replication of the old ScriptDB, I found I had no room for the kind of in-flight changes I sometimes do to accommodate my own untidiness, so I decided to expand on Reporting file, function and line number in Apps Script, which is part of my cUseful library, to include a simple check at run time of arguments being passed versus what was expected.

Here’s how it works.

This function

function validateArgs (funcArgs , funcTypes , optFail) {

}

where

  • funcArgs : an array of JavaScript items. Typically this would be the arguments received by a function.
  • funcTypes : an array of strings describing what type these arguments should be
  • optFail: whether or not to fail if the types of funcArgs don’t match the types described in funcTypes. Default is to throw an error
  • The function returns an object describing the first mismatch. If you choose not to throw an error on failure, the result’s .ok property can be tested.

Examples

Before moving on to applying this to testing function arguments, let’s look at some simple examples.

Valid checks

validateArgs (["abc",23,true,{"a":2}],  ["string","number","boolean","object"] );

It can also detect JavaScript objects

validateArgs ([new Date(), Date] ,  ["Date","function"] );

and Arrays

validateArgs ([[1,2],[{"a":2},{"b":3}]], ["Array.number","Array.object"] );

Optional or multiple types

validateArgs ([1,"w",false,new Date(),[3,4],[ "any","string","any","Array.any"] );

Missing arguments

validateArgs( [1,2] , ["number","number","any"] );

Array validation for missing arguments

validateArgs( [1,2,["a","b","c"]] , ["number","number","any.string"] );

Custom objects – from libraries or you create yourself

validateArgs ( [myDb , results] , [DbAbstraction, Array.DbResults] );

and so on.

Validating function arguments

Of course the real reason for this function is to validate arguments arriving in a function at run time against what was expected. One way is to repeat the function arguments.

function yourFunction ( arga , argb ) {
    cUseful.validateArgs ([arga,argb] , ["string","Array.object"]);
	}

The arguments parameter can be tweaked to look like an array and passed over, so a better way is to do this.

 cUseful.validateArgs ( 
      Array.prototype.slice.call(arguments),
      [ "string" ,"Array.object"] 
	  );

That way, the only thing you have to maintain is the expected types array and validateArgs will complain if you have less valid types specified than arguments arriving.

Throwing an error

By default, it will throw an error when an argument is mismatched. However, passing the optFail parameter as false will not fail but return a results object like this, which shows the entire stack and what the problem was in the detail property. Even if you choose not to fail, this result will be written to the log file. You can access it like this

var result = cUseful.validateArgs ( youArgs, yourChecks , false);
if (!result.ok) {
    ...do something with the result object
}

You'll get this
{
    "ok": false,
    "location": [
        {
            "caller": "whereAmI",
            "line": "326",
            "file": "Code (cUseful)"
        },
        {
            "caller": "report",
            "line": "582",
            "file": "Code (cUseful)"
        },
        {
            "caller": "check",
            "line": "576",
            "file": "Code (cUseful)"
        },
        {
            "caller": "validateArgs",
            "line": "529",
            "file": "Code (cUseful)"
        },
        {
            "caller": "getMutationResults",
            "line": "30",
            "file": "RipDbMutation (cRipDB)"
        },
        {
            "caller": "unknown",
            "line": "204",
            "file": "RipDb (cRipDB)"
        },
        {
            "caller": "unknown",
            "line": "155",
            "file": "RipDb (cRipDB)"
        },
        {
            "caller": "unknown",
            "line": "110",
            "file": "Code"
        },
        {
            "caller": "unknown",
            "line": "1170",
            "file": "cDbAbstraction (cDbAbstraction)"
        },
        {
            "caller": "unknown",
            "line": "1236",
            "file": "cDbAbstraction (cDbAbstraction)"
        },
        {
            "caller": "unknown",
            "line": "258",
            "file": "Code (cNamedLock)"
        },
        {
            "caller": "doGuts_",
            "line": "1230",
            "file": "cDbAbstraction (cDbAbstraction)"
        },
        {
            "caller": "unknown",
            "line": "1124",
            "file": "cDbAbstraction (cDbAbstraction)"
        },
        {
            "caller": "test",
            "line": "69",
            "file": "Code"
        },
        {
            "caller": "unknown",
            "line": "54",
            "file": "Code"
        },
        {
            "caller": "tall",
            "line": "52",
            "file": "Code"
        }
    ],
    "detail": {
        "index": 1,
        "arrayElement": -1,
        "type": "object",
        "expected": "object",
        "got": "undefined"
    }
	}

The code

Best way to get this is to include the cUseful library, or you can find on GitHub

For more like this see Google Apps Scripts Snippets
Why not join our forum,follow the blog or follow me on twitter to ensure you get updates when they are available.