Validation Inception

Introduction

The validation API provides macro-based helpers to generate Rule and Write for case classes (or any class with a companion object providing apply / and unapply methods).

The generated code:

  • is completely typesafe
  • is compiled
  • does not rely on runtime introspection at all
  • is strictly equivalent to a hand-written definition

Example

Traditionally, for a given case class Person we would define a Rule like this:

scala> case class Person(name: String, age: Int, lovesChocolate: Boolean)
defined class Person
import jto.validation._
import play.api.libs.json._

implicit val personRule: Rule[JsValue, Person] = From[JsValue] { __ =>
  import jto.validation.playjson.Rules._
  ((__ \ "name").read[String] ~
   (__ \ "age").read[Int] ~
   (__ \ "lovesChocolate").read[Boolean])(Person.apply)
}

Let's test it:

scala> val json = Json.parse("""{
     |   "name": "Julien",
     |   "age": 28,
     |   "lovesChocolate": true
     | }""")
json: play.api.libs.json.JsValue = {"name":"Julien","age":28,"lovesChocolate":true}

scala> personRule.validate(json)
res1: jto.validation.VA[Person] = Valid(Person(Julien,28,true))

The exact same Rule can be generated using Rule.gen:

import jto.validation._
import play.api.libs.json._

implicit val personRule = {
  import jto.validation.playjson.Rules._ // let's not leak implicits everywhere
  Rule.gen[JsValue, Person]
}

The validation result is identical :

scala> val json = Json.parse("""{
     |   "name": "Julien",
     |   "age": 28,
     |   "lovesChocolate": true
     | }""")
json: play.api.libs.json.JsValue = {"name":"Julien","age":28,"lovesChocolate":true}

scala> personRule.validate(json)
res3: jto.validation.VA[Person] = Valid(Person(Julien,28,true))

Similarly we can generate a Write:

import jto.validation._
import play.api.libs.json._

implicit val personWrite = {
  import jto.validation.playjson.Writes._ // let's no leak implicits everywhere
  Write.gen[Person, JsObject]
}
scala> personWrite.writes(Person("Julien", 28, true))
res5: play.api.libs.json.JsObject = {"name":"Julien","age":28,"lovesChocolate":true}

Known limitations

  • Don’t override the apply method of the companion object. The macro inspects the apply method to generate Rule/Write. Overloading the apply method creates an ambiguity the compiler will complain about.
  • Macros only work when apply and unapply have corresponding input/output types. This is naturally true for case classes. However if you want to validate a trait, you must implement the same apply/unapply you would have in a case class.
  • Validation Macros accept Option/Seq/List/Set & Map[String, _]. For other generic types, you'll have to test and possibly write your Rule/Write if it's not working out of the box.

results matching ""

    No results matching ""