Play est un framework permettant de démarrer et développer rapidement des applications Web. Ce dernier offre un large éventail d’outils. Parmi eux, nous retrouvons la validation de règles métiers.
Dans cet article, nous vous proposons de mettre en place le mécanisme de validation de Json avec Play / Scala.
Application
Nous allons utiliser une application offrant une API de gestion d’utilisateurs, et plus précisément l’ajout d’un utilisateur.
Voici la définition d’un utilisateur dans notre application :
case class User(id: Option[Long], email: String, firstName: String, lastName: String, dateAccess:LocalDate)
Dans la configuration de Play nous définissons la route suivante :
POST /users controllers.MainController.newUser
Ainsi que la ressource associée dans notre Controller :
import play.api.mvc.Controller import play.api.libs.json._ import service.UsersService import model.User import play.api.libs.functional.syntax._ import play.api.libs.json.Reads._ import play.api.data.validation.ValidationError object MainController extends Controller { def newUser = DBAction(parse.json) { implicit rs => rs.request.body.validate[User].map { case user => UsersService save User(None, user.email, user.firstName, user.lastName, user.dateAccess) Created("User Created") }.recoverTotal { e => NotFound("Detected error:" + JsError.toFlatJson(e)) } } }
Les connaisseurs remarquerons que nous utilisons la bibliothèque Slick pour la persistance de nos données. Nous en parlerons dans un prochain billet.
La ligne qui nous intéresse est la suivante :
rs.request.body.validate[User].map
La méthode validate permet d’appliquer des règles de validation sur notre objet Json. Mais comment les définir ?
Validation
En regardant la signature de cette méthode, nous voyons :
def validate[T](implicit rds: Reads[T]): JsResult[T]
Cela signifie que nous devons définir un implicit pour notre User. Une manière simple de le déclarer est la suivante :
implicit val userRead = Json.reads[User]
Or nous remarquons qu’aucune règle de validation n’est présente. Nous devons enrichir notre implicit :
implicit val userRead: Reads[User] = {( (__ \ "id").readNullable[Long] and (__ \ "email").read(email keepAnd minLength[String](5)) and (__ \ "firstName").read(minLength[String](2) andKeep maxLength[String](30)) and (__ \ "lastName").read[String] and (__ \ "dateAccess").read(jodaLocalDateReads("yyyyMMdd")) )(User) }
La syntaxe se définit sous forme de path Json. La syntaxe __ (2 underscores) est un alias pour JsPath.
Regardons plus en détail la validation de l’email : email keepAnd minLength[String](5)
- email : valide que le champ contient une chaîne de caractères et le format du mail
- minLength : un minimum de 5 caractères est requis
- keepAnd : permet de composer les validateurs et de retourner le type Read[String] dans notre cas
Ainsi lors de l’appel à notre ressource avec le Json suivant :
{ "email": "email1@gmail.com", "firstName": "firstName1", "lastName": "lastName1", "dateAccess": "03-03-2014" }
Nous aurons l’erreur de validation suivante :
Detected error:{"obj.dateAccess":[{"msg":"error.expected.jodadate.format","args":["yyyyMMdd"]}]}
Définir son validateur
Il est bien entendu possible de définir son propre validateur. Nous allons ajouter un validateur sur le champ lastName :
def lastNameReads(implicit r: Reads[String]): Reads[String] = r.filter(ValidationError("error.lastName.numbers"))(_.matches("\S+\d{4}$"))
Cela signifie que ce champ doit se terminer par 4 chiffres, auquel cas le message d’erreur "error.lastName.numbers" sera renvoyé.
Nous n’avons plus qu’à appeler cette fonction dans notre implicit:
implicit val userRead: Reads[User] = {( (__ \ "id").readNullable[Long] and (__ \ "email").read(email keepAnd minLength[String](5)) and (__ \ "firstName").read(minLength[String](2) andKeep maxLength[String](30)) and (__ \ "lastName").read(lastNameReads) and (__ \ "dateAccess").read(jodaLocalDateReads("yyyyMMdd")) )(User) }
A travers cet article, nous avons vu la simplicité que Play / Scala nous offre pour valider du Json. Enjoy !
Lien utile : http://www.playframework.com/documentation/2.2.2/ScalaJsonCombinators