Почему akka-http Unmarshaler возвращает Future[T] вместо T?

Поскольку документация не готов, я спрошу здесь у сопровождающих akka.

Почему akka-http Unmarshaler возвращает Future[T] вместо T? Вот моя цель. Я хочу разобрать класс из XML-ответа http так же, как это делается для json. Например, я хотел бы написать

Unmarshal(HttpResponse.entity).to[Person]

где класс case и его unmarshaller выглядят так

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

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

Он не будет компилироваться с ScalaXmlSupport, поставляемым с 1.0-RC4, потому что Unmarshaller[ResponseEntity,Person] недоступен в области видимости. Поэтому, чтобы обмануть его, я написал два неявных преобразования

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)

Это работает, но мне не нравятся уродливые .value.get.get. Есть ли более элегантный способ реализовать это?


person expert    schedule 04.07.2015    source источник
comment
Я думаю, что он возвращает Future, потому что HttpEntity может быть нестрогим, и поэтому тело может быть неполным из-за типа получения ответа. То есть, если вы разбили ответы на фрагменты, вы не можете выполнить демаршалирование до тех пор, пока ответ не будет завершен.   -  person ale64bit    schedule 04.07.2015
comment
У меня была аналогичная проблема с удобством получения Future. Насколько я помню, HttpEntity по умолчанию имеет известную длину, но по-прежнему предоставляет содержимое через Source[ByteString], и поэтому вы получаете Future, я думаю. Тем не менее, я согласен с вами, что не очень приятно, когда вы хотите, чтобы все было просто.   -  person ale64bit    schedule 04.07.2015
comment
Есть ли причина определять Unmarshaller[NodeSeq, Person] в первую очередь? В противном случае вы могли бы напрямую определить FromEntityUnmarshaller[Person] с помощью defaultNodeSeqUnmarshaller.map(xml => /* the code from your personUnmarshaller*/).   -  person jrudolph    schedule 04.07.2015
comment
@jrudolph Я просто хотел использовать NodeSeq unmarshaler, который вы, ребята, предоставили, который используется на основе Content-Type. Думаю, я мог бы создать свой собственный трейт вместо использования Unmarshaller[NodeSeq, T], но я предпочитаю использовать как можно больше стандартных компонентов..   -  person expert    schedule 04.07.2015
comment
@jrudolph Как мне переписать xmlUnmarshaller, чтобы избежать value.get.get? Я получаю исключение, потому что вызывается None,get. По-видимому, unmarshaller пытается получить значение до того, как оно будет рассчитано.   -  person expert    schedule 05.07.2015
comment
Я просто имел в виду, что вам не нужно определять посредника Unmarshaller[NodeSeq, Person] для использования стандартного NodeSeq unmashaller. Вот почему я бы предложил попробовать implicit val personUnmarshaller: FromEntityUnmarshaller[Person] = defaultNodeSeqUnmarshaller.map(xml => /* the code from your personUnmarshaller*/)   -  person jrudolph    schedule 05.07.2015
comment
@jrudolph Я согласен. Но что, если я хочу повторно использовать код unmarshaller не только при обработке http-ответов, но и где-то еще (например, в тестах). Хорошо иметь универсальное решение в виде Unmarshaller[NodeSeq, T]. Почему бы вам, ребята, не заставить FromEntityUnmarshaller работать с фьючерсами, как и все остальное? Текущий дизайн не соответствует вашей цели сделать все асинхронным   -  person expert    schedule 05.07.2015


Ответы (1)


Что ж, на данный момент я реализовал свое собственное решение, но я надеюсь, что команда Akka сделает синхронизацию/асинхронность единообразной в библиотеке.

Поэтому я создал простой клон Unmarshaller, который определяется следующим образом.

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

Поэтому демаршаллеры для доменных классов будут определены следующим образом.

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

Затем есть два имплицита для ScalaXmlSupport, о которых я уже упоминал.

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(_))

Вот и все. И, наконец, если вы хотите использовать вызовы Akka, например

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

Вам нужен преобразователь с моего SyncUnmarshaller на 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) }
  }
person expert    schedule 05.07.2015