Validating agains an partial interface or external "class"
advdv opened this issue · comments
Hopefully its ok if I ask a question here,
first of all I like the library, fits in with the fail fast philosophy and the lack of javascripts type system. Due to the name it seems specifically fitting for duck typing. Yet from the documentation i find it hard to check wether an function argument is of a certain 'class'.
For example:
var nconf = require('nconf');
var foobar = function (conf) {
var self = this;
var args = ducky.params('foobar()', arguments, {
conf: {pos: 0, req: true, valid: '???'},
});
};
How would i go about checking if the first parameter is of type nconf.Provider
(https://github.com/flatiron/nconf)?
With the latest Ducky 2.0.4 you can do it directly:
var ducky = require('./lib/ducky.js');
var foobar = function () {
var args = ducky.params('foobar', arguments, {
conf: { pos: 0, req: true, valid: 'winston.Provider' }
});
};
winston = {};
winston.Provider = function () {};
winston.Provider.prototype.foo = function () {};
var p = new winston.Provider();
foobar(p);
With Ducky < 2.0.4 class validation worked only if the class is directly
placed into the global context as the dot-notation was not supported.
But just use Ducky >= 2.0.4 now and the above will work as shown.
Hey rse, quick response! awesome!. But although this would be sufficient in a browser environment it is not ideal in a Node.js environment. It is still depend on winston
(or any other reference anyone would like to use for type checking) being in the global scope (right?), this hardly ever happens in a node environment as its most often encapsulated in modules.
What do you think?
Good catch! For this I think the only possibility is that one has to "register" the particular type (= function object of the constructor) at Ducky, so that Ducky no longer has to resolve the type on a global object (like "window" in Browsers or "global" in Node).
So, something like:
// just once
var provider = require("winston").Provider;
ducky.validate.register("winstonProvider", provider);
[....]
// arbitrary times
ducky.param([...], valid: "winstonProvider" [...]);
How about this?
To me this is only a slight improvement. It still causes there to be a global state although it is moved from the global
object to the ducky.validate
which is less polluting. I don't know how much development effort it would take but I could think of the following options:
- Allow the
valid:
key to hold an arbritary function which is them able to simply return the result of aninstanceof
. Actually this might already be possible already since the docs mention this: " and valid for type validation (either a string accepted by the validate>() method, or a valid regular expression /.../ object for validating a String against it or an arbitrary validation callback function of signature valid(Object): Boolean." I could not get this to work:
var winston = require('winston');
[.....]
ducky.param([...], valid: function(obj){ return (obj instanceof winston.Provider); } [...]);
- Pass the type ("class") directly to the valid property. This would make the above no longer possible (making it less flexible) since all classes are functions but it hurts the eye a lot less:
var winston = require('winston');
[.....]
ducky.param([...], valid: winston.Provider [...]);
- Add another key to the params entry with the sole purpose of doing an
instanceof
, on top of the whole valid parsing. I like this because it adds an extra layer of validation: An object might be an instance of provider but didn't set some mandatory property. theinstanceOf
would take precedence over thevalid:
spec.
var winston = require('winston');
[.....]
ducky.param([...], valid: '{importantKey: string}', instanceOf: winston.Provider [...]);
The documentation was wrong: Ducky does not accept a callback function (background is: Ducky was extracted out of ComponentJS and there this special functionality exists, but for Ducky it is more clean that param()'s "valid" accepts exactly the same as the validate() function. So, option one of your suggestions doesn't work.
Option two has the problem that it is usable only for the special case that you are validating the top-level value. But "valid" is actually a full-blown validation specification and there classes have to be matched at lots of other positions, too. For instance, the validation string "{ provider: winston.Provider }" has to work, too. And there your option two doesn't work -- except we register the "winston.Provider" first under this name.
Option three is very special, of course. Hmmmm... sorry, I still think the registration approach provides both the necessary flexibility and can be correctly combined with the full-blown validation string scenario.
Ok, Ducky 2.1.0 now provides this host type registry functionality which at least is flexible and architecture-wise clean, even if it is perhaps not really elegant.