Stefan Weiss wrote:
> There is also an alternative to currying. "Conform" (or whatever catchy
> name you can come up with) could be more than a simple namespace; it
> could be a real constructor:
>
> var addrCheck = new Conform(addressSpec);
> if (addrCheck.passes(obj)) {
> // we're fine
> } else {
> throw Error(addrCheck.lastError());
> }
>
> This way, you can have "static" methods like Conform.actsAs,
> Conform.check, Conform.assert, etc, and also specialized Conform objects
> with a stored specification object.
I created (the start of) an implementation along those lines.
You can see tests for it here:
<http://scott.sauyet.com/Javascript/Test/Conform/2011-03-23d/>
The JS is inlined in that document for the moment.
If I create newer versions, this should always point to the most
recent:
<http://scott.sauyet.com/Javascript/Test/Conform/>
The API is much like Stefan suggested, although I do hate the name
"Conform". (Remember Alan Cooper telling us all that all the forms on
the internet were Big Brother, insisting that we all 'submit'? I feel
the same about 'conform'!)
Conform.valid(spec, obj): boolean
Conform.invalid(spec, obj): boolean
Conform.check(spec, obj): possibly empty array of error messages
Conform.assert(spec, obj): no return, but might throw an error
Conform also serves as a constructor, so
var test = new Conform(spec); // 'new' is optional
test.valid(obj): boolean
test.invalid(obj): boolean
test.check(obj): possibly empty array of error messages
test.assert(obj): no return, but might throw an error
The constructor version is how I would probably want to use it, but
both are available, and the constructor version is a very thin layer
on top of the 'static' one.
There are many test cases on that page, but at the bottom of this post
I include a set that gives a fairly comprehensive overview of the
capabilities. One addition I have to npup's concepts was to validate
nested objects. There is only one error reported for each atomic
property (missing property, wrong type, or constraint violation), but
objects with their own properties also get error messages. The error
messages are not nested. I return a flat array with property names
for the errors reported in dotted-path notation, e.g.
"owner.address.city is not defined".
There is more to do. Besides the allowed and disallowed values, the
only constraints implemented are a few for Strings: 'nonEmpty',
'minLength', and 'maxLength'. There are obvious possibilities for
numbers and dates at least, and probably a 'nonEmpty' one for arrays
too. And I've done nothing yet with custom types. Another
possibility would be to allow the user to supply additional validation
functions or additional constraints; I don't think those would clutter
the API too much, but the implementation might get ugly. Finally,
there is no error checking of the spec object, which clearly needs to
be added to make this more than a toy.
As to the API, I'm fairly happy with it, except for two things: I
keep vacillating on whether to have both 'valid' and 'invalid', but
there's probably no reason to have both when they always return
opposite booleans from one another. And I'm still not happy with the
names. "Conform", "valid", "check", and possibly even "assert" could
use better names, but I haven't found them yet. There are possible
checks that I intentionally didn't include. For my use cases, there
is simply no reason to add an 'optional' tag to the specification.
Although this is moving closer toward the territory of the json-schema
specification, it's still meant to be plain duck-typing. I want to
use it to check if the object matches my current needs, not if it
validates against some formal definition. There is no reason that I
can see then for 'optional'. Also note that the specs I accept are
more robust than npup's original, but also more cumbersome.
The unit tests cover the static API almost exclusively. It just
doesn't seem worth the bother to add all the tests for the constructor
function version when I know that its implementation is such a thin
wrapper around the static one. But my TDD demon is screaming at
me. :-)
The implementation is pretty straightforward; I don't think there's
anything there that would throw an intermediate JS developer for a
loop. It's larger than npup's version, weighing in right now at
almost 150 lines, 6KB, which minimizes to 2KB and then gzips to 1KB.
But that seems a lot for the amount of functionality provided, and I
imagine the missing parts will increase it by 50%.
Please let me know what you think.
-- Scott
============
Sample code:
============
test("Multiple errors are correctly reported", function() {
var spec = {
name: {type: "string", maxLength: 10},
answer: {type: "number", allowed: [24, 7, 31, 365]},
calculate: {type: "function"},
config: {
type: "object",
spec: {
lucky: {type: "boolean"},
happy: {type: "boolean"}
}
}
};
var obj1 = {
name: "Yogi Bear",
answer: 31,
calculate: function() {},
config: {
lucky: true,
happy: true
}
};
var results1 = Conform.check(spec, obj1);
equals(results1.length, 0, "Should have no errors");
equals(Conform.valid(spec, obj1), true, "Definitely not
invalid");
equals(Conform.invalid(spec, obj1), false, "Clearly valid");
try {
Conform.assert(spec, obj1);
ok(true, "Assertion should succeed");
} catch(ex) {
ok(false, "Should not throw exception");
}
var conform1 = new Conform(spec);
results1 = conform1.check(obj1);
equals(results1.length, 0, "Should have no errors");
equals(conform1.valid(obj1), true, "Definitely not invalid");
equals(conform1.invalid(obj1), false, "Clearly valid");
try {
conform1.assert(obj1);
ok(true, "Assertion should succeed");
} catch(ex) {
ok(false, "Should not throw exception");
}
var obj2 = {
name: "Fred Flintstone",
answer: 42,
calculate: false,
config: {
lucky: false,
}
};
var results2 = Conform.check(spec, obj2);
equals(results2.length, 4, "Should have four distinct errors");
equals(Conform.valid(spec, obj2), false, "Definitely not
valid");
equals(Conform.invalid(spec, obj2), true, "Clearly invalid");
raises(function() {Conform.assert(spec, obj2);}, "Raises
Exception");
var conform2 = new Conform(spec);
results2 = conform2.check(obj2);
equals(results2.length, 4, "Should have four distinct errors,
OO");
equals(conform2.valid(obj2), false, "Definitely not valid, OO");
equals(conform2.invalid(obj2), true, "Clearly invalid, OO");
raises(function() {conform2.assert(obj2);}, "Raises Exception");
});
The result of the last two 'check' calls should look like this:
[
"name violates the constraint 'maxLength (10)' with a value of
'Fred Flintstone'.",
"answer has value '42', which is not in the allowd set of
[24,7,31,365].",
"calculate is not not of type 'function'.",
"config.happy is not defined."
]
--
To view archived discussions from the original JSMentors Mailman list:
http://www.mail-archive.com/[email protected]/
To search via a non-Google archive, visit here:
http://www.mail-archive.com/[email protected]/
To unsubscribe from this group, send email to
[email protected]