roll your own validation in scala

A collegue approached me with a problem: He has a REST API and he wishes to validate the posted parameters and construct a class from them or throw an exception.

 

Validations are both trying to parse the parameter, e.g., convert to Int, as well as custom validations, e.g., make sure the number is positive, a string is of a certain format etc.

 

The trivial approach is to use a lot of 'if' statements or try-catch statements:

val i = param.toInt

if (i < 0) throw new IllegalArgumentException("param must be non negative")

 

Or, if we need a custom exception or want to do something else with errors:

var error: Throwable = _
try {
  val i = param.toInt

  if (i < 0) error = new IllegalArgumentException("param must be non negative")
} catch {
  case e => error = e
}

// do something with error

This of course gets worse as we handle more parameters and more types of validations.

 

What more, the requirement is to catch all validation errors for all parameters and report them as one to the user.

 

If we were in Java land, we would probably need at this point to find a library that does this sort of thing for us, with a lot of boilerplate and maybe some reflection.

 

We might try scalaz, but for me it is too complex to get into (btw, would love a comment to show how to do this in scalaz)

 

Fortunately, it is easy to roll our own.

 

The approach: a Validation object can be either a Success or a Failure. A Success holds a value that passed validation, a Failure holds a list of errors gathered. Success is fragile, once a validation fails, it becomes a Failure. The Failure does not care of future validations.

 

To validate the value inside a Validation object, we call the flatMap method and pass it a function

 

If you're familiar with Scala, this is very similar to Option: Success is like Some, Failure is like None. The difference is that Failure carries information (the errors)

 

Validation is generic: A is the value type, E is the error type (so we can have erros in the form of exceptions, or strings or whatever is suitable)

 

trait Validation[A, E] {
  def map[B](f: A => B): Validation[B, E]
  def flatMap[B, E2](f: A => Validation[B, E2]): Validation[B, E2]
  def getOrElse[B >: A](f: Seq[E] => B): B
  def >>=[B, E2](f: A => Validation[B, E2]): Validation[B, E2] = flatMap(f)
}

case class Success[A, E](a: A) extends Validation[A, E] {
  def map[B](f: A => B) = Success(f(a))
  def flatMap[B, E2](f: A => Validation[B, E2]): Validation[B, E2] = f(a)
  def getOrElse[B >: A](f: Seq[E] => B) = a
}

case class Failure[A, E](errors: Seq[E]) extends Validation[A, E] {
  def map[B](f: A => B) = this.asInstanceOf[Failure[B, E]]
  def flatMap[B, E2](f: A => Validation[B, E2]): Validation[B, E2] = this.asInstanceOf[Failure[B, E2]]
  def getOrElse[B >: A](f: Seq[E] => B) = f(errors)
}

 

We can add some utility methods to create  a Validation as well as validate if something can be done without exception or passes a predicate:

object Validation {
  def apply[A](a: A) = Success(a) 
  def succeeds[A, B](f: A => B): A => Validation[B, Throwable] = (a: A) => try {Success(f(a))} catch {case e => Failure(Seq(e))}
  def tests[A, E](f: A => Boolean,e: => E): A => Validation[A, E] = (a: A) => if (f(a)) Success(a) else Failure(Seq(e))
}

 

We can now validate as follows:

scala> val v = Validation("-1") >>= succeeds(_.toInt) >>= tests(_ >=0 , new IllegalArgumentException("param must be non negative"))
v: Validation[Int,java.lang.IllegalArgumentException] = Failure(List(java.lang.IllegalArgumentException: param must be non negative))

scala> val w = Validation("hi") >>= tests(!_.isEmpty, new IllegalArgumentException("must not be empty"))
w: Validation[java.lang.String,java.lang.IllegalArgumentException] = Success(hi)

 

So far, pretty simple. But how do we use these validations? We can try and match all to see if successfull, but then our code become ugly again.

 

What I'd like to have is create a composite validation object so that I can use `map` and `getOrElse` on it. Furthermore, I'd like things to be type safe. So the composite validation should carry a value that is a tuple of the values of parameters.

 

It is probably clearer to show usage code:

case class Person(name: String, age: Int)

(v ++ w) map {case (i, s) => Person(s, i)} getOrElse {s => throw s.head}

I create the compsite validation using the `++` operator. Since v is type Validation[Int, Throwable] and w is type Validation[String, Throwable], the composite validation is of type Validation[(Int, String), Throwable]. I can therefore map with a function that extracts the individual components and creates a Person object and in case of errors, throw the first one (or, create a list of errors and display / throw them)

 

The tricky part is implementing `++`. `v ++ w` should create a Validation[(Int, String), Throwable] while `v ++ w ++ u` should create a Validation[(Int, String, U), Throwable] where U is the type of value of u.

 

In other words, what is the return value of:

def ++[B, F >: E](vb: Validation[B, F])

?

 

