How to validate optional query parameters in Play Framework?

play framework query parameters
play framework routes scala
play framework multiple routes files
play framework redirect
play framework optional path parameter
play framework routes multiple parameters
scala play default route
play api scala

So I have a routes file that looks something like this:

GET     /myRes                 controllers.MyController.get(ids: Option[String], elems: Option[String])

All well and good. Users can get stuff by doing:

/myRes
/myRes?ids=X
/myRes?elems=Y
/myRes?ids=X&elems=Y

However, they can also query the interface by doing:

/myRes?id=X

Which is problematic, because in this case the user is gets the same result as if they had queried /myRes, which is almost certainly not the result they expected. This has been causing a lot of confusion/bugs for developers of the API. Is there an elegant way to catch incorrect/unspecified query parameters being passed to the controller and return a hard error for such queries?

Edit: Changed title to something more descriptive. My problem is basically validating the query parameters to catch any query parameters passed to the API which are not valid/correct.

It can be done with the help of a macro annotation like the following one:

import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import play.api.mvc._

@compileTimeOnly("respond 400 bad request in case of unexpected params")
class StrictParams extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro StrictParamsMacro.impl
}

object StrictParamsMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._
    annottees.map(_.tree).toList match {
      case q"def $name(..$params) = $action { ..$body }" :: Nil =>
        val supportedParamNames = params.map(ab => ab.name.toString).toSet
        c.Expr[Any](
          q"""def $name(..$params) = { req: Request[_] =>
            val unsupportedParams = req.queryString.keySet -- $supportedParamNames
            if (unsupportedParams.nonEmpty) {
              BadRequest(unsupportedParams.mkString("Unsupported Params: ", ", ", ""))
            } else {
              $body
            }
          }"""
        )
    }
  }
}

Then you can annotate your action method like this:

@StrictParams
def get(ids: Option[String], elems: Option[String]) = Action {
  ...
}

Scala Routing - 2.8.x, the request path (e.g. /clients/1542 , /photos/list ), including the query string; the HTTP Play's default routes generator creates a router class that accepts controller It is your responsibility to validate the raw URI segment as you would for any  Validating HTTP data with Play. Validations ensure that the data has certain values or meets specific requirements. You can use validation to verify that your models are correct before saving them to the database, or use them directly on HTTP parameters to validate a simple form.

i usually pass it like this on get method

GET    /getSomething        Controllers.Application.getData()

GET    /getSomething/:id         Controllers.Application.getData(id:Integer)

GET    /getSomething/:id/:name          Controllers.Application.getData(id:Integer, name :String)

Scala Routing - 2.5.7, the request path (e.g. /clients/1542 , /photos/list ), including the query string The default is the dependency injected router, and that is also the case in the Play It is your responsibility to validate the raw URI segment as you would for any  For parameters of type String, the parameter type is optional. If you want Play to transform the incoming parameter into a specific Scala type, you can add an explicit type: GET /clients/:id controllers.Clients.show(id: Long) Then use the same type for the corresponding action method parameter in the controller:

You can define a QueryStringBindable[A] to bind a Map of query string parameters to an instance of type A. See the corresponding documentation. Didn't fully explore it yet, but it doesn't seem too difficult to implement a QueryStringBindable[Option[A]].

If you want to forbid confusing parameters (e.g. allowing ids but not id), you can check for unexpected keys in the parameters Map and return an error message if needed (although I would recommand to accept the id key to match the behaviour expected by users).

Request Binders - 2.8.x, A simple example of the binder's use binding the :from and :to query string parameters: @Override public Optional<AgeRange> bind(String key, Map<String​,  You can define a QueryStringBindable[A] to bind a Map of query string parameters to an instance of type A. See the corresponding documentation . Didn't fully explore it yet, but it doesn't seem too difficult to implement a QueryStringBindable[Option[A]] .

Java Routing - 2.8.x, The router is the component that translates each incoming HTTP request to an action It is your responsibility to validate the raw URI segment as you would for any user input. For parameters of type String , the parameter type is optional. For parameters of type String, typing the parameter is optional. If you want Play to transform the incoming parameter into a specific Scala type, you can explicitly type the parameter: If you want Play to transform the incoming parameter into a specific Scala type, you can explicitly type the parameter:

Java Routing - 2.3.x, The router is the component that translates each incoming HTTP request to an action call The HTTP method can be any of the valid methods supported by HTTP ( GET , POST For parameters of type String , the parameter type is optional. This approach will work nicely even if we have more than one action parameters with multiple different attributes, and also works with custom validation attributes implemented by us. The action I wanted to use this with looked like this, where one of the parameters come from the query string, and the other from the body.

validation - 1.4.x, Each request has it own Validation object which collects errors. Add validation annotations to the controller method's parameter declarations. when Play generates errors, it uses default conventions that follow the Java variables' names. This quick and simple guide will walk you through the steps to validate JAX-RS query parameters using filters, including their creation and enforcement. Join the DZone community and get the full member experience.

Comments
  • Very interesting. Will try this ASAP.
  • Hmm, doesn't seem to work. When we try this, we get a "[error] /*/app/controllers/MyController.scala:88: respond 400 bad request in case of unexpected params" in the compile step. Are we missing something?
  • Macro paradise is required to compile. Add the following setting to your projects - both the macro lib and projects using it. addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
  • Thanks for your time. Still doesn't work unfortunately; getting an exception during macro expansion. Thinking this might be because we are also using Swagger annotations, but even commenting out those results in further issues with the compilation. Will have to try and dig a bit into the generated code and see if we can figure out what is wrong - alternatively just do it without the macro, I guess.
  • Multiple errors. With the swagger annotations removed, though, the relevant one is probable noting in the routes file: "Cannot use a method returning play.api.mvc.Request[_] => Product with java.io.Serializable as a Handler for requests".
  • Thanks for the reply. However, that's essentially the same as setting required parameters. That works for query parameters as well, but not is not what I'm trying to do. None of the query parameters are required, and I do not want to specify every possible combination of query parameters - that is not really scalable beyond 2-3 params.
  • I changed the title to hopefully make the question clearer.