!htp #15927 port MarshallingDirectives from spray

This commit is contained in:
Mathias 2014-11-12 14:37:28 +01:00
parent 931e8f9b18
commit 193dda6e01
6 changed files with 237 additions and 13 deletions

View file

@ -0,0 +1,157 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.http.server
package directives
import scala.xml.NodeSeq
import akka.http.marshallers.xml.ScalaXmlSupport
import akka.http.unmarshalling._
import akka.http.marshalling._
import akka.http.model._
import akka.http.marshallers.sprayjson.SprayJsonSupport._
import MediaTypes._
import HttpCharsets._
import headers._
import spray.json.DefaultJsonProtocol._
class MarshallingDirectivesSpec extends RoutingSpec {
import ScalaXmlSupport._
private val iso88592 = HttpCharsets.getForKey("iso-8859-2").get
implicit val IntUnmarshaller: FromEntityUnmarshaller[Int] =
nodeSeqUnmarshaller(ContentTypeRange(`text/xml`, iso88592), `text/html`) map {
case NodeSeq.Empty throw Unmarshaller.NoContentException
case x x.text.toInt
}
implicit val IntMarshaller: ToEntityMarshaller[Int] =
Marshaller.oneOf(ContentType(`application/xhtml+xml`), ContentType(`text/xml`, `UTF-8`)) { contentType
nodeSeqMarshaller(contentType).wrap(contentType) { (i: Int) <int>{ i }</int> }
}
"The 'entityAs' directive" should {
"extract an object from the requests entity using the in-scope Unmarshaller" in {
Put("/", <p>cool</p>) ~> {
entity(as[NodeSeq]) { echoComplete }
} ~> check { responseAs[String] shouldEqual "<p>cool</p>" }
}
"return a RequestEntityExpectedRejection rejection if the request has no entity" in {
Put() ~> {
entity(as[Int]) { echoComplete }
} ~> check { rejection shouldEqual RequestEntityExpectedRejection }
}
"return an UnsupportedRequestContentTypeRejection if no matching unmarshaller is in scope" in {
Put("/", HttpEntity(`text/css`, "<p>cool</p>")) ~> {
entity(as[NodeSeq]) { echoComplete }
} ~> check {
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`))
}
Put("/", HttpEntity(ContentType(`text/xml`, `UTF-16`), "<int>26</int>")) ~> {
entity(as[Int]) { echoComplete }
} ~> check {
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(ContentTypeRange(`text/xml`, iso88592), `text/html`))
}
}
"cancel UnsupportedRequestContentTypeRejections if a subsequent `entity` directive succeeds" in {
Put("/", HttpEntity(`text/plain`, "yeah")) ~> {
entity(as[NodeSeq]) { _ completeOk } ~
entity(as[String]) { _ validate(false, "Problem") { completeOk } }
} ~> check { rejection shouldEqual ValidationRejection("Problem") }
}
"extract an Option[T] from the requests entity using the in-scope Unmarshaller" in {
Put("/", <p>cool</p>) ~> {
entity(as[Option[NodeSeq]]) { echoComplete }
} ~> check { responseAs[String] shouldEqual "Some(<p>cool</p>)" }
}
"extract an Option[T] as None if the request has no entity" in {
Put() ~> {
entity(as[Option[Int]]) { echoComplete }
} ~> check { responseAs[String] shouldEqual "None" }
}
"return an UnsupportedRequestContentTypeRejection if no matching unmarshaller is in scope (for Option[T]s)" in {
Put("/", HttpEntity(`text/css`, "<p>cool</p>")) ~> {
entity(as[Option[NodeSeq]]) { echoComplete }
} ~> check {
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`))
}
}
"properly extract with a super-unmarshaller" in {
case class Person(name: String)
val jsonUnmarshaller: FromEntityUnmarshaller[Person] = jsonFormat1(Person)
val xmlUnmarshaller: FromEntityUnmarshaller[Person] =
ScalaXmlSupport.nodeSeqUnmarshaller(`text/xml`).map(seq Person(seq.text))
implicit val unmarshaller = Unmarshaller.firstOf(jsonUnmarshaller, xmlUnmarshaller)
val route = entity(as[Person]) { echoComplete }
Put("/", HttpEntity(`text/xml`, "<name>Peter Xml</name>")) ~> route ~> check {
responseAs[String] shouldEqual "Person(Peter Xml)"
}
Put("/", HttpEntity(`application/json`, """{ "name": "Paul Json" }""")) ~> route ~> check {
responseAs[String] shouldEqual "Person(Paul Json)"
}
Put("/", HttpEntity(`text/plain`, """name = Sir Text }""")) ~> route ~> check {
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(`application/json`, `text/xml`))
}
}
}
"The 'completeWith' directive" should {
"provide a completion function converting custom objects to an HttpEntity using the in-scope marshaller" in {
Get() ~> completeWith(instanceOf[Int]) { prod prod(42) } ~> check {
responseEntity shouldEqual HttpEntity(ContentType(`application/xhtml+xml`, `UTF-8`), "<int>42</int>")
}
}
"return a UnacceptedResponseContentTypeRejection rejection if no acceptable marshaller is in scope" in {
Get() ~> Accept(`text/css`) ~> completeWith(instanceOf[Int]) { prod prod(42) } ~> check {
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(`application/xhtml+xml`, ContentType(`text/xml`, `UTF-8`)))
}
}
"convert the response content to an accepted charset" in {
Get() ~> `Accept-Charset`(`UTF-8`) ~> completeWith(instanceOf[String]) { prod prod("Hällö") } ~> check {
responseEntity shouldEqual HttpEntity(ContentType(`text/plain`, `UTF-8`), "Hällö")
}
}
}
"The 'handleWith' directive" should {
def times2(x: Int) = x * 2
"support proper round-trip content unmarshalling/marshalling to and from a function" in (
Put("/", HttpEntity(`text/html`, "<int>42</int>")) ~> Accept(`text/xml`) ~> handleWith(times2)
~> check { responseEntity shouldEqual HttpEntity(ContentType(`text/xml`, `UTF-8`), "<int>84</int>") })
"result in UnsupportedRequestContentTypeRejection rejection if there is no unmarshaller supporting the requests charset" in (
Put("/", HttpEntity(`text/xml`, "<int>42</int>")) ~> Accept(`text/xml`) ~> handleWith(times2)
~> check {
rejection shouldEqual UnsupportedRequestContentTypeRejection(Set(ContentTypeRange(`text/xml`, iso88592), `text/html`))
})
"result in an UnacceptedResponseContentTypeRejection rejection if there is no marshaller supporting the requests Accept-Charset header" in (
Put("/", HttpEntity(`text/html`, "<int>42</int>")) ~> addHeaders(Accept(`text/xml`), `Accept-Charset`(`UTF-16`)) ~>
handleWith(times2) ~> check {
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(`application/xhtml+xml`, ContentType(`text/xml`, `UTF-8`)))
})
}
"The marshalling infrastructure for JSON" should {
import spray.json._
case class Foo(name: String)
implicit val fooFormat = jsonFormat1(Foo)
val foo = Foo("Hällö")
"render JSON with UTF-8 encoding if no `Accept-Charset` request header is present" in {
Get() ~> complete(foo) ~> check {
responseEntity shouldEqual HttpEntity(ContentType(`application/json`, `UTF-8`), foo.toJson.prettyPrint)
}
}
"reject JSON rendering if an `Accept-Charset` request header requests a non-UTF-8 encoding" in {
Get() ~> `Accept-Charset`(`ISO-8859-1`) ~> complete(foo) ~> check {
rejection shouldEqual UnacceptedResponseContentTypeRejection(Set(ContentType(`application/json`, `UTF-8`)))
}
}
}
}