scalaakkasprayspray-clientakka-http

Why akka-http Unmarshaler returns Future[T] instead of T?


Since documentation is not ready I'll ask akka maintainers here.

Why akka-http Unmarshaler returns Future[T] instead of T ? Here is my goal. I want to unmarshal class from XML http response similarly how it's done for json. For example I'd like to write

Unmarshal(HttpResponse.entity).to[Person]

where case class and its unmarshaller looks like this

case class Person(name: String, age: Int)

implicit val personUnmarshaller = Unmarshaller[NodeSeq, Person] { _ => xml =>
      Future(Person((xml \\ "name").text, (xml \\ "age").text.toInt))
}

It's not gonna compile with ScalaXmlSupport supplied with 1.0-RC4 because Unmarshaller[ResponseEntity,Person] is not available in the scope. So to trick it I wrote two implicit conversions

implicit def xmlUnmarshallerConverter[T](marsh: Unmarshaller[NodeSeq, T])(implicit mat: Materializer): FromEntityUnmarshaller[T] =
  xmlUnmarshaller(marsh, mat)

implicit def xmlUnmarshaller[T](implicit marsh: Unmarshaller[NodeSeq, T], mat: Materializer): FromEntityUnmarshaller[T] =
  defaultNodeSeqUnmarshaller.map(Unmarshal(_).to[T].value.get.get)

It works but I don't like ugly .value.get.get. Is there more elegant way to implement this ?


Solution

  • Well, I've implemented my own solution for now but I hope Akka team will make sync/async stuff consistent in the library.

    So I created simple clone of Unmarshaller which is defined as follows

    trait SyncUnmarshaller[-A, B] {
      def apply(value: A): B
    }
    
    object SyncUnmarshaller {
      def apply[A, B](f: A => B): SyncUnmarshaller[A, B] =
        new SyncUnmarshaller[A, B] {
          def apply(a: A) = f(a)
        }
    }
    
    object SyncUnmarshal {
      def apply[T](value: T): SyncUnmarshal[T] = new SyncUnmarshal(value)
    }
    
    class SyncUnmarshal[A](val value: A) {
      def to[B](implicit um: SyncUnmarshaller[A, B]): B = um(value)
    }
    

    Therefore unmarshallers for domain classes will be defined like this

    implicit val articleBodyUnmarshaller = SyncUnmarshaller[NodeSeq, ArticleBody] { xml =>
      ArticleBody(xml.toString())
    }
    

    Then there are two implicits for ScalaXmlSupport that I already mentioned

    implicit def xmlUnmarshallerConverter[T](marshaller: SyncUnmarshaller[NodeSeq, T])(implicit mat: Materializer): FromEntityUnmarshaller[T] =
      xmlUnmarshaller(marshaller, mat)
    
    implicit def xmlUnmarshaller[T](implicit marshaller: SyncUnmarshaller[NodeSeq, T], mat: Materializer): FromEntityUnmarshaller[T] = {
      defaultNodeSeqUnmarshaller.map(marshaller(_))
    

    That's it. And finally in case you want to use Akka's calls like

    Unmarshal(response.entity).to[Article].map(Right(_))

    You'd need converter from my SyncUnmarshaller to akka's Unmarshaller

    implicit def syncToAsyncConverter[A, B](marshaller: SyncUnmarshaller[A, B]): Unmarshaller[A, B] =
      new Unmarshaller[A, B] {
        def apply(a: A)(implicit ec: ExecutionContext) =
          try FastFuture.successful(marshaller(a))
          catch { case NonFatal(e) ⇒ FastFuture.failed(e) }
      }