Samuel Gomez
Samuel Gomez

Reputation: 23

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

Upvotes: 0

Views: 298

Answers (2)

Rich
Rich

Reputation: 15464

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.

Upvotes: 0

Yevgen
Yevgen

Reputation: 4729

At least one thing that you forgot is at add parameter in your routes file. The :p param in your route. It should be same as one that you pass to your getOrCreatePersonLocation function.

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

Upvotes: 0

Related Questions