!htp #15656 split out scala-xml support into its own subproject
This commit is contained in:
parent
c6ab4c74ba
commit
2941a55401
12 changed files with 126 additions and 57 deletions
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import akka.http.unmarshalling.{ Unmarshal, FromEntityUnmarshaller }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ ExecutionContext, Await }
|
||||
|
||||
import akka.http.marshalling._
|
||||
import akka.http.model.HttpEntity
|
||||
import akka.stream.FlowMaterializer
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
trait MarshallingTestUtils {
|
||||
def marshal[T: ToEntityMarshallers](value: T)(implicit ec: ExecutionContext, mat: FlowMaterializer): HttpEntity.Strict =
|
||||
Await.result(Marshal(value).to[HttpEntity].flatMap(_.toStrict(1.second)), 1.second)
|
||||
|
||||
def unmarshalValue[T: FromEntityUnmarshaller](entity: HttpEntity)(implicit ec: ExecutionContext, mat: FlowMaterializer): T =
|
||||
unmarshal(entity).get
|
||||
|
||||
def unmarshal[T: FromEntityUnmarshaller](entity: HttpEntity)(implicit ec: ExecutionContext, mat: FlowMaterializer): Try[T] = {
|
||||
val fut = Unmarshal(entity).to[T]
|
||||
Await.ready(fut, 1.second)
|
||||
fut.value.get
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10,7 +10,6 @@ import scala.concurrent.{ Await, Future }
|
|||
import scala.concurrent.duration._
|
||||
import scala.util.DynamicVariable
|
||||
import scala.reflect.ClassTag
|
||||
import org.scalatest.Suite
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.FlowMaterializer
|
||||
import akka.http.client.RequestBuilding
|
||||
|
|
@ -21,7 +20,7 @@ import akka.http.model._
|
|||
import headers.Host
|
||||
import FastFuture._
|
||||
|
||||
trait RouteTest extends RequestBuilding with RouteTestResultComponent {
|
||||
trait RouteTest extends RequestBuilding with RouteTestResultComponent with MarshallingTestUtils {
|
||||
this: TestFrameworkInterface ⇒
|
||||
|
||||
/** Override to supply a custom ActorSystem */
|
||||
|
|
@ -142,6 +141,4 @@ trait RouteTest extends RequestBuilding with RouteTestResultComponent {
|
|||
}
|
||||
}
|
||||
|
||||
trait ScalatestRouteTest extends RouteTest with TestFrameworkInterface.Scalatest { this: Suite ⇒ }
|
||||
|
||||
//FIXME: trait Specs2RouteTest extends RouteTest with Specs2Interface
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import akka.http.model.HttpEntity
|
||||
import akka.http.unmarshalling.FromEntityUnmarshaller
|
||||
import akka.stream.FlowMaterializer
|
||||
import org.scalatest.Suite
|
||||
import org.scalatest.matchers.Matcher
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ ExecutionContext, Future, Await }
|
||||
import scala.util.Try
|
||||
|
||||
trait ScalatestUtils extends MarshallingTestUtils {
|
||||
import org.scalatest.Matchers._
|
||||
def evaluateTo[T](value: T): Matcher[Future[T]] =
|
||||
equal(value).matcher[T] compose (x ⇒ Await.result(x, 1.second))
|
||||
|
||||
def haveFailedWith(t: Throwable): Matcher[Future[_]] =
|
||||
equal(t).matcher[Throwable] compose (x ⇒ Await.result(x.failed, 1.second))
|
||||
|
||||
def unmarshalToValue[T: FromEntityUnmarshaller](value: T)(implicit ec: ExecutionContext, mat: FlowMaterializer): Matcher[HttpEntity] =
|
||||
equal(value).matcher[T] compose (unmarshalValue(_))
|
||||
|
||||
def unmarshalTo[T: FromEntityUnmarshaller](value: Try[T])(implicit ec: ExecutionContext, mat: FlowMaterializer): Matcher[HttpEntity] =
|
||||
equal(value).matcher[Try[T]] compose (unmarshal(_))
|
||||
}
|
||||
|
||||
trait ScalatestRouteTest extends RouteTest with TestFrameworkInterface.Scalatest with ScalatestUtils { this: Suite ⇒ }
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.marshallers.xml
|
||||
|
||||
import akka.http.unmarshalling.Unmarshal
|
||||
|
||||
import akka.http.testkit.ScalatestRouteTest
|
||||
import akka.http.unmarshalling.UnmarshallingError.UnsupportedContentType
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
|
||||
import akka.http.model.HttpCharsets._
|
||||
import akka.http.model.MediaTypes._
|
||||
import akka.http.model.{ ContentTypeRange, ContentType, HttpEntity }
|
||||
|
||||
import akka.http.marshalling.{ ToEntityMarshallers, Marshal }
|
||||
|
||||
import scala.xml.NodeSeq
|
||||
|
||||
class ScalaXmlSupportSpec extends WordSpec with Matchers with ScalatestRouteTest {
|
||||
import ScalaXmlSupport._
|
||||
|
||||
"ScalaXmlSupport" should {
|
||||
"NodeSeqMarshaller should marshal xml snippets to `text/xml` content in UTF-8" in {
|
||||
marshal(<employee><nr>Ha“llo</nr></employee>) shouldEqual
|
||||
HttpEntity(ContentType(`text/xml`, `UTF-8`), "<employee><nr>Ha“llo</nr></employee>")
|
||||
}
|
||||
"nodeSeqUnmarshaller should unmarshal `text/xml` content in UTF-8 to NodeSeqs" in {
|
||||
Unmarshal(HttpEntity(`text/xml`, "<int>Hällö</int>")).to[NodeSeq].map(_.text) should evaluateTo("Hällö")
|
||||
}
|
||||
"nodeSeqUnmarshaller should reject `application/octet-stream`" in {
|
||||
Unmarshal(HttpEntity(`application/octet-stream`, "<int>Hällö</int>")).to[NodeSeq].map(_.text) should
|
||||
haveFailedWith(UnsupportedContentType(ScalaXmlSupport.nodeSeqMediaTypes map (ContentTypeRange(_))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
package akka.http.marshalling
|
||||
|
||||
import akka.http.testkit.MarshallingTestUtils
|
||||
import akka.http.marshallers.xml.ScalaXmlSupport._
|
||||
|
||||
import scala.collection.immutable.ListMap
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
import org.scalatest.{ BeforeAndAfterAll, FreeSpec, Matchers }
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.FlowMaterializer
|
||||
|
|
@ -17,7 +18,7 @@ import headers._
|
|||
import HttpCharsets._
|
||||
import MediaTypes._
|
||||
|
||||
class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with MultipartMarshallers {
|
||||
class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with MultipartMarshallers with MarshallingTestUtils {
|
||||
implicit val system = ActorSystem(getClass.getSimpleName)
|
||||
implicit val materializer = FlowMaterializer()
|
||||
import system.dispatcher
|
||||
|
|
@ -29,10 +30,6 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
"CharArrayMarshaller should marshal char arrays to `text/plain` content in UTF-8" in {
|
||||
marshal("Ha“llo".toCharArray) shouldEqual HttpEntity("Ha“llo")
|
||||
}
|
||||
"NodeSeqMarshaller should marshal xml snippets to `text/xml` content in UTF-8" in {
|
||||
marshal(<employee><nr>Ha“llo</nr></employee>) shouldEqual
|
||||
HttpEntity(ContentType(`text/xml`, `UTF-8`), "<employee><nr>Ha“llo</nr></employee>")
|
||||
}
|
||||
"FormDataMarshaller should marshal FormData instances to application/x-www-form-urlencoded content" in {
|
||||
marshal(FormData(Map("name" -> "Bob", "pass" -> "hällo", "admin" -> ""))) shouldEqual
|
||||
HttpEntity(ContentType(`application/x-www-form-urlencoded`, `UTF-8`), "name=Bob&pass=h%C3%A4llo&admin=")
|
||||
|
|
@ -145,9 +142,6 @@ class MarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with
|
|||
|
||||
override def afterAll() = system.shutdown()
|
||||
|
||||
def marshal[T: ToEntityMarshallers](value: T): HttpEntity.Strict =
|
||||
Await.result(Await.result(Marshal(value).to[HttpEntity], 1.second).toStrict(1.second), 1.second)
|
||||
|
||||
protected class FixedRandom extends java.util.Random {
|
||||
override def nextBytes(array: Array[Byte]): Unit = "my-stable-boundary".getBytes("UTF-8").copyToArray(array)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
package akka.http.server
|
||||
|
||||
import akka.http.marshalling.Marshaller
|
||||
import akka.http.marshallers.xml.ScalaXmlSupport
|
||||
import akka.http.server.directives.AuthenticationDirectives._
|
||||
import com.typesafe.config.{ ConfigFactory, Config }
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -36,7 +36,9 @@ object TestServer extends App {
|
|||
case _ ⇒ false
|
||||
}
|
||||
|
||||
implicit val html = Marshaller.nodeSeqMarshaller(MediaTypes.`text/html`)
|
||||
// FIXME: a simple `import ScalaXmlSupport._` should suffice but currently doesn't because
|
||||
// of #16190
|
||||
implicit val html = ScalaXmlSupport.nodeSeqMarshaller(MediaTypes.`text/html`)
|
||||
|
||||
handleConnections(bindingFuture) withRoute {
|
||||
get {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package akka.http.server.directives
|
|||
import org.scalatest.FreeSpec
|
||||
|
||||
import scala.concurrent.Promise
|
||||
import akka.http.marshallers.xml.ScalaXmlSupport._
|
||||
import akka.http.marshalling._
|
||||
import akka.http.server._
|
||||
import akka.http.model._
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
package akka.http.unmarshalling
|
||||
|
||||
import akka.http.testkit.ScalatestUtils
|
||||
import akka.util.ByteString
|
||||
|
||||
import scala.xml.NodeSeq
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ Future, Await }
|
||||
import org.scalatest.matchers.Matcher
|
||||
|
|
@ -20,7 +20,7 @@ import headers._
|
|||
import MediaTypes._
|
||||
import FastFuture._
|
||||
|
||||
class UnmarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
||||
class UnmarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll with ScalatestUtils {
|
||||
implicit val system = ActorSystem(getClass.getSimpleName)
|
||||
implicit val materializer = FlowMaterializer()
|
||||
import system.dispatcher
|
||||
|
|
@ -32,9 +32,6 @@ class UnmarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
|||
"charArrayUnmarshaller should unmarshal `text/plain` content in UTF-8 to char arrays" in {
|
||||
Unmarshal(HttpEntity("árvíztűrő ütvefúrógép")).to[Array[Char]] should evaluateTo("árvíztűrő ütvefúrógép".toCharArray)
|
||||
}
|
||||
"nodeSeqUnmarshaller should unmarshal `text/xml` content in UTF-8 to NodeSeqs" in {
|
||||
Unmarshal(HttpEntity(`text/xml`, "<int>Hällö</int>")).to[NodeSeq].fast.map(_.text) should evaluateTo("Hällö")
|
||||
}
|
||||
}
|
||||
|
||||
"The MultipartUnmarshallers." - {
|
||||
|
|
@ -213,9 +210,6 @@ class UnmarshallingSpec extends FreeSpec with Matchers with BeforeAndAfterAll {
|
|||
|
||||
override def afterAll() = system.shutdown()
|
||||
|
||||
def evaluateTo[T](value: T): Matcher[Future[T]] =
|
||||
equal(value).matcher[T] compose (x ⇒ Await.result(x, 1.second))
|
||||
|
||||
def haveParts[T <: Multipart](parts: Multipart.BodyPart*): Matcher[Future[T]] =
|
||||
equal(parts).matcher[Seq[Multipart.BodyPart]] compose { x ⇒
|
||||
Await.result(x
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ package akka.http.marshalling
|
|||
import scala.collection.immutable
|
||||
import scala.concurrent.{ Future, ExecutionContext }
|
||||
import scala.util.control.NonFatal
|
||||
import scala.xml.NodeSeq
|
||||
import akka.http.util.FastFuture
|
||||
import akka.http.model._
|
||||
import FastFuture._
|
||||
import MediaTypes._
|
||||
|
||||
case class Marshallers[-A, +B](marshallers: immutable.Seq[Marshaller[A, B]]) {
|
||||
require(marshallers.nonEmpty, "marshallers must be non-empty")
|
||||
|
|
@ -22,13 +20,8 @@ case class Marshallers[-A, +B](marshallers: immutable.Seq[Marshaller[A, B]]) {
|
|||
object Marshallers extends SingleMarshallerMarshallers {
|
||||
def apply[A, B](m: Marshaller[A, B]): Marshallers[A, B] = apply(m :: Nil)
|
||||
def apply[A, B](first: Marshaller[A, B], more: Marshaller[A, B]*): Marshallers[A, B] = apply(first +: more.toVector)
|
||||
def apply[A, B](first: MediaType, more: MediaType*)(f: MediaType ⇒ Marshaller[A, B]): Marshallers[A, B] = {
|
||||
val vector: Vector[Marshaller[A, B]] = more.map(f)(collection.breakOut)
|
||||
Marshallers(f(first) +: vector)
|
||||
}
|
||||
|
||||
implicit def nodeSeqMarshallers(implicit ec: ExecutionContext): ToEntityMarshallers[NodeSeq] =
|
||||
Marshallers(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`)(PredefinedToEntityMarshallers.nodeSeqMarshaller)
|
||||
def apply[A, B](mediaTypes: MediaType*)(f: MediaType ⇒ Marshaller[A, B]): Marshallers[A, B] =
|
||||
Marshallers(mediaTypes.map(f)(collection.breakOut))
|
||||
|
||||
implicit def entity2response[T](implicit m: Marshallers[T, ResponseEntity], ec: ExecutionContext): Marshallers[T, HttpResponse] =
|
||||
m map (entity ⇒ HttpResponse(entity = entity))
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package akka.http.marshalling
|
|||
|
||||
import java.nio.CharBuffer
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.xml.NodeSeq
|
||||
import akka.http.model.parser.CharacterClasses
|
||||
import akka.http.model.MediaTypes._
|
||||
import akka.http.model._
|
||||
|
|
@ -53,9 +52,6 @@ trait PredefinedToEntityMarshallers extends MultipartMarshallers {
|
|||
def stringMarshaller(mediaType: MediaType): ToEntityMarshaller[String] =
|
||||
Marshaller.withOpenCharset(mediaType) { (s, cs) ⇒ HttpEntity(ContentType(mediaType, cs), s) }
|
||||
|
||||
implicit def nodeSeqMarshaller(mediaType: MediaType)(implicit ec: ExecutionContext): ToEntityMarshaller[NodeSeq] =
|
||||
StringMarshaller.wrap(mediaType)(_.toString())
|
||||
|
||||
implicit val FormDataMarshaller: ToEntityMarshaller[FormData] =
|
||||
Marshaller.withOpenCharset(`application/x-www-form-urlencoded`) { (formData, charset) ⇒
|
||||
val query = Uri.Query(formData.fields: _*)
|
||||
|
|
|
|||
|
|
@ -4,15 +4,11 @@
|
|||
|
||||
package akka.http.unmarshalling
|
||||
|
||||
import java.io.{ ByteArrayInputStream, InputStreamReader }
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.xml.{ XML, NodeSeq }
|
||||
import akka.stream.FlowMaterializer
|
||||
import akka.stream.scaladsl._
|
||||
import akka.util.ByteString
|
||||
import akka.http.util.FastFuture
|
||||
import akka.http.model._
|
||||
import MediaTypes._
|
||||
|
||||
trait PredefinedFromEntityUnmarshallers extends MultipartUnmarshallers {
|
||||
|
||||
|
|
@ -42,21 +38,6 @@ trait PredefinedFromEntityUnmarshallers extends MultipartUnmarshallers {
|
|||
bytes.decodeString(entity.contentType.charset.nioCharset.name) // ouch!!!
|
||||
}
|
||||
|
||||
private val nodeSeqMediaTypes = List(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`)
|
||||
implicit def nodeSeqUnmarshaller(implicit fm: FlowMaterializer,
|
||||
ec: ExecutionContext): FromEntityUnmarshaller[NodeSeq] =
|
||||
byteArrayUnmarshaller flatMapWithInput { (entity, bytes) ⇒
|
||||
if (nodeSeqMediaTypes contains entity.contentType.mediaType) {
|
||||
val parser = XML.parser
|
||||
try parser.setProperty("http://apache.org/xml/properties/locale", java.util.Locale.ROOT)
|
||||
catch {
|
||||
case e: org.xml.sax.SAXNotRecognizedException ⇒ // property is not needed
|
||||
}
|
||||
val reader = new InputStreamReader(new ByteArrayInputStream(bytes), entity.contentType.charset.nioCharset)
|
||||
FastFuture.successful(XML.withSAXParser(parser).load(reader)) // blocking call! Ideally we'd have a `loadToFuture`
|
||||
} else FastFuture.failed(UnmarshallingError.UnsupportedContentType(nodeSeqMediaTypes map (ContentTypeRange(_))))
|
||||
}
|
||||
|
||||
implicit def urlEncodedFormDataUnmarshaller(implicit fm: FlowMaterializer,
|
||||
ec: ExecutionContext): FromEntityUnmarshaller[FormData] =
|
||||
stringUnmarshaller mapWithInput { (entity, string) ⇒
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import scala.util.control.NonFatal
|
|||
import scala.collection.immutable
|
||||
import scala.concurrent.{ Future, ExecutionContext }
|
||||
import akka.http.util._
|
||||
import akka.http.model.ContentTypeRange
|
||||
import akka.http.model.{ HttpCharset, MediaType, ContentTypeRange }
|
||||
import FastFuture._
|
||||
|
||||
trait Unmarshaller[-A, B] extends (A ⇒ Future[B]) {
|
||||
|
|
@ -53,6 +53,17 @@ object Unmarshaller
|
|||
def flatMapWithInput[C](f: (A, B) ⇒ Future[C])(implicit ec: ExecutionContext): Unmarshaller[A, C] =
|
||||
Unmarshaller(a ⇒ um(a).fast.flatMap(f(a, _)))
|
||||
}
|
||||
|
||||
implicit class EnhancedToEntityUnmarshaller[T](val um: FromEntityUnmarshaller[T]) extends AnyVal {
|
||||
def mapWithCharset[U](f: (T, HttpCharset) ⇒ U)(implicit ec: ExecutionContext): FromEntityUnmarshaller[U] =
|
||||
um.mapWithInput { (entity, data) ⇒ f(data, entity.contentType.charset) }
|
||||
|
||||
def filterMediaType(allowed: MediaType*)(implicit ec: ExecutionContext): FromEntityUnmarshaller[T] =
|
||||
um.flatMapWithInput { (entity, data) ⇒
|
||||
if (allowed contains entity.contentType.mediaType) Future.successful(data)
|
||||
else FastFuture.failed(UnmarshallingError.UnsupportedContentType(allowed map (ContentTypeRange(_)) toList))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract class UnmarshallingError(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue