scalaplayframeworkplayframework-2.2playframework-2.3querystringparameter

Play Framework cannot find QueryStringBinders


I have a case class in the model package called CoordinatesTranslationDTO:

case class CoordinatesTranslationDTO(locale: String, lat: BigDecimal, lng: BigDecimal)

I'm importing this in the QueryStringBinders controller like so:

import models.CoordinatesTranslationDTO

My implementation of the object in the QueryStringBinders controller looks like this:

object QueryStringBinders {
  implicit def coordinatesTranslationStringBinder(
                                                   implicit bigDecimalBinder: QueryStringBindable[BigDecimal],
                                                   stringBinder: QueryStringBindable[String]
                                                 ): QueryStringBindable[CoordinatesTranslationDTO] =
    new QueryStringBindable[CoordinatesTranslationDTO] {
      private def subBind[T](key: String, subkey: String, params: Map[String, Seq[String]])
                            (implicit b: QueryStringBindable[T]): Either.RightProjection[String, Option[T]] = {
        b.bind(s"$key.$subkey", params).map(_.right.map(r => Option(r))).getOrElse(Right(None)).right
      }

      override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, CoordinatesTranslationDTO]] = Some {
        def bnd[T](s: String)(implicit b: QueryStringBindable[T]) = subBind[T](key, s, params)

        for {
          locale <- bnd[String]("locale")
          lat <- bnd[BigDecimal]("lat")
          lng <- bnd[BigDecimal]("lng")
        } yield CoordinatesTranslationDTO(locale, lat, lng)
      }

      override def unbind(key: String, coordinates: CoordinatesTranslationDTO): String = {
        def ubnd[T](key: String, s: Option[T])(implicit b: QueryStringBindable[T]) = s.map(f => b.unbind(key, f))

        val keys = Seq(
          ubnd("lat", coordinates.lat),
          ubnd("lng", coordinates.lng),
          ubnd("locale", coordinates.locale)
        ).flatten
        keys.mkString("&")
      }
    }
}

And my route in the routes file looks like this:

GET           /people/translation                                                          controllers.PeopleController.getOrCreatePersonLocation(p: models.CoordinatesTranslationDTO)

I already run sbt clean, set cleanFiles and God knows how many commands more. But nothing seems to work. All I get is:

[error] /Users/developmentuser/Desktop/Jobs/parent/solar/conf/routes:269:1: No QueryString binder found for type models.CoordinatesTranslationDTO. Try to implement an implicit QueryStringBindable for this type

Solution

  • I think the problem is actually that Play can't find an implicit QueryStringBindable[BigDecimal], which is required for your coordinatesTranslationStringBinder.

    The error message is misleading. It is generated by the following code in Play, which cannot distinguish between either i) QueryStringBindable[A] not being implemented at all, or ii) there is an implementation of QueryStringBindable[A] but its implicit requirements cannot be met:

      // mvc/Binders.scala
    @implicitNotFound(
      "No QueryString binder found for type ${A}. Try to implement an implicit QueryStringBindable for this type."
    )
    trait QueryStringBindable[A] {
    

    I had the same problem and had to figure out what was going on by reading the source closely then narrow it down by calling for each of my implicitly provided sub-binders one by one until I found the culprit. Too much magic!

    (You can implement a binder for BigDecimal using the Parsing helper class from mvc/Binders.scala.)

    I can't see how Play can easily fix this error message. Perhaps they could just add "(or its implicit dependencies cannot be resolved)" to this message.