Argument checking for native addons for Node.js. Do it right!
During development of CloudCV I came to the problem on converting
native C++ data types in my Node.js native module. If you are new to C++ and Node.js, I suggest you to read how to write C++ modules for Node.js and connecting OpenCV and Node.js first.
Mapping V8 data types to native C++ equivalents is trivial, but somewhat wordy. One should take the argument at given index, check whether it is defined, then check the argument type and finally cast to C++ type. This works fine while you have function that receive two or three arguments of trivial type (That can be mapped directly to built-in C++ types). What about strings? Arrays? Complex types like objects or function callback? Your code will grow like and became hard-to-maintain pasta-code some day.
In this post I present my approach on solving this problem with a laconic way on describing what do you expect as input arguments.
To illustrate the difference between imperative approach I included source code for calibrationPatternDetect method that expose function to detect calibration pattern on a single image to Node.js code. As you may see below, there are a lot of if conditions, magic numbers and no type checking for a half of arguments. But even without it, this function occupy 50 lines of code.
What even worse, 90% of this code is going to be the same for other functions. The main purpose of code of any
NAN_METHOD implementation - to marshal data in such a way it can be used by C++ code.
So the goal is to add more syntax sugar for argument checking.
Basically, it should provide a convenient way to check number and type of arguments passed.
For CloudCV project I’ve ended with a declarative approach because I found it fit my needs very much
and makes argument checking self-explanatory. Here is how new implementation of
calibrationPatternDetect looks like:
I hope you agree that second version is much more easy to read. Fluent architecture allows to write predicates in a chain, which actually is very similar to the way we thing. All predicate has self-telling names made from verb and a noun. So let me give you a brief overview what
NanCheck is capable of.
Method chaining (aka Fluent API) makes it very easy to build final predicate for argument checking via consecutive checks.
Each next step will be made if and only if all previous predicates were successful.
In case of error, predicate will throw an
ArgumentMismatchException exception that will terminate all further checks.
NanCheck(args) can be evaluated to
bool which makes it possible to use NanCheck in a condition statement:
To check particular argument at given index,
NanCheckArguments provide a
Argument(index) function. This function lets you to build a sub-predicate for given argument and bind it’s value with particular local variable:
NanCheck support type checking of the following built-in V8 types:
In addition, it offers
NotNull predicate to ensure argument is not null or empty.
The list of predicates will grow for sure. New functions to check whether argument is
v8::Boolean will be added in a next updates.
After type checking, it’s necessary to complete sub-predicate construction by binding argument to a local variable. Binding is a assignment of the argument (with data marshaling, if it’s necessary) to a variable that will be used later;
NanCheck support transparent binding to all v8 types (Number, String, Function, Object, Array, etc.), native C++ and OpenCV types (via marshaling system):
There is a special case of string arguments called
StringEnum - that is, a string argument, which can be one of a priory defined values. It introduced to support *C++ enum types and pass them
as string constants.
StringEnum predicate allow to parse string value and map to C++ enum type:
Thanks to C++11, it’s really easy to construct predicate chain using lambda functions. Basically predicate chain is nothing but a recursive anonymous function of the following form:
To illustrate an idea of building predicate chain, let’s take a look on ArgumentsCount implementation:
Here we construct outer predicate which compare number of arguments to expected value and throw an exception if it does not match.
With a help of
std::initializer_list it became really simple to declare string enum with minimal syntax overhead:
Now we’re able to call this function with arbitrary number of elements for this enum using
NanCheck helped me to reduce amount of code required to check arguments passed to CloudCV back-end. There are many cool ideas that I’ll probably add as soon as there will be necessity to have them in my library:
- Strongly typed objects (Objects with required fields)
- Optional parameters with default values
- Automatic type inference based on
- Support of multiple types per argument (Parameter can be either of type A or B)
Please leave your comments on this post. I’ve spent many hours on figuring out how to implement data marshaling and type checking in V8 and Node.js, so please help information to spread out - share and re-tweet this post. Cheers!