If (the value type of the left validation) is any type, it should be Validation[(A, B), F]. But if A is a tuple, say (X, Y) then the result should be Validation[(X, Y, B), F]

 

To do that, we use type classes. First the code, then the explanation:

trait TupleAdd[A, B] {
  type Added
  def add(a: A, b: B): Added
}

trait LowLevelTupleAdd {
  implicit def any[A, B] = new TupleAdd[A, B] {
    type Added = (A, B)
    def add(a: A, b: B) = (a, b)
  }
}

object TupleAdd extends LowLevelTupleAdd {

  implicit def tuple2TA[A, B, C] = new TupleAdd[(A, B), C] {
    type Added = (A, B, C)
    def add(ab: (A, B), c: C) = (ab._1, ab._2, c)
  }
}

 

So TupleAdd of A and B defines a type for the result of adding the two into a tuple as well as a method that does this. I've provided two implementations: the first accepts any and B and creates a tupel (A, B). The second accepts a first element of type (A, B) and a second elemnet of type C and creates a tupe (A, B, C) (The use of LowLevelTupleAdd is to prioritize tuple2TA before any since the latter applys to all types). Since Validation is typesafe, and the compiler always tries to find the most restrictive implicit, this will do what we need.

 

Here is `++` in Validation (Note: We need the experimental feature of dependent method types for this to work. Use -Xexperimental to turn it on in the REPL or compiler)

 

 def ++[B, F >: E](vb: Validation[B, F])(implicit tu: TupleAdd[A, B]): Validation[tu.Added, F] = (this, vb) match {
    case (Success(a), Success(b)) => Success(tu.add(a,b))
    case (Failure(e1), Failure(e2)) => Failure(e1 ++ e2)
    case (_, f@Failure(e2)) => f.asInstanceOf[Validation[tu.Added, F]]
    case (f@Failure(e1), _) => f.asInstanceOf[Validation[tu.Added, F]]
  }

 

Here is the complete source code:

trait TupleAdd[A, B] {
  type Added
  def add(a: A, b: B): Added
}

trait LowLevelTupleAdd {
  implicit def any[A, B] = new TupleAdd[A, B] {
    type Added = (A, B)
    def add(a: A, b: B) = (a, b)
  }
}

object TupleAdd extends LowLevelTupleAdd {

  implicit def tuple2TA[A, B, C] = new TupleAdd[(A, B), C] {
    type Added = (A, B, C)
    def add(ab: (A, B), c: C) = (ab._1, ab._2, c)
  }

  
}

import collection.generic.CanBuildFrom
import collection.SeqLike

trait Validation[A, E] {
  def map[B](f: A => B): Validation[B, E]
  def flatMap[B, E2](f: A => Validation[B, E2]): Validation[B, E2]
  def >>=[B, E2](f: A => Validation[B, E2]): Validation[B, E2] = flatMap(f)
  def getOrElse[B >: A](f: Seq[E] => B): B
  def ++[B, F >: E](vb: Validation[B, F])(implicit tu: TupleAdd[A, B]): Validation[tu.Added, F] = (this, vb) match {
    case (Success(a), Success(b)) => Success(tu.add(a,b))
    case (Failure(e1), Failure(e2)) => Failure(e1 ++ e2)
    case (_, f@Failure(e2)) => f.asInstanceOf[Validation[tu.Added, F]]
    case (f@Failure(e1), _) => f.asInstanceOf[Validation[tu.Added, F]]
  }
}

case class Success[A, E](a: A) extends Validation[A, E] {
  def map[B](f: A => B) = Success(f(a))
  def flatMap[B, E2](f: A => Validation[B, E2]): Validation[B, E2] = f(a)
  def getOrElse[B >: A](f: Seq[E] => B) = a
}

case class Failure[A, E](errors: Seq[E]) extends Validation[A, E] {
  def map[B](f: A => B) = this.asInstanceOf[Failure[B, E]]
  def flatMap[B, E2](f: A => Validation[B, E2]): Validation[B, E2] = this.asInstanceOf[Failure[B, E2]]
  def getOrElse[B >: A](f: Seq[E] => B) = f(errors)
}


object Validation {
  def apply[A](a: A) = Success(a) 
  def succeeds[A, B](f: A => B): A => Validation[B, Throwable] = (a: A) => try {Success(f(a))} catch {case e => Failure(Seq(e))}
  def tests[A, E](f: A => Boolean,e: => E): A => Validation[A, E] = (a: A) => if (f(a)) Success(a) else Failure(Seq(e))
}

import Validation._

val v = Validation("38") >>= succeeds(_.toInt) >>= tests(_ >=0 , new IllegalArgumentException("param must be non negative"))

val w = Validation("ittay") >>= tests(!_.isEmpty, new IllegalArgumentException("must not be empty"))


case class Person(name: String, age: Int) 
val x = (v ++ w) map {case (i, s) => Person(s, i)} getOrElse {s => throw s.head}