Search code examples
formsscalavalidationplayframeworkplay-json

how to apply Form validators on object


I have an API in Scala. When I want to create a new User, i need validators on some fields (UserName, FirstName, LastName). I can't find a solution to apply the Form on my case class User. I thought using (User.apply) this will override automatically my User class, but this isn't happening.

User.scala

 case class User(
              id: Int = 0,
              userName: String,
              firstName: String,
              lastName: String
            )

object User {
  import play.api.libs.json._

  implicit val jsonWrites = Json.writes[User]

  implicit val userReads = Json.reads[User]

  def tupled = (User.apply _).tupled
}

// Form validator for post requests
object UserForm {
  val form: Form[User] = Form(
    mapping(
      "id" -> number,
      "userName" -> text(minLength = 5),
      "firstName" -> text(minLength = 5),
      "lastName" -> nonEmptyText
    )(User.apply)(User.unapply)
  )
}

UsersController.scala

def create = Action(parse.json) { implicit request => {
    val userFromJson = Json.fromJson[User](request.body)
    userFromJson match {
      case JsSuccess(user: User, path: JsPath) => {
        var createdUser = Await.result(usersRepository.create(user), Duration.Inf)
        Ok(Json.toJson(createdUser))
      }
      case error @ JsError(_) => {
        println(error)
        InternalServerError(Json.toJson("Can not create user"))
      }
      }
    }
  }

UsersRepository.scala

    def create(user: User): Future[User] = {
      val insertQuery = dbUsers returning dbUsers.map(_.id) into ((x, id) => x.copy(id = id))
      val query = insertQuery += user
      db.run(query)
    }

(UsersRepository doesn't matter so much in this problem)

So, when I read the json, I think there need to apply the form validators

val userFromJson = Json.fromJson[User](request.body)

but nothing happening. Another problem is if I send from client a null parameter, will get an error, can not read and create an User.

For example :

"firstName": "" - empty userName will pass

"firstName": null - will fail

This depends on how is configured the User class in client (Angular), with the field null or empty in constructor. I prefer to set it null, even if is a string, if is not a required field (better NULL value in database, than an empty cell)


Solution

  • Find a solution, my Form need to be part of object User, and not to be another object.

      object User {
      import play.api.libs.json._
    
      implicit val jsonWrites = Json.writes[User]    
      implicit val userReads = Json.reads[User]
    
      val form: Form[User] = Form(
        mapping(
          "id" -> number,
          "userName" -> text(minLength = 2),
          "firstName" -> text(minLength = 4),
          "lastName" -> nonEmptyText
        )(User.apply)(User.unapply)
      )
    
      def tupled = (User.apply _).tupled
    }
    

    and the method in Controller became :

    def create = Action(parse.json) { implicit request => {
        User.form.bindFromRequest.fold(
          formWithErrors => BadRequest(Json.toJson("Some form fields are not validated.")),
          success => {
            val userFromJson = Json.fromJson[User](request.body)
            userFromJson match {
              case JsSuccess(user: User, path: JsPath) => {
                val createdUser = Await.result(usersRepository.create(user), Duration.Inf)
                Ok(Json.toJson(createdUser))
              }
              case error@JsError(_) => {
                println(error)
                InternalServerError(Json.toJson("Can not create user"))
              }
            }
          }
        )
       }
      }
    

    I know, I need to format my code ... using IntelliJ is so annoying after Visual Studio :(