2
votes

I have a controller defined like this:

  def registerCompany = Action.async(BodyParsers.parse.json) { request =>
    request.body.validate[Company].fold(
      errors => Future {
        BadRequest(errors.mkString)
      },
      company => Future {
        registrationService.registerCompany
        Ok("saved")
      }
    )
  }

Company is a simple case class

case class Company(name: String, address: Address, adminUser: Option[User] = None,
                   venues: Option[Set[Venue]] = None, _id: Option[Long]) {
}

so that I can take advantage of

implicit val companyFormatter = Json.format[Company]

So far so good, but now I want to have validation in the Company class. I've been googling a bit and the best I found was this:

http://koff.io/posts/292173-validation-in-scala/

So many solutions, yet, I'm not happy with any of them. Most of these solutions have known limitations or are a bit messy. I'd like to have declarative validation (annotation based), as that means I write less code and it looks cleaner.

I could mix java with scala and use JSR-303, but it doesn't work for case classes and I don't want to implement Reads and Writes for simple objects.

This is the closest I could find to what I want, but it doesn't support NotNull: https://github.com/bean-validation-scala/bean-validation-scala

Seems a bit like a luxury problem, with so many different solutions, but the truth is that in Java I can get the best of both worlds.

Is there anything else that I could use? Or any work around to the possibilities I'm listing here that could allow me to use both annotation based validation and case classes?

2
On the other hand, one could argue that a solution based on implicits like Accord or io.underscore.validation is in fact much cleaner, as it keeps the case classes free of any annotations and also gives more flexibility in using multiple validators in different situations... any thoughts?redwulf

2 Answers

0
votes

It all depends on what you mean by validation.

You gave an example of a validate Json in request body and I think you would like validate Json request (with Company constraints) but not case class Company (although the topic is called "Scala case class validation"). So you need use Validation with Reads

For example you may use:

  1. ( __ \ "name").read[String] for Required constraint.
  2. ( __ \ "_id").read[Long](min(0) keepAnd max(150)) for 0 < x < 150 constraint.

You may implement own Reads[Company] and Writes[Company] but not macros Json.format[Company]

Update for comment "The difference is that you rarely need to write a custom deserializer, as jackson knows how to handle any object"

If you don't want implement own deserialization and want use format macros, you may implement Company validation, but still using Reads:

val f(company: Company): Boolean = {... company constraints to boolean ...}

request.body.validate[Company](
  companyFormatter.filter(ValidationError("Company validation error"))(f)
)

but this constraints will be applied after full Company deserialization.

Don't even know which is better: universal deserialization and "post" constraints or own deserialization and "pre" constraints. But both are working.

0
votes

You could also roll your own, something like this: https://gist.github.com/eirirlar/b40bd07a71044d3776bc069f210798c6

It will validate both case classes and incomplete case classes (hlists of options of types tagged with keys, where types and keys match the case class).

It won't give you annotations, but you're free to declare different validators for different contexts.