Skip to content

Instantly share code, notes, and snippets.

@jeroenr
Last active August 30, 2016 08:53
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeroenr/8956587 to your computer and use it in GitHub Desktop.
Save jeroenr/8956587 to your computer and use it in GitHub Desktop.
Parameter validation with Play 2 framework
object ApplicationBuild extends Build {
val appName = "my-app"
val appVersion = "1-SNAPSHOT"
val appDependencies = Seq(
jdbc,
cache
)
val main = play.Project(appName, appVersion, appDependencies)
.settings(
routesImport += "binders._",
)
}
package binders
import play.api.mvc.QueryStringBindable
import util.{Try, Success, Failure}
import services.ParamValidator
import play.api.data.validation._
import play.api.i18n.Messages
case class Pager(offset: Int, size: Int)
object Pager {
val NUM = "num"
val SIZE = "size"
val DEFAULT_NUM = 1
val DEFAULT_SIZE = 30
val CONSTRAINTS = Seq(ParamValidator.MIN_0, Constraints.max(50000000))
implicit def queryStringBinder(implicit intBinder: QueryStringBindable[Int]) = new QueryStringBindable[Pager] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Pager]] = {
val pagingKeys = Seq(s"$key.$NUM", s"$key.$SIZE")
val pagingParams = pagingKeys.filter(params.keys.toSeq.contains(_))
val result = for {
num <- Try(intBinder.bind(pagingKeys(0), params).get).recover {
case e => Right(DEFAULT_NUM)
}
size <- Try(intBinder.bind(pagingKeys(1), params).get).recover {
case e => Right(DEFAULT_SIZE)
}
} yield {
(num.right.toOption, size.right.toOption)
}
result match {
case Success((maybeNum, maybeSize)) =>
ParamValidator(CONSTRAINTS, maybeNum, maybeSize) match {
case Valid =>
Some(Right(Pager(maybeNum.get - 1, maybeSize.get)))
case Invalid(errors) =>
Some(Left(errors.zip(pagingParams).map {
case (ValidationError(message, v), param) => Messages(message, param, v)
}.mkString(", ")))
}
case Failure(e) => Some(Left(s"Invalid paging params: ${e.getMessage}"))
}
}
override def unbind(key: String, pager: Pager): String = {
intBinder.unbind(s"$key.$NUM", pager.offset + 1) + "&" + intBinder.unbind(s"$key.$SIZE", pager.size)
}
}
}
package services
import play.api.data.validation.{Constraint, Invalid, Valid, Constraints}
/**
* Created by jeroen on 2/7/14.
*/
object ParamValidator {
val MIN_0 = Constraints.min(0)
def apply[T](constraints: Iterable[Constraint[T]], optionalParam: Option[T]*) =
optionalParam.flatMap { _.map { param =>
constraints flatMap {
_(param) match {
case i:Invalid => Some(i)
case _ => None
}
}
}
}.flatten match {
case Nil => Valid
case invalids => invalids.reduceLeft {
(a,b) => a ++ b
}
}
}
GET /api/users controllers.UserController.list(page: Pager)
@jeroenr
Copy link
Author

jeroenr commented Feb 17, 2014

Haha great :). I made a pull request for this stuff here playframework/playframework#2377. Would be nice if they merge it in in some way.

@jeroenr
Copy link
Author

jeroenr commented Feb 18, 2014

Added sample of using the validator in a custom query string binder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment