akka http case class validation directive

If you work with Akka HTTP for a while, you should definitely know that it has multiple ways for model validation. I talk about http request body validation. Probably in 99% of cases you would like to ensure that user send something meaningful to the server. So exactly for this purpose Akka HTTP provides validation mechanisms. But what if you want to send back to the client side information about all invalid fields?

Overview of existing validators

When I first time started looking for handy validation mechanisms which Akka HTTP has, I pretty fast found the validate directive. Of course you know it well:

def validate(check: => Boolean, errorMsg: String): Directive0

It’s extremely straightforward and pragmatic. You pass check argument and errorMsg, if check is false, the directive generates ValidationRejection with appropriate error message.

Not bad, not bad. But it’s hard to apply this approach for case class validation, when you want to receive back entire list of errors for each invalid field.

What else Akka HTTP has for a validation? Right! A case class extraction in combination with require method as validator.

I was so happy to see this code snippet:

case class Color(name: String, red: Int, green: Int, blue: Int) {
  require(!name.isEmpty, "color name must not be empty")
  require(0 <= red && red <= 255, "red color component must be between 0 and 255")
  require(0 <= green && green <= 255, "green color component must be between 0 and 255")
  require(0 <= blue && blue <= 255, "blue color component must be between 0 and 255")
}

Until I understood that in case of a “require” statement failure it would immediately send ValidationRejection to the client with the recent error, instead of:

[
  {"fieldName": "red", "errorMessage": "red color component must be between 0 and 255"},
  {"fieldName": "name", "errorMessage": "color name must not be empty"}
]

Hell. In some moment of time I started think that it’s just impossible to achieve what I want with native Akka HTTP. StackOverflow, blogs and finally akka gitter channel… By the way, thanks to admins of akka gitter, they suggested me one way to do what I want with help of ScalaTest checkers or something like this.

Sorry, dear hakkers, but finally I decided to write my own solution. I wanted something really compact, simple, understandable, extensible (and the rest of characteristics which make opens source libs outstanding).
Unfortunately, I’m not so talented programmer, so we have what we have 🙂

mini-nano-micro directive for case class validation

Do you want to dive into source code? Check out the github repo. Example of the directive usage is in the test suite.

So how I see validation of case classes in Akka HTTP context? Let’s start from preconditions.

Firstly we need to define some validation rules for a case class. These rules may be reused later and composed with another one rules. For this reason we can use such line of code:

final case class FieldRule[-M](fieldName: String, isInvalid: M => Boolean, errorMsg: String)

Then we want to be dynamic enough in order to avoid a boilerplate code. Java reflection can help us with this challenge:

def caseClassFields[M <: Any](obj: AnyRef): Seq[(String, M)] = {
  val metaClass = obj.getClass
  metaClass.getDeclaredFields.map {
    field => {
      field.setAccessible(true)
      (field.getName, field.get(obj).asInstanceOf[M])
    }
  }
}

Now we can create a validation directive:

final case class FieldErrorInfo(name: String, error: String)
final case class ModelValidationRejection(invalidFields: Set[FieldErrorInfo]) extends Rejection

implicit val validatedFieldFormat = jsonFormat2(FieldErrorInfo)

def validateModel[T, M <: Any](model: T, rules: FieldRule[M]*): Directive1[T] = {
  import scala.collection.mutable.Set
  val errorsSet: Set[FieldErrorInfo] = Set[FieldErrorInfo]()
  val keyValuePairs: Seq[(String, M)] = caseClassFields(model.asInstanceOf[AnyRef])
  Try {
    rules.map { rule =>
      keyValuePairs.find(_._1 == rule.fieldName) match {
        case None => throw new IllegalArgumentException(s"No such field for validation: ${rule.fieldName}")
        case Some(pair) => {
          if (rule.isInvalid(pair._2)) errorsSet += FieldErrorInfo(rule.fieldName, rule.errorMsg)
        }
      }
    }
    errorsSet.toSet[FieldErrorInfo]
  } match {
    case Success(set) => if (set.isEmpty) provide(model) else reject(ModelValidationRejection(set))
    case Failure(ex) => reject(ValidationRejection(ex.getMessage))
  }
}

All together, without imports it takes ~ 40 lines of code. I’m not sure that this solution is optimal enough, that it’s elegant and all of Scala features are used as needed. But I hope to hear what can be done better, what can be improved, and how the code can be more universal.

My nearest 2 goals are to unbound spray-json dependency (make this directive open for any serialization provider) and implement a way to pass validation rules not only one by one, but in a Seq form as well.

Summary

Of course you can take a look at source code. I’ll be happy to read your impression about this directory in comments. Any suggestions are welcome 🙂

About The Author

Mathematician, programmer, wrestler, last action hero... Java / Scala architect, trainer, entrepreneur, author of this blog

Close