Easy validation in Scala using Scalaz, Readers and ValidationNel
I’m working on a new book for Packt which shows how you can use various Scala frameworks to create REST services. For this book I’m looking at the following list of frameworks: Finch, Unfiltered, Scalatra, Spray and Akka-HTTP (reactive streams).
While writing the chapters on Finch and Unfiltered I really liked the way these two frameworks handled validation. They both use composable validators which allow you to create easy to use validation chains, that collect any validation errors.
In this article I’ll show you how you can use such an approach yourself using the Scalaz library, and more specifically on how the Reader monad and the Validation applicative can be combined to create flexible, compassable validators, that accumulate any validation errors. As an example we’ll create a number of Readers that you can use to process an incoming HTTPRequest, get some parameters and convert it to a case class.
Getting started
The complete example can be found in the following Gist. All the code fragments in this article, are shown directly from that Gist. First, though, for this project we of course use SBT. Since we’ll only be using Scalaz, we have a very simple SBT build file:
name := "scalaz-readers" version := "1.0" scalaVersion := "2.11.7" libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.3"
To work with Scalaz, you can either import the required packages and functions individually, or, as we do in this case, be lazy and just import everything. So at the top of our example we’ll add the following:
import scalaz._ import Scalaz._ object ValidationSample extends App { }
Before we’ll look at the readers themselves, lets first define our case class and some type aliases to make things easier to understand:
// lets pretend this is the http request (or any other type of request) // that can be mapped to a map of values. type Request = Map[String, String] val Request = Map[String, String] _ // we also define what our reader looks like. In this case // we define that we want the result to be of type T, and our // reader expects a Request to process. The result of this reader // won't be a T, but will be a ValidationNel, which contains // a list of error messages (as String) or the actual T. type RequestReader[T] = Reader[Request, ValidationNel[String, T]] // To experiment, lets say we want to parse the incoming request into a // Person. case class Person(firstName: String, lastName: String, age: Int)
The Reader monad
Before we start creating our own readers, lets look a bit closer at what a reader monad allows you to do. We’ll look at the example from here (great introduction into Scalaz): http://eed3si9n.com/learning-scalaz-day10
scala> def myName(step: String): Reader[String, String] = Reader {step + ", I am " + _} myName: (step: String)scalaz.Reader[String,String] scala> def localExample: Reader[String, (String, String, String)] = for { a <- myName("First") b <- myName("Second") >=> Reader { _ + "dy"} c <- myName("Third") } yield (a, b, c) localExample: scalaz.Reader[String,(String, String, String)] scala> localExample("Fred") res0: (String, String, String) = (First, I am Fred,Second, I am Freddy,Third, I am Fred)
The point of a reader monad is to supply some configuration object (for instance a HTTPRequest, a DB, or anything else you can inject) without having to manually (or implicitly) pass it around all the functions. As you can see from this example we create three Readers (by calling myName) and pass in the request just once to the composed result. Note that for comprehension only works when all the readers are of the same type. In our case we have strings and ints so we use a somewhat different syntax to compose the readers as we’ll see later. The basic idea, however, is the same. We define a number of readers, and pass in the request to be processed.
Our Readers
We’ve defined our readers in a helper object, to make using them a bit easier. First let’s look at the complete code:
/** * Object which contains our readers (or functions that create readers), just simple readers * that check based on type. */ object Readers { def keyFromMap(request: Request, key: String) = request.get(key).map(Success(_)).getOrElse(Failure(s"Key: $key Not found")) // convert the provided validation containing a throwable, to a validation // containing a string. def toMessage[S](v: Validation[Throwable, S]): Validation[String, S] = { // Returns new Functor of type self, and set value to result of provided function ((f: Throwable) => s"Exception: ${f.getClass} msg: ${f.getMessage}") <-: v } def as[T](key: String)(implicit to: (String) => RequestReader[T]): RequestReader[T] = { to(key) } // Create a requestreader for booleanValues. implicit def asBool(key: String): RequestReader[Boolean] = { Reader((request: Request) => keyFromMap(request, key).flatMap({_.parseBoolean |> toMessage[Boolean] }).toValidationNel) } // Create a requestreader for intvalues. implicit def asInt(key: String): RequestReader[Int] = { Reader((request: Request) => keyFromMap(request, key).flatMap({_.parseInt |> toMessage[Int] }).toValidationNel) } // Create a requestreader for string values. implicit def asString(key: String): RequestReader[String] = { Reader((request: Request) => keyFromMap(request, key).toValidationNel) } }
What we do here is define a single as[T] method with an implicit to method, that returns a RequestReader of the specified type. Through the use of implicits scala will use one of the asString, AsInt etc. methods to determine how to convert the passed in key to a correct reader. Let’s look a bit closer at the asInt and the keyFromMap function.
def keyFromMap(request: Request, key: String) = request.get(key).map(Success(_)).getOrElse(Failure(s"Key: $key Not found")) // convert the provided validation containing a throwable, to a validation // containing a string. def toMessage[S](v: Validation[Throwable, S]): Validation[String, S] = { // Returns new Functor of type self, and set value to result of provided function ((f: Throwable) => s"Exception: ${f.getClass} msg: ${f.getMessage}") <-: v } implicit def asInt(key: String): RequestReader[Int] = { Reader((request: Request) => keyFromMap(request, key).flatMap({_.parseInt |> toMessage[Int] }).toValidationNel) }
The asInt function creates a new Reader and uses the request that is passed in to call the keyFromMap function. This function tries to get the value from the Map, and if it is successful it return a Success, if not a Failure. Next we flatMap this result (only applies when the result is a Success) and try to convert the found value to an integer using the scalar provided parseInt function, which in turn also returns a Validation. The result from this function is passed on to the toMessage function which transforms a Validation[Throwable, S] to a Validation[String, s]. Finally, before returning, we use the toValidationNel function to convert the Validation[String, Int] to a ValidationNel[String, Int]. We do this so that we can more easily collect all the failures together.
Creating a new validation, just means creating a new reader that returns a ValidationNel[String, T].
Composing validations
Now lets look at how we can compose the validations together. To do this we can use the ApplicativeBuilder from Scalaz like this:
// This reader doesn't accumulate the validations yet, just returns them as a tuple of Validations val toTupleReader = (as[String]("first") |@| as[String]("last") |@| as[Int]("age")).tupled // automatically convert to tuple
Using the |@| symbol we combine our readers using the scalaz ApplicativeBuilder. Using the tupled function we return a list of tuples containing the individual results of our readers, when they’ve run. To run this reader, we need to supply it with a request:
// our sample requests. This first one is invalid, val invalidRequest = Request(Seq( "first" -> "Amber", "las" -> "Dirksen", "age" -> "20 Months" )) // another sample request. This request is valid val validRequest = Request(Seq( "first" -> "Sophie", "last" -> "Dirksen", "age" -> "5" )) // now we can run our readers by supplying a request. val tuple3Invalid = toTupleReader.run(invalidRequest) val tuple3Valid = toTupleReader.run(validRequest) println(s"tuple3Invalid:\n $tuple3Invalid ") println(s"tuple3valid:\n $tuple3Valid ")
The result of these calls look like this:
tuple3Invalid: (Success(Amber),Failure(NonEmptyList(Key: last Not found)),Failure(NonEmptyList(Exception: class java.lang.NumberFormatException msg: For input string: "20 Months"))) tuple3valid: (Success(Sophie),Success(Dirksen),Success(5))
Even though this approach already allows us to create and compose validations and return the individual successes and failures, it will still take some work to get either the failures or convert the values to our case class. Luckily though, we can also easily collect the success and failures, since we’re using the ValidationNel object:
// This reader converts to a Success(person) or a failure Nel val toPersonReader = (as[String]("first") |@| as[String]("last") |@| as[Int]("age")) .apply((a, b, c) => (a |@| b |@| c ).apply(Person) ) // manually convert to case class
When this reader is run each individual validation will be applied, and passed into the provided apply function. In this function we collect the validations using the |@| constructor. This will return a Failure containing the collected errors, or an instantiated person if all the validations are successful:
val personInvalid = toPersonReader.run(invalidRequest) val personValid = toPersonReader.run(validRequest) println(s"personInvalid:\n $personInvalid ") println(s"personValid:\n $personValid ")
Which results in this:
personInvalid: Failure(NonEmptyList(Key: last Not found, Exception: class java.lang.NumberFormatException msg: For input string: "20 Months")) personValid: Success(Person(Sophie,Dirksen,5))
Cool right! This way we either get a success containing our domain object, or a single Failure object contain a list of errors.
The final part I’d like to show is an alternative way of collecting the validations. In the previous example we used the |@| syntax, you can also directly create an Applicative and use it to collect the validations:
// we can further process the tuple using an applicative builder |@|, or // we can use the Applicative.apply function like this: // we need to use a type lambda, since we use a higher order function val V = Applicative[({type λ[α]=ValidationNel[String, α]})#λ] val appValid: ValidationNel[String, Person] = V.apply3(tuple3Valid._1, tuple3Valid._2, tuple3Valid._3)(Person) val appInvalid: ValidationNel[String, Person] = V.apply3(tuple3Invalid._1, tuple3Invalid._2, tuple3Invalid._3)(Person) println(s"applicativeInvalid:\n $appInvalid") println(s"applicativeValid:\n $appValid")
And the output of this function is this:
applicativeInvalid: Failure(NonEmptyList(Key: last Not found, Exception: class java.lang.NumberFormatException msg: For input string: "20 Months")) applicativeValid: Success(Person(Sophie,Dirksen,5))
And that’s it. To summarise the main points:
- Using the reader pattern it is very easy to pass some context into functions for processing.
- With the applicative builder or the |@| symbol, you can easily compose readers that continue even if the first one should fail.
- By using a validationNel we can easily collect various validation results, and either return the collected errors, or return a single success.
Reference: | Easy validation in Scala using Scalaz, Readers and ValidationNel from our JCG partner Jos Dirksen at the Smart Java blog. |