The API is still a draft and may change before the release of Play 2.3
@mandubian and I talked about this Architecture and general and the new validation API at pingconf in January. You may want to skip the first 30 minutes, and jump directly to the API. This article sums up what we said there, and explore the validation API a bit deeper.
Why validation ?
When you think about a web application, you're probably thinking of a bunch of web-pages, or single page app, or maybe even just a REST API serving json content. Truth is, a web application can be all of that, and even more, but from a more abstract point of view, the job of a webapp is really just to answer queries. Queries are formulated using HTTP requests, and answered using HTTP responses, and that's pretty much all there is to know. HTML, JSON, XML, or any other format are "representations" of the data. Your application may, and even probably should, support multiple representations, so the client can list the representation it supports, and the server can then decide which one is appropriate. That's content negotiation.
The client can request different representations, but the client can also send data to your application, and again you need to support different representations. Maybe the user is submitting a form ? You're going to receive URLFormEncoded values. Maybe this form is a bit dynamic, and the values submitted by an AJAX request in JSON? You may also offer a restful XML api. Or maybe data are sent using a proprietary protocol.
Of course, when an HTTP request comes in, one of the the first things you do is to de-serialize it's body to transform it to something more convenient than Bytes. For example an instance of a class. And once you've decided what to send back, you need to serialize that to the appropriate representation. So for each HTTP request, you'll parse, and eventually serialize data. But it's not that simple, the data coming in are not "safe". You must validate everything.
Data validation in Playframework
Play offers two different APIs for the Job. One dedicated to Form validation, and one to JSON marshaling and unmarshaling.
The scala form API is really simple. The core of it is the
The two important methods are
bind maps forms data (
Map[String, String]) to a type
T. Of course this mapping can fail, for example if some fields are missing, or their values are invalid. So instead of just returning a
T, the method returns either all the validation errors, or a value of type
Here's an example from play's documentation:
nonEmptyText is just a built-in mapping, and
optional is just a function lifting a
Mapping[A] to a
You build a new mapping by composing existing mappings together.
UserData.unapply are there to turn a successful mapping application into a
UserData class instance, and a
UserData class instance into form data.
Since all the hard work is done by
Mapping, you may be wondering what's the job of the
Form object now. The form object is just providing a bunch of helpers used by the templates to get error messages, pre-filled values, etc.
The Json API is based on 2 concepts:
Writes. Obviously a
Reads is for parsing and a
Write is for serializing.
Reads is basically just a function from a JsValue to a type
Reads[A] = JsValue => JsResult[A]
and what is a
JsResult ? well it can be either a success or a failure:
JsResult[A] = JsSuccess(a: A) | JsFailure(errors: Seq[(JsPath,ValidationError)])
Yep, that's pretty much the same thing! But the Form api can also "serialize" classes instances to form values. Can we do that we json? Of course. We just have to use a
And what's a
Pretty much just a function from
Now the Json API also has a class called
Now let's compare the two APIs side by side:
Format, the previous example may be implemented this way:
So again, you compose
Formats to built new
Formats. Very similar to the Form api.
We've demonstrated that appart from the different syntax, the APIs are fairly similar.
Generalizing the JSON API
Now those two APIs are really nice, but having to learn two different API is really frustrating, especially when the only difference is the representation they handle. Having two API's also means that all your custom validation rules are duplicated. We knew play should come with a better solution, so in collaboration with @typesafe, and with the help of @mandubian and @sadache I was in charge of designing and implementing a new validation API.
The objectives are
- Support any representation through extensions.
- Easy migration from the JSON API.
- Built-in support for json and forms
- Compatibility with the template helpers
Introducing the new validation API
The new validation API is very similar to the Json API. Let's compare
Reads to it's generic counterpart:
The obvious difference is the new
I type parameter. I is the input type, generally the type you're parsing. for example if you define a validation of
Instead of a
JsResult, the result of applying a Rule returns a
VA[I, O], which is just a type alias:
Validation, just like JsSuccess from the Json API, has two possible implementations:
Just like the Json API, you can compose rules together:
Here you've noticed one of the key difference with the Json API. Since the API is not only dedicated to one representation, we need to tell the compiler what representation you want to work with using
From[...]. Since we enclosed the validation in a
From[JsValue]. We are now defining a
Rule validating a Json AST.
Note that we are also importing
import play.api.data.mapping.json.Rules._. That object contains all the built-in validation for the Json type.
Since the API also supports forms data, we can define a form validation very easily:
We just had to change the From type, and the import.
Differences with the json API
Even though the validation API is largely inspired from the JSON api, there are still a few key differences:
The JSON API is always assuming you're going from, or to json. Since the new API is generic, you always have to be explicit about the
This change has some impacts on the API use and possibilities. The most obvious is that the new API is a tiny bit more verbose than the json API.
But the generalization also impacts positively the API, and some things are easier to implement.
Let's say you implemented 2 Rules (their implementations is let as exercise to the reader):
You may want to create a new Rule, validating that a given JsValue is a positive Int. All you have to do is to compose those rules sequentially. First you test the value is an Int using
isInt, then you test it's positive with
min. That's a very simple task with the API.
With the Json API, a
Reads always parses
JsValue. You just can't define a Reads validating an Int value. You're forced to implement a
isPositiveInt: Read[Int] directly. It makes it hard to reuse custom validations.
When working on recursive types, you need to be extra careful with the json API, and use lazyRead to avoid stack overflow.
The new API is lazy by default, you don't have to use a "special" method anymore:
The new API also support the "type" notation with recursive types, while the json API didn't
The new API built-in number validations are a bit "stricter" than their equivalents of the json API. For example if you use the json validation for
Int, and use it on
JsNumber(3.14), it will succeed, but truncate the value to 3. The new validation would reject the same value.
There's a bunch of other differences, must of them are just implementation details improvements. For example the
OWritestrait isn't needed anymore. But those changes have no impact on your code.
Playing with the new API
The API will be released with Play 2.3. You can already play with it, all you have to do is to checkout my branch:
git clone https://github.com/jto/Play20.git && git checkout new_validation_api and build it.
Pull request is here, feedbacks and contributions are welcome.