Argument checking for native addons for Node.js. Do it right!
During development of CloudCV I came to the problem on converting v8::Arguments
to
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.
Fluent API
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:
|
|
Type checking
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:
|
|
Currently, NanCheck
support type checking of the following built-in V8 types:
v8::Function
v8::Object
v8::String
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::Array
, v8::Number
, v8::Integer
, v8::Boolean
will be added in a next updates.
Binding
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:
|
|
Implementation highlights
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
std::initializer_list
syntax:
|
|
Conclusion
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
Bind<T>(...)
type. - 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!