Permalink
Browse files

! httpx: enable FEOU and FSOD to be interchanged in the usual cases, f…

…ixes #426

The idea is to
  - interpret application/x-www-form-urlencoded fields as text/plain so that FEOU can read
    it as well
  - interpret multipart form parts with content-type text/plain as strings that
    can be read from FSOD

This should make it much easier to change the form encoding on the client
without having to change anything on the spray side.
  • Loading branch information...
jrudolph committed Oct 10, 2013
1 parent 5ac8b64 commit ebaa5803b6b3c4df05bcd1e0b73372c1f8b295f0
@@ -51,15 +51,37 @@ class UrlEncodedFormField(val name: String, val rawValue: Option[String]) extend
type Raw = String
def as[T](implicit ffc: FormFieldConverter[T]) = ffc.urlEncodedFieldConverter match {
case Some(conv) conv(rawValue)
- case None fail(name, "multipart/form-data")
+ case None
+ ffc.multipartFieldConverter match {
+ case Some(conv)
+ conv(rawValue.map(HttpEntity(_))) match {
+ case Left(UnsupportedContentType(msg))
+ Left(UnsupportedContentType(msg + " but tried to read from application/x-www-form-urlencoded encoded field '" +
+ name + "' which provides only text/plain values."))
+ case x x
+ }
+ case None fail(name, "multipart/form-data")
+ }
}
}
class MultipartFormField(val name: String, val rawValue: Option[BodyPart]) extends FormField {
type Raw = BodyPart
def as[T](implicit ffc: FormFieldConverter[T]) = ffc.multipartFieldConverter match {
case Some(conv) conv(rawValue.map(_.entity))
- case None fail(name, "application/x-www-form-urlencoded")
+ case None
+ ffc.urlEncodedFieldConverter match {
+ case Some(conv)
+ rawValue match {
+ case Some(BodyPart(HttpEntity.NonEmpty(tpe, data), _)) if tpe.mediaRange.matches(MediaTypes.`text/plain`)
+ conv(Some(data.asString))
+ case None | Some(BodyPart(HttpEntity.Empty, _)) conv(None)
+ case Some(BodyPart(HttpEntity.NonEmpty(tpe, _), _))
+ Left(UnsupportedContentType(s"Field '$name' can only be read from " +
+ s"'application/x-www-form-urlencoded' form content but was '${tpe.mediaRange}'"))
+ }
+ case None fail(name, "application/x-www-form-urlencoded")
+ }
}
}
@@ -61,14 +61,15 @@ class FormFieldSpec extends Specification {
marshal(formData)
.flatMap(_.as[HttpForm])
.flatMap(_.field("age").as[NodeSeq]) ===
- Left(UnsupportedContentType("Field 'age' can only be read from 'multipart/form-data' form content"))
+ Left(UnsupportedContentType("Expected 'text/xml' or 'application/xml' or 'text/html' or 'application/xhtml+xml' " +
+ "but tried to read from application/x-www-form-urlencoded encoded field 'age' which provides only text/plain values."))
}
"return an error when accessing a field of multipart forms for which no Unmarshaller is available" in {
marshal(multipartFormData)
.flatMap(_.as[HttpForm])
.flatMap(_.field("age").as[Int]) ===
- Left(UnsupportedContentType("Field 'age' can only be read from 'application/x-www-form-urlencoded' form content"))
+ Left(UnsupportedContentType("Field 'age' can only be read from 'application/x-www-form-urlencoded' form content but was 'text/xml'"))
}
}
@@ -20,15 +20,25 @@ import shapeless.HNil
import spray.httpx.marshalling.marshalUnsafe
import spray.httpx.unmarshalling.FromStringDeserializers.HexInt
import spray.http._
+import scala.xml.NodeSeq
+import spray.routing.directives.FieldDefMagnet
class FormFieldDirectivesSpec extends RoutingSpec {
val nodeSeq: xml.NodeSeq = <b>yes</b>
val urlEncodedForm = FormData(Map("firstName" -> "Mike", "age" -> "42"))
+ val urlEncodedFormWithVip = FormData(Map("firstName" -> "Mike", "age" -> "42", "VIP" -> "true"))
val multipartForm = MultipartFormData {
Map(
"firstName" -> BodyPart("Mike"),
- "age" -> BodyPart(marshalUnsafe(<int>42</int>)))
+ "age" -> BodyPart(marshalUnsafe(<int>42</int>)),
+ "VIPBoolean" -> BodyPart("true"))
+ }
+ val multipartFormWithTextHtml = MultipartFormData {
+ Map(
+ "firstName" -> BodyPart("Mike"),
+ "age" -> BodyPart(marshalUnsafe(<int>42</int>)),
+ "VIP" -> BodyPart(HttpEntity(MediaTypes.`text/html`, "<b>yes</b>")))
}
"The 'formFields' extraction directive" should {
@@ -74,24 +84,36 @@ class FormFieldDirectivesSpec extends RoutingSpec {
}
} ~> check { rejection === MissingFormFieldRejection("sex") }
}
- "create a proper error message if only a multipart unmarshaller is available for a www-urlencoded field" in {
- Get("/", urlEncodedForm) ~> {
- formFields('firstName, "age", 'sex?, "VIP" ? nodeSeq) { (firstName, age, sex, vip)
- complete(firstName + age + sex + vip)
+ "properly extract the value if only a urlencoded deserializer is available for a multipart field that comes without a" +
+ "Content-Type (or text/plain)" in {
+ Get("/", multipartForm) ~> {
+ formFields('firstName, "age", 'sex?, "VIPBoolean" ? false) { (firstName, age, sex, vip)
+ complete(firstName + age + sex + vip)
+ }
+ } ~> check {
+ responseAs[String] === "Mike<int>42</int>Nonetrue"
}
- } ~> check {
- rejection === UnsupportedRequestContentTypeRejection("Field 'VIP' can only be read from " +
- "'multipart/form-data' form content")
}
- }
- "create a proper error message if only a urlencoded deserializer is available for a multipart field" in {
- Get("/", multipartForm) ~> {
- formFields('firstName, "age", 'sex?, "VIP" ? false) { (firstName, age, sex, vip)
+ "create a proper error message if only a urlencoded deserializer is available for a multipart field with custom " +
+ "Content-Type" in {
+ Get("/", multipartFormWithTextHtml) ~> {
+ formFields(('firstName, "age", 'VIP ? false)) { (firstName, age, vip)
+ complete(firstName + age + vip)
+ }
+ } ~> check {
+ rejection === UnsupportedRequestContentTypeRejection("Field 'VIP' can only be read from " +
+ "'application/x-www-form-urlencoded' form content but was 'text/html'")
+ }
+ }
+ "create a proper error message if only a multipart unmarshaller is available for a www-urlencoded field" in {
+ Get("/", urlEncodedFormWithVip) ~> {
+ formFields('firstName, "age", 'sex?, "VIP" ? nodeSeq) { (firstName, age, sex, vip)
complete(firstName + age + sex + vip)
}
} ~> check {
- rejection === UnsupportedRequestContentTypeRejection("Field 'VIP' can only be read from " +
- "'application/x-www-form-urlencoded' form content")
+ rejection === UnsupportedRequestContentTypeRejection("Expected 'text/xml' or 'application/xml' or 'text/html' " +
+ "or 'application/xhtml+xml' but tried to read from application/x-www-form-urlencoded encoded " +
+ "field 'VIP' which provides only text/plain values.")
}
}
}

0 comments on commit ebaa580

Please sign in to comment.