# Request Validation with Akka-HTTP and Cats [Greg Beech](https://www.gregbeech.com/) wrote an article on [Akka HTTP Entity Validation](https://www.gregbeech.com/2018/08/12/akka-http-entity-validation/). This post is on the same topic, except we are going to use typeclass instances, as opposed to subtyping, for form validations. ### Sample Problem: User Regsitration To start with, let us suppose that we are working on some sort of user management service using Scala/Akka. However, here we are only going to focus on the registration part, in which the client is expected to send a user data, in JSON format; and the server needs to validate them, and save the user if the validations succeed, or reject the request with _all_ the errors detected. That said, we should keep in mind that our validations may be used through out the entire codebase, not just in the user registration endpoint. The rules for our validation are as follows: 1. All fields are required. 1. Each field must have the correct type. 1. All strings must be non-empty. 1. Classic password validations (which we are not going to emphasize much). 1. A user should have at least one hobby (see #1). 1. Only people of ages 18 or above are allowed to register (it's not for kids). Note that in a real-world project, registration validations are usually more complicated than this list, but for our current purposes, this should do. ### Data Model and Route Implementation Our user model has the usual attributes present in most registration forms: ```scala final case class User( username: String, password: String, firstName: String, lastName: String, age: Int, hobbies: Vector[String]) ``` In order to parse the JSON data and convert it into a `User` instance and vice-versa, we need to tell Akka-HTTP how to (un)marshall between JSON and `User`: ```scala object RegistrationJsonSupport extends SprayJsonSupport with DefaultJsonProtocol { implicit val userFormat = jsonFormat6(User) // ... more formats here ... } ``` Then we bring that into the scope and use it in our route: ```scala import RegistrationJsonSupport._ val route = post { entity(as[User]) { user => // For simplicity, let us agree that this operation never fails. saveUser(user) complete(s"User ${user.username} has been successfully registered.") } } ``` This has added advantages: Akka-HTTP ensures that non-optional fields are provided by the client, and each field will have the correct type as specified in the model. Any requests that do not comply with these structural rules will be rejected. This means our current code already got the first 2 validation rules above covered. ### Fields Validation The next thing we need to do is implement the different validations for the fields. _If you are interested only in the form validations and how to integrate them with Akka-HTTP, you can skip this section._ The remaining validation rules are all about checking for non-emptiness and minimum values, and password validations. We can write our own utilities to generalize some of these things: ```scala trait FieldValidator { trait Required[F] extends (F => Boolean) trait Minimum[F] extends ((F, Int) => Boolean) def required[F](field: F)(implicit req: Required[F]): Boolean = req(field) def minimum[F](field: F, limit: Int)(implicit min: Minimum[F]): Boolean = min(field, limit) implicit val minimumStringLength: Minimum[String] = _.length >= _ implicit val minimumInteger: Minimum[Int] = _ >= _ implicit val requiredString: Required[String] = _.nonEmpty // Warning: I haven't tried compiling this part. implicit val requireVector: Required[Vector[String]] = _.forall(required[String]) } ``` If that looks like a bunch of typeclasses and their instances, that's because it is. I've got two main motivations for suggesting this approach: 1. Type-safety. Having `minimum` and `required` take `Any` values on which to pattern match to determine the specific type of input is not type-safe. 1. Ad-hoc Polymorphism. You do not have to invoke different functions for the same type of validations (`minimumInt`, `minimumString`, etc.). If you want to check for, say, a minimum, you can just call the `minimum` function regardless of the type of field, providing, of course, that the typeclass has an (implicit) instance for that field in scope. Sometimes you might need to specify the type of the field to check (e.g. `mimimum[Int]`, `required[String]`). We can then use these utilities in our field validation functions: ```scala import cats.data.ValidatedNec import cats.implicits._ trait FieldValidator { // previous code goes here... def validateRequired[F: Required](field: F, fieldName: String): ValidationResult[F] = Either.cond( required(field), field, EmptyField(fieldName)).toValidatedNec def validateMinimum[F: Minimum](field: F, fieldName: String, limit: Int): ValidationResult[F] = Either.cond( minimum(field, limit), field, BelowMinimum(fieldName, limit)).toValidatedNec def validatePassword(password: String): ValidationResult[String] = ... // more validations here } ``` In case you didn't know what `[F: Required]` indicates, it's a Scala feature known as [context bounds](https://docs.scala-lang.org/tutorials/FAQ/context-bounds.html). Put simply, the signatures desugar to: ```Scala def validateRequired[F](field: F, fieldName: String)(implicit required: Required[F]): ValidationResult[F] = ??? def validateMinimum[F](field: F, fieldName: String, limit: Int)(implicit min: Minimum[F]): ValidationResult[F] = ??? ``` We use `Either.cond` to handle the results of the validations. `EmptyField` and `BelowMinimum` are case classes representing errors: ```scala sealed trait RequestValidation { def errorMessage: String } object RequestValidation { final case class EmptyField(fieldName: String) extends RequestValidation { override def errorMessage = s"$fieldName is empty" } final case class BelowMinimum(fieldName: String, limit: Int) extends RequestValidation { override def errorMessage = s"$fieldName is below the minimum of $limit" } // ... more error messages here... } ``` Let's go back to our field validation functions. You can see that the resulting `Either`s from the `Either.cond`s call `toValidatedNec`. This method turns an Either into a `ValidatedNec` from Cats. `ValidatedNec` is just like `Either`, but far more useful when accumulating multiple errors. However, if you look at the signature of the functions, the results are actually `VaidationResult`s, a type that we'll talk about in the next section. ### Form Validation Our form validator will try to validate all the fields in a given request. It can produce two possible outcomes: the validated object indicating that the validation went successfully, or an accumulation of errors. For this, we've got two options: use `Either` or Cats's `ValidatedNec`. Cats [documentation for Validated](https://typelevel.org/cats/datatypes/validated.html) recommends to use `Validated` (and consequently, `ValidatedNec`) for the accumulation of errors. In this post, we are going for `ValidatedNec`. Just like `Either`, `ValidatedNec` has left and right projections, with left used to represent errors. We already know the type of errors we want, and we don't want to keep on specifying it, so we'll make a type alias that will fill in `ValidatedNec`'s left projection with `RequestValidation`. And that's where the `ValidationResult` we saw in the previous section comes in: ```scala type ValidationResult[A] = ValidatedNec[RequestValidation, A] ``` Let's also add a `FormValidation` type alias for a function that lifts a form (of any type) to `ValidationResult` (which would allow us to do [point-free style](https://stackoverflow.com/questions/944446/what-is-point-free-style-in-functional-programming) programming later on): ```scala type FormValidation[F] = F => ValidationResult[F] ``` Now that we have the types in place, we can write a generic form validator: ```scala def validateForm[F, A](form: F)(f: ValidationResult[F] => A) (implicit formValidation: FormValidation[F]): A = f(formValidation(form)) ``` I forgot to tell you that `FormValidation` can be treated as a typeclass. And that means we can apply `validateForm` to any `form` of type `F` as long as there is a `FormValidation` instance for that form in scope. Here's our form validator, with the typeclass instances: ```scala trait FormValidator { type ValidationResult[A] = ValidatedNec[RequestValidation, A] type FormValidation[F] = F => ValidationResult[F] def validateForm[F, A](form: F)(f: ValidationResult[F] => A) (implicit formValidation: FormValidation[F]): A = f(formValidation(form)) implicit lazy val registrationFormValidation: FormValidation[User] = { case User(username, password, firstName, lastName, address, age) => ( validateRequired(username, "Username"), validatePassword(password, "Password"), validateRequired(firstName, "First Name", validateRequired(lastName, "Last Name"), validateRequired(age, "Age"), validateRequired(hobbies, "Hobbies") validateMinimum(age, "Age", 18))).mapN(User) } // ...other implicit typeclass instances here... implicit lazy val userUpdateFormValidation: FormValidation[User] = ??? implicit lazy val userDeletionFormValidation: FormValdiation[String] = ??? } ``` `mapN` would take our _chain_ of validations and accumulate all the errors found in the process, wrapping them all in the `Invalid` projection (`ValidatedNec`'s equivalent to `Either`'s `Left`). If no errors are found, it will return a `Valid` result (`ValidatedNec`'s equivalent to `Either`'s `Right`) with a user instance as the content. We can then incorporate it into our route: ```scala val route = post { entity(as[User]) { user => validateForm(user) { case Valid(_) => saveUser(user) complete(s"User ${user.username} has been successfully registered.") case Invalid(failures) => complete(internalError( failures.iterator.map(_.errorMessage).mkString("\n"))) } } } ``` The only remaining thing to do is the removal of that extra call to `validateForm`, which adds another layer of indentation, and explicit handling of the validation results. To do that we may have to implement our custom unmashaller that does the validation. The problem is, Greg's custom unmarshaller, implemented using _implicit conversions_, wouldn't work in our case because our validations are not part of the entity. Also we will need to declare implicit parameters for the instances of our `FormValidation` typeclasses. Fortunately, there is another way to circumvent the issue, and that is to add extension methods to the default unmarshaller: ```scala implicit class ValidationRequestMarshaller[A](um: FromRequestUnmarshaller[A]) { def validate(implicit validation: FormValidation[A]) = um.flatMap { _ => _ => entity => validateForm(entity) { case Valid(_) => Future.successful(entity) case Invalid(failures) => Future.failed(new IllegalArgumentException( failures.iterator.map(_.errorMessage).mkString("\n"))) } } } ``` We are using _implicit class_ instead of direct implicit conversion (_implicit def_). This implicit class effectively adds a `validate` method to a `FromRequestUnmarshaller`. Note that `validate` also contains the implicit parameter I mentioned above. Here's the final, much better version of our route: ```scala import validators._ // perhaps a package object that extends both FieldValidator and FormValidator ... val route = post { entity(as[User].validate) { user => saveUser(user) complete(s"User ${user.username} has been successfully registered.") } } ```