rse / ducky

Duck-Typed Value Handling for JavaScript

Home Page:http://duckyjs.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Validating agains an partial interface or external "class"

advdv opened this issue · comments

commented

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.

commented

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?

commented

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 an instanceof. 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. the instanceOf would take precedence over the valid: 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.