diff --git a/akka-http-tests/src/test/scala/akka/http/server/directives/MarshallingDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/server/directives/MarshallingDirectivesSpec.scala index 00334d0282..96550a70c0 100644 --- a/akka-http-tests/src/test/scala/akka/http/server/directives/MarshallingDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/server/directives/MarshallingDirectivesSpec.scala @@ -6,6 +6,7 @@ package akka.http.server package directives import scala.xml.NodeSeq +import org.scalatest.Inside import akka.http.marshallers.xml.ScalaXmlSupport import akka.http.unmarshalling._ import akka.http.marshalling._ @@ -16,14 +17,14 @@ import HttpCharsets._ import headers._ import spray.json.DefaultJsonProtocol._ -class MarshallingDirectivesSpec extends RoutingSpec { +class MarshallingDirectivesSpec extends RoutingSpec with Inside { 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 + case x ⇒ { val i = x.text.toInt; require(i >= 0); i } } implicit val IntMarshaller: ToEntityMarshaller[Int] = @@ -60,6 +61,23 @@ class MarshallingDirectivesSpec extends RoutingSpec { entity(as[String]) { _ ⇒ validate(false, "Problem") { completeOk } } } ~> check { rejection shouldEqual ValidationRejection("Problem") } } + "return a ValidationRejection if the request entity is semantically invalid (IllegalArgumentException)" in { + Put("/", HttpEntity(ContentType(`text/xml`, iso88592), "-3")) ~> { + entity(as[Int]) { _ ⇒ completeOk } + } ~> check { + inside(rejection) { + case ValidationRejection("requirement failed", Some(_: IllegalArgumentException)) ⇒ + } + } + } + "return a MalformedRequestContentRejection if unmarshalling failed due to a not further classified error" in { + Put("/", HttpEntity(`text/xml`, " { + entity(as[NodeSeq]) { _ ⇒ completeOk } + } ~> check { + rejection shouldEqual MalformedRequestContentRejection( + "XML document structures must start and end within the same entity.", None) + } + } "extract an Option[T] from the requests entity using the in-scope Unmarshaller" in { Put("/",

cool

) ~> { entity(as[Option[NodeSeq]]) { echoComplete } diff --git a/akka-http/src/main/scala/akka/http/server/Rejection.scala b/akka-http/src/main/scala/akka/http/server/Rejection.scala index 208a721219..636d1854e2 100644 --- a/akka-http/src/main/scala/akka/http/server/Rejection.scala +++ b/akka-http/src/main/scala/akka/http/server/Rejection.scala @@ -94,7 +94,10 @@ case class TooManyRangesRejection(maxRanges: Int) extends Rejection /** * Rejection created by unmarshallers. - * Signals that the request was rejected because there was an error while unmarshalling the request content + * Signals that the request was rejected because unmarshalling failed with an error that wasn't + * an `IllegalArgumentException`. Usually that means that the request content was not of the expected format. + * Note that semantic issues with the request content (e.g. because some parameter was out of range) + * will usually trigger a `ValidationRejection` instead. */ case class MalformedRequestContentRejection(message: String, cause: Option[Throwable] = None) extends Rejection @@ -161,7 +164,9 @@ case object AuthorizationFailedRejection extends Rejection case class MissingCookieRejection(cookieName: String) extends Rejection /** - * Rejection created by the `validation` directive. + * Rejection created by the `validation` directive as well as for `IllegalArgumentExceptions` + * thrown by domain model constructors (e.g. via `require`). + * It signals that an expected value was semantically invalid. */ case class ValidationRejection(message: String, cause: Option[Throwable] = None) extends Rejection diff --git a/akka-http/src/main/scala/akka/http/server/directives/MarshallingDirectives.scala b/akka-http/src/main/scala/akka/http/server/directives/MarshallingDirectives.scala index 402d45fcfc..9cfbe43f88 100644 --- a/akka-http/src/main/scala/akka/http/server/directives/MarshallingDirectives.scala +++ b/akka-http/src/main/scala/akka/http/server/directives/MarshallingDirectives.scala @@ -27,6 +27,7 @@ trait MarshallingDirectives { case Success(value) ⇒ provide(value) case Failure(Unmarshaller.NoContentException) ⇒ reject(RequestEntityExpectedRejection) case Failure(Unmarshaller.UnsupportedContentTypeException(x)) ⇒ reject(UnsupportedRequestContentTypeRejection(x)) + case Failure(x: IllegalArgumentException) ⇒ reject(ValidationRejection(x.getMessage.nullAsEmpty, Some(x))) case Failure(x) ⇒ reject(MalformedRequestContentRejection(x.getMessage.nullAsEmpty, Option(x.getCause))) } } & cancelRejections(RequestEntityExpectedRejection.getClass, classOf[UnsupportedRequestContentTypeRejection])