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 ?
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) }
}