A little while ago a co-worker had expressed a dream of his to create some kind of language agnostic standard for validation. Without actually listening to what he meant by that, I set forth on doing this for JavaScript. I wanted to Lisp it up, as I am that kind of zealot. The idea was simple, provide a list like structure for describing the validation needs, but not make it a complete cluster *($#. I got the first part, not sure about the second. Essentially I wanted something like this:
var rules =
('Username',
('is not empty' 'Username is required.')
('is not longer than' 7 'Username is too long.'))
Where ‘Username’ is the name of the property needing the validation, and the ‘7’ is the maximum length, and the last part is the error. But, seeing as this was JavaScript, I had to do something a little more rigid.
Now for what the rules would look like:
var rules =
['Username',
['is not empty', 'Username is required.'],
['is not longer than', 7, 'Username is too long.']],
['Name',
['is not empty', 'Name is required.']],
['Password',
['length is between', 4, 6, 'Password is not acceptable.']]];
As you can see, the rules turn out to be very readable. On top of that, since it is only a list, converting a json based array to the rules should be pretty easy. The advantage of that would be the ability for a server response to contain the rules at which time the interpreter would match them to the methods. Thus providing a way to help with consistency of error messages, and constant values like in the password rule above. Something that is a pain in the #@@ some… all of the time.
The structure is fairly simple. [Property name [[validation need][extra parameters][error]…..[validation need][extra parameters][error]]] Essentially every property that is going to be validated can have n number of validation methods “attached” to it. Now one catch is that the object being validated has to have a matching “Property name”. Kind of falls into the “opinionated programming” side of things.
For this example, there will be three fairly generic validation methods. The methods either return null, or an error message. Over simplistic much?
function isNotEmpty(toCheck, propertyName, error, rest) {
return toCheck[propertyName] === '' ? error : null;
};
function isNotLongerThan(toCheck, propertyName, error, rest) {
return toCheck[propertyName].length > rest[0] ? error : null;
};
function isBetween(toCheck, propertyName, error, rest) {
var length = toCheck[propertyName].length;
return (length < rest[0] || length > rest[1]) ? error : null;
};
And here is an example of how to attach the english to methods:
var methods = [
['is not empty', isNotEmpty],
['is not longer than', isNotLongerThan],
['length is between', isBetween]];
I created a pseudo method look up table to match a much more “english” name for the validation method needed. Not exactly brilliant, and is sort of cheating. What would be nice is to actually read the english, break it down, and create the functions on the fly. BUT this works for now. That whole english breakdown I’ll attack at some point. After all, this was a sort of thought experiment.
The interpreter takes in the rules, matches the method, and send the extra values if needed. For example,
['length is between', 4, 6, 'Password is not acceptable.']
Is matched to:
isBetween(toCheck, propertyName, error, rest)
Where “rest” is the 4 and 6. Rest is a carryover from Commmon Lisp’s &rest. It’s a collection of things that will be broken down by the validation method. Just think of them as optional values.
var length = toCheck[propertyName].length;
return (length < rest[0] || length > rest[1]) ? error : null;
There are two parts to the interpreter, but the first part is merely rolling through the rules, and making a call to this:
src.site.validation.validationInterpreter.createAValidationCall = function(propertyName, methods, innerRule, find, car, cdr, peek, sink) {
//Find the method that matches the english phrase
var methodPair = find(methods, function(method) {
return car(method) === car(innerRule);
});
//In Closure the find method returns the whole matched "row", so I need the method which is the second column.
var methodToUse = peek(methodPair);
return function(obj) {
var error = peek(innerRule); //error is the last index
var values = sink(cdr(innerRule)); //get everything but the error
return methodToUse(obj, propertyName, error, values); //construct the validation call
};
};
car, cdr, sink, peek? The *(&^ are those? First two are carry overs from Common Lisp too. Car is just taking the first item of a list, if there is one.
(car '(1 2 3)) ;; 1
Cdr returns a new list that is everything but the first item.
(cdr '(1 2 3)) ;; '(2 3)
Haskell refers to them as Head and Tail. Clojure has First, and Rest. ect. ect. ect.
sink is used to get all but the last item in the list. Everything but the kitchen sink. Eh? Eh? Sigh. (The code for them can be seen here.).
peek is actually just a parameter cover for the goog.array.peek method. It gets the last element of an array. Remember, the structure is very specific. [validation need][extra parameters][error] Because of this, it’s easy to break down each rule into its respective parts using simple array manipulation.
Now at this point I only have created the method list creator (“Interpreter”), and haven’t created anything for running the validation. However, the code below is a rough estimate of how that would work.
var result = interpret(rules, methods);
var toValidate = {'Username': '12345678', 'Name': '', 'Password': '123'};
for(var i = 0; i < result.length; i++) { something.innerText += '\n' + result[i](toValidate); };
The glaring issue with that is that I’m not removing empty strings. Other than that, it works. The working example is in the example folder for this repository. The removal would be nothing more than using a map -> filter type method chain.
var validationMesssages = goog.array.map(result, function(item) { return item(toValidate); };
return goog.array.filter(validationMesssages, function(message) { return !goog.string.isEmptySafe(message); });
Or something like that… probably not exactly like that unless you’re in with the Closure, or are stuck with Jsomethingry.