Hacker News new | ask | show | jobs
by CloselyChunky 1889 days ago
When validation gets complex (e.g. there are many criteria to check), I like to build a list/stream/array (what ever the language offers) of tuples of predicates (functions from the object that gets validated to boolean) and strings (or functions from the object to string so I can have context in my error messages).

Then iterate over the tuples, if a predicate fails, return the associated error message and throw an error/display the message to the user.

In the end it looks something like this:

  var validators = Stream.of(
    Map.entry(user -> user != null, "User must be defined"),
    Map.entry(user -> user.firstName != null, "Missing first name"))

  validators.filter(e -> e.getKey().apply(userToBeValidated)).map(Map.Entry::getValue).getFirst()
(This example uses Map.entry for tuples as Java lacks native support for tuples)

This limits branching and you have all validation criteria neatly organized in the same location.

3 comments

Sure, if you're validating some data there're loads of better ways to do it than a bunch of conditionals. I was purely commenting on the switch(true) pattern vs some "if"s.

That approach looks nice though. On that subject, JS has some nice libraries including io-ts[1] which has a functional approach using Eithers to encapsulate errors/success.

[1]: https://github.com/gcanti/io-ts

Sure, if you have an either/result type, the whole thing becomes a fold, where each validator is a function from user to Either<Error, User> and then

  validators.reduce(Either.right(user), (acc, next) -> acc.flatMapRight(next))
This way you'll end up with either the validated user, or the first error that occurred and all the other validators were skipped.
If you are using a language with yield you can make this simpler (and more flexible) by using a generator.
Agreed, +1 This pattern goes beyond JS and is very helpful for supporting tests; you've already factored out your validators. There's always going to be stragglers but you can define and push in an extra check on the spot (i.e. using lambda funcs at least in JS).