diff --git a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java index f8a2563ef1..db751056e7 100644 --- a/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java +++ b/akka-http-core/src/main/java/akka/http/model/japi/HttpEntityRegular.java @@ -4,10 +4,6 @@ package akka.http.model.japi; -import akka.util.ByteString; - -import java.io.File; - /** * A marker type that denotes HttpEntity subtypes that can be used in Http requests. */ diff --git a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala index 914a9dd1d7..92abc977c8 100644 --- a/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala +++ b/akka-http-core/src/main/scala/akka/http/model/MultipartContent.scala @@ -85,10 +85,10 @@ object MultipartFormData { } } -final case class FormFile(name: Option[String], entity: HttpEntity.Default) +final case class FormFile(name: Option[String], entity: HttpEntity) object FormFile { - def apply(name: String, entity: HttpEntity.Default): FormFile = apply(Some(name), entity) + def apply(name: String, entity: HttpEntity): FormFile = apply(Some(name), entity) } /** @@ -129,4 +129,39 @@ object BodyPart { def apply(entity: HttpEntity, fieldName: String): BodyPart = apply(entity, fieldName, Map.empty[String, String]) def apply(entity: HttpEntity, fieldName: String, params: Map[String, String]): BodyPart = BodyPart(entity, immutable.Seq(`Content-Disposition`(ContentDispositionTypes.`form-data`, params.updated("name", fieldName)))) -} \ No newline at end of file +} + +/** + * A convenience extractor that allows to match on a BodyPart including its name if the body-part + * is used as part of form-data. If the part has no name the extractor won't match. + * + * Example: + * + * {{{ + * (formData: StrictMultipartFormData).fields collect { + * case NamedBodyPart("address", data, headers) => data + * } + * }}} + */ +object NamedBodyPart { + def unapply(part: BodyPart): Option[(String, HttpEntity, immutable.Seq[HttpHeader])] = + part.name.map(name ⇒ (name, part.entity, part.headers)) +} + +/** + * A convenience extractor that allows to match on a BodyPart including its name and filename + * if the body-part is used as part of form-data. If the part has no name an empty string will be + * extracted, instead. If the part has no filename the extractor won't match. + * + * Example: + * + * {{{ + * (formData: StrictMultipartFormData).fields collect { + * case FileBodyPart("file", filename, data, headers) => filename -> data + * } + * }}} + */ +object FileBodyPart { + def unapply(part: BodyPart): Option[(String, String, HttpEntity, immutable.Seq[HttpHeader])] = + part.filename.map(filename ⇒ (part.name.getOrElse(""), filename, part.entity, part.headers)) +} diff --git a/akka-http-core/src/test/scala/akka/http/model/MultipartContentSpec.scala b/akka-http-core/src/test/scala/akka/http/model/MultipartContentSpec.scala new file mode 100644 index 0000000000..87fbe41fc7 --- /dev/null +++ b/akka-http-core/src/test/scala/akka/http/model/MultipartContentSpec.scala @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http.model + +import akka.http.model.headers.{ ContentDispositionTypes, `Content-Disposition` } +import akka.util.ByteString + +import org.scalatest.{ Inside, Matchers, WordSpec } + +class MultipartContentSpec extends WordSpec with Matchers with Inside { + "BodyPart" should { + val data = HttpEntity(ByteString("data")) + + "be matched with NamedBodyPart extractor if it has a name" in { + val part = BodyPart(data, "name") + inside(part) { + case NamedBodyPart("name", entity, _) ⇒ entity should equal(data) + } + } + + "not be matched with NamedBodyPart extractor if it has no name" in { + val part = BodyPart(data) + inside(part) { + case NamedBodyPart(name, entity, _) ⇒ fail(s"Shouldn't match but did match with name '$name'") + case _ ⇒ + } + } + + "be matched with FileBodyPart extractor if it contains a file" in { + val part = BodyPart(FormFile("data.txt", data), "name") + inside(part) { + case FileBodyPart("name", "data.txt", entity, _) ⇒ entity should equal(data) + } + } + + "be matched with FileBodyPart extractor if it contains a file but no name" in { + val part = BodyPart(data, `Content-Disposition`(ContentDispositionTypes.`form-data`, Map("filename" -> "data.txt")) :: Nil) + + inside(part) { + case FileBodyPart("", "data.txt", entity, _) ⇒ entity should equal(data) + } + } + + "not be matched with NamedBodyPart extractor if it doesn't contains a file" in { + val part = BodyPart(data) + inside(part) { + case FileBodyPart(name, filename, entity, _) ⇒ fail(s"Shouldn't match but did match with name '$name' and filename '$filename'") + case _ ⇒ + } + } + } +}