hseeberger / akka-http-json

Integrate some of the best JSON libs in Scala with Akka HTTP

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

possible to unmarshal generic subtypes?

evbo opened this issue · comments

commented

Hi,

Is it possible to unmarshall generic types? Here I've reproduced an example that fails to compile due to:

Error:(28, 88) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.Unmarshaller[akka.http.scaladsl.model.HttpResponse,Requests.this.SomeResponse[T]]
val unmarshalled: Future[SomeResponse[T]] = response.flatMap(Unmarshal(_).to[SomeResponse[T]])

full REPL reproduction below. See comment:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model.{HttpMethods, HttpRequest, HttpResponse, RequestEntity}
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport
import io.circe.generic.auto._

import scala.concurrent.duration._
import scala.concurrent.{Await, Future}


trait Requests extends FailFastCirceSupport {

  implicit val system = ActorSystem("testSystem")
  implicit val ec = system.dispatcher
  implicit val mat = ActorMaterializer()

  case class SomeRequest(query: String)
  case class SomeData[T](queryResponse: T)
  case class SomeResponse[T](data: SomeData[T])
  case class SampleData(hello: String)


  def parseContent[T <: SampleData](query: String) = {
    val response: Future[HttpResponse] = requestContent(SomeRequest(query))

// See Comment:
// This line wont compile unless I replace T with an explicit type like SampleData. 
// Why? Is it be possible to use T somehow?
    val unmarshalled: Future[SomeResponse[T]] = response.flatMap(Unmarshal(_).to[SomeResponse[T]])

    val hello: Future[String] = for {
      root <- unmarshalled
    } yield root.data.queryResponse.hello

    Await.result[String](hello, 30 seconds)
  }

  def requestContent(request: SomeRequest): Future[HttpResponse] = {
    for {
      request <- Marshal(request).to[RequestEntity]
      response <- Http().singleRequest(HttpRequest(
        method = HttpMethods.POST,
        uri = "http://localhost:8000/Some",
        entity = request
      ))
    } yield response
  }
}

object OtherRequests extends Requests {
  val query =
    """
        query GetSomething {
          results: getSomething() {
            hello
          }
        }
      """.replaceAll("//\\d+", "")

  def getSome() = {
    parseContent[SampleData](query)
  }
}

OtherRequests.getSome()

Have you tried moving the FailFastCirceSupport so that it would not affect the place of your problem.

I struggled with similar today:

package test

import akka.http.scaladsl.model.{ContentTypes, StatusCodes}
import akka.http.scaladsl.testkit.ScalatestRouteTest
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{FlatSpec, Matchers}
import renamed.data.Fake

class RouteTest extends FlatSpec with Matchers with ScalatestRouteTest /*with FailFastCirceSupport*/ with ScalaFutures {
  import renamed.fake.AppRoute.route
  import io.circe.generic.auto._

  behavior of "GET /ping"

  it should "return 'pong'" in {
    Get("/ping") ~> route ~> check {
      status shouldBe StatusCodes.OK
      contentType shouldBe ContentTypes.`text/plain(UTF-8)`

      entityAs[String] shouldBe "pong"
    }
  }

  behavior of "GET /mock"

  // Note: Keep this away from code needing 'entityAs[String]' - it causes them not to compile.
  //
  import FailFastCirceSupport._

  it should "provide a valid 'Fake' object as JSON" in {
    Get("/mock") ~> route ~> check {
      status shouldBe StatusCodes.OK
      contentType shouldBe ContentTypes.`application/json`

      val o: Fake = entityAs[Fake]    // decode
      info(o.toString)
    }
  }
}

Bringing FailFastCirceSupport in - while using akka.http.scaladsl.testkit.ScalatestRouteTest - seems to shadow the unmarshalling support needed to make EntityAs[String] work.

It's not a big thing, but debugging this kind of things does take time.

Agreed with @akauppi this was and remains a pain for me. It would be really nice if there was a better solution than what @akauppi suggests.