I'm trying to create a generic HTTP client in Scala using spray. Here is the class definition:
object HttpClient extends HttpClient
class HttpClient {
implicit val system = ActorSystem("api-spray-client")
import system.dispatcher
val log = Logging(system, getClass)
def httpSaveGeneric[T1:Marshaller,T2:Unmarshaller](uri: String, model: T1, username: String, password: String): Future[T2] = {
val pipeline: HttpRequest => Future[T2] = logRequest(log) ~> sendReceive ~> logResponse(log) ~> unmarshal[T2]
pipeline(Post(uri, model))
}
val genericResult = httpSaveGeneric[Space,Either[Failure,Success]](
"http://", Space("123", IdName("456", "parent"), "my name", "short_name", Updated("", 0)), "user", "password")
}
An object utils.AllJsonFormats
has the following declaration. It contains all the model formats. The same class is used on the "other end" i.e. I also wrote the API and used the same formatters there with spray-can and spray-json.
object AllJsonFormats
extends DefaultJsonProtocol with SprayJsonSupport with MetaMarshallers with MetaToResponseMarshallers with NullOptions {
Of course that object has definitions for the serialization of the models.api.Space, models.api.Failure and models.api.Success.
The Space
type seems fine, i.e. when I tell the generic method that it will be receiving and returning a Space
, no errors. But once I bring an Either into the method call, I get the following compiler error:
could not find implicit value for evidence parameter of type spray.httpx.unmarshalling.Unmarshaller[Either[models.api.Failure,models.api.Success]].
My expectation was that the either implicit in spray.json.DefaultJsonProtocol, i.e. in spray.json.StandardFormts, would have me covered.
The following is my HttpClient class trying it's best to be generic: Update: Clearer/Repeatable Code Sample
object TestHttpFormats
extends DefaultJsonProtocol {
// space formats
implicit val idNameFormat = jsonFormat2(IdName)
implicit val updatedByFormat = jsonFormat2(Updated)
implicit val spaceFormat = jsonFormat17(Space)
// either formats
implicit val successFormat = jsonFormat1(Success)
implicit val failureFormat = jsonFormat2(Failure)
}
object TestHttpClient
extends SprayJsonSupport {
import TestHttpFormats._
import DefaultJsonProtocol.{eitherFormat => _, _ }
val genericResult = HttpClient.httpSaveGeneric[Space,Either[Failure,Success]](
"https://api.com/space", Space("123", IdName("456", "parent"), "my name", "short_name", Updated("", 0)), "user", "password")
}
With the above, the problem still occurs where the unmarshaller is unresolved. Help would be greatly appreciated..
Thanks.
Spray defines a default marshaller for
Either[A,B]
if aMarshaller[A]
andMarshaller[B]
are in defined scope inside theMetaMarshallers
trait. But, going the other direction requires anUnmarshaller
. You will need to define an in-scopeUnmarshaller
forEither[Failure, Success]
. This cannot be coded without specific knowledge of the expected response and what the strategy will be for choosing whether to unmarshall a response as aLeft
or as aRight
. For example, let's say you want to return a Failure on a non-200 response and a Success from a 200 json response body:Update
Looking deeper into this, the problem appears to be that the default
eitherFormat
inspray.json.StandardFormats
is not aRootJsonFormat
, which is required by the default JSON unmarshaller defined inspray.httpx.SprayJsonSupport
. Defining the following implicit method should solve the issue:I have an working example gist that hopefully explains how you would use this. https://gist.github.com/mikemckibben/fad4328de85a79a06bf3