diff --git a/akka-docs/rst/java/code/docs/http/javadsl/server/directives/FileUploadDirectivesExamplesTest.java b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/FileUploadDirectivesExamplesTest.java new file mode 100644 index 0000000000..63324058e7 --- /dev/null +++ b/akka-docs/rst/java/code/docs/http/javadsl/server/directives/FileUploadDirectivesExamplesTest.java @@ -0,0 +1,134 @@ +/** + * Copyright (C) 2016-2016 Lightbend Inc. + */ +package docs.http.javadsl.server.directives; + +import akka.http.impl.engine.rendering.BodyPartRenderer; +import akka.http.javadsl.model.*; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.Unmarshaller; +import akka.http.javadsl.server.directives.FileInfo; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.stream.javadsl.Framing; +import akka.stream.javadsl.Source; +import akka.util.ByteString; +import org.junit.Ignore; +import org.junit.Test; +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +public class FileUploadDirectivesExamplesTest extends JUnitRouteTest { + + @Test + public void testUploadedFile() { + //#uploadedFile + // function (FileInfo, File) => Route to process the file metadata and file itself + BiFunction infoFileRoute = + (info, file) -> { + // do something with the file and file metadata ... + file.delete(); + return complete(StatusCodes.OK); + }; + + + final Route route = uploadedFile("csv", infoFileRoute); + + Map filenameMapping = new HashMap<>(); + filenameMapping.put("filename", "data.csv"); + + akka.http.javadsl.model.Multipart.FormData multipartForm = + Multiparts.createStrictFormDataFromParts(Multiparts.createFormDataBodyPartStrict("csv", + HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, + "1,5,7\n11,13,17"), filenameMapping)); + + // test: + testRoute(route).run(HttpRequest.POST("/").withEntity( + multipartForm.toEntity(HttpCharsets.UTF_8, BodyPartRenderer + .randomBoundaryWithDefaults()))) + .assertStatusCode(StatusCodes.OK); + //# + } + + @Test + public void testFileUpload() { + //#fileUpload + final Route route = extractRequestContext(ctx -> { + // function (FileInfo, Source) => Route to process the file contents + BiFunction, Route> processUploadedFile = + (metadata, byteSource) -> { + CompletionStage sumF = byteSource.via(Framing.delimiter( + ByteString.fromString("\n"), 1024)) + .mapConcat(bs -> Arrays.asList(bs.utf8String().split(","))) + .map(s -> Integer.parseInt(s)) + .runFold(0, (acc, n) -> acc + n, ctx.getMaterializer()); + return onSuccess(() -> sumF, sum -> complete("Sum: " + sum)); + }; + return fileUpload("csv", processUploadedFile); + }); + + Map filenameMapping = new HashMap<>(); + filenameMapping.put("filename", "primes.csv"); + + akka.http.javadsl.model.Multipart.FormData multipartForm = + Multiparts.createStrictFormDataFromParts( + Multiparts.createFormDataBodyPartStrict("csv", + HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, + "2,3,5\n7,11,13,17,23\n29,31,37\n"), filenameMapping)); + + // test: + testRoute(route).run(HttpRequest.POST("/").withEntity( + multipartForm.toEntity(HttpCharsets.UTF_8, BodyPartRenderer.randomBoundaryWithDefaults()))) + .assertStatusCode(StatusCodes.OK).assertEntityAs(Unmarshaller.entityToString(), "Sum: 178"); + //# + } + + @Ignore("compileOnly") + @Test + public void testFileProcessing() { + //#fileProcessing + final Route route = extractRequestContext(ctx -> { + // function (FileInfo, Source) => Route to process the file contents + BiFunction, Route> processUploadedFile = + (metadata, byteSource) -> { + CompletionStage sumF = byteSource.via(Framing.delimiter( + ByteString.fromString("\n"), 1024)) + .mapConcat(bs -> Arrays.asList(bs.utf8String().split(","))) + .map(s -> Integer.parseInt(s)) + .runFold(0, (acc, n) -> acc + n, ctx.getMaterializer()); + return onSuccess(() -> sumF, sum -> complete("Sum: " + sum)); + }; + return fileUpload("csv", processUploadedFile); + }); + + Map filenameMapping = new HashMap<>(); + filenameMapping.put("filename", "primes.csv"); + + String prefix = "primes"; + String suffix = ".csv"; + + File tempFile = null; + try { + tempFile = File.createTempFile(prefix, suffix); + tempFile.deleteOnExit(); + Files.write(tempFile.toPath(), Arrays.asList("2,3,5", "7,11,13,17,23", "29,31,37"), Charset.forName("UTF-8")); + } catch (Exception e) { + // ignore + } + + + akka.http.javadsl.model.Multipart.FormData multipartForm = + Multiparts.createFormDataFromPath("csv", ContentTypes.TEXT_PLAIN_UTF8, tempFile.toPath()); + + // test: + testRoute(route).run(HttpRequest.POST("/").withEntity( + multipartForm.toEntity(HttpCharsets.UTF_8, BodyPartRenderer.randomBoundaryWithDefaults()))) + .assertStatusCode(StatusCodes.OK).assertEntityAs(Unmarshaller.entityToString(), "Sum: 178"); + //# + } +} diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst index 7c3f703edf..01991357bf 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/fileUpload.rst @@ -14,7 +14,8 @@ with the same name, the first one will be used and the subsequent ones ignored. Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. +.. includecode2:: ../../../../code/docs/http/javadsl/server/directives/FileUploadDirectivesExamplesTest.java + :snippet: fileUpload :: diff --git a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst index 7d66d3afa9..f6ffe06511 100644 --- a/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst +++ b/akka-docs/rst/java/http/routing-dsl/directives/file-upload-directives/uploadedFile.rst @@ -20,4 +20,5 @@ one will be used and the subsequent ones ignored. Example ------- -TODO: Example snippets for JavaDSL are subject to community contributions! Help us complete the docs, read more about it here: `write example snippets for Akka HTTP Java DSL #20466 `_. +.. includecode2:: ../../../../code/docs/http/javadsl/server/directives/FileUploadDirectivesExamplesTest.java + :snippet: uploadedFile \ No newline at end of file diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java index 02d34db036..9a236faad5 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/HttpEntities.java @@ -87,7 +87,7 @@ public final class HttpEntities { (akka.http.scaladsl.model.ContentType) contentType, toScala(data)); } - + private static akka.stream.scaladsl.Source toScala(Source javaSource) { return (akka.stream.scaladsl.Source)javaSource.asScala(); } diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/Multiparts.java b/akka-http-core/src/main/java/akka/http/javadsl/model/Multiparts.java new file mode 100644 index 0000000000..8f9dd0ea14 --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/Multiparts.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016-2016 Lightbend Inc. + */ +package akka.http.javadsl.model; + +import scala.collection.immutable.List; +import scala.collection.immutable.Nil$; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; + +import static akka.http.impl.util.Util.convertArray; +import static akka.http.impl.util.Util.convertMapToScala; +import static akka.http.impl.util.Util.emptyMap; + +/** + * Constructors for Multipart instances + */ +public final class Multiparts { + /** + * Constructor for `multipart/form-data` content as defined in http://tools.ietf.org/html/rfc2388. + * All parts must have distinct names. (This is not verified!) + */ + public static Multipart.FormData createFormDataFromParts(Multipart.FormData.BodyPart... parts) { + return akka.http.scaladsl.model.Multipart.FormData$.MODULE$.createNonStrict(convertArray(parts)); + } + + /** + * Constructor for `multipart/form-data` content as defined in http://tools.ietf.org/html/rfc2388. + * All parts must have distinct names. (This is not verified!) + */ + public static Multipart.FormData.Strict createStrictFormDataFromParts(Multipart.FormData.BodyPart.Strict... parts) { + return akka.http.scaladsl.model.Multipart.FormData$.MODULE$.createStrict(convertArray(parts)); + } + + /** + * Constructor for `multipart/form-data` content as defined in http://tools.ietf.org/html/rfc2388. + * All parts must have distinct names. (This is not verified!) + */ + public static Multipart.FormData.Strict createFormDataFromFields(Map fields) { + return akka.http.scaladsl.model.Multipart.FormData$.MODULE$.createStrict(toScalaMap(fields)); + } + + /** + * Creates a FormData instance that contains a single part backed by the given file. + * + * To create an instance with several parts or for multiple files, use + * `Multiparts.createFormDataFromParts(Multiparts.createFormDataPartFromPath("field1", ...), Multiparts.createFormDataPartFromPath("field2", ...)` + */ + public static Multipart.FormData createFormDataFromPath(String name, ContentType contentType, Path path, int chunkSize) { + return akka.http.scaladsl.model.Multipart.FormData$.MODULE$.fromPath(name, (akka.http.scaladsl.model.ContentType) contentType, path, chunkSize); + } + + /** + * Creates a FormData instance that contains a single part backed by the given file. + * + * To create an instance with several parts or for multiple files, use + * `Multiparts.createFormDataFromParts(Multiparts.createFormDataPartFromPath("field1", ...), Multiparts.createFormDataPartFromPath("field2", ...)` + */ + public static Multipart.FormData createFormDataFromPath(String name, ContentType contentType, Path path) { + return akka.http.scaladsl.model.Multipart.FormData$.MODULE$.fromPath(name, (akka.http.scaladsl.model.ContentType) contentType, path, -1); + } + + /** + * Creates a BodyPart backed by a file that will be streamed using a FileSource. + */ + public static Multipart.FormData.BodyPart createFormDataPartFromPath(String name, ContentType contentType, Path path, int chunkSize) { + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$.MODULE$.fromPath(name, (akka.http.scaladsl.model.ContentType) contentType, path, chunkSize); + } + + /** + * Creates a BodyPart backed by a file that will be streamed using a FileSource. + */ + public static Multipart.FormData.BodyPart createFormDataPartFromPath(String name, ContentType contentType, Path path) { + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$.MODULE$.fromPath(name, (akka.http.scaladsl.model.ContentType) contentType, path, -1); + } + + /** + * Creates a BodyPart. + */ + public static Multipart.FormData.BodyPart createFormDataBodyPart(String name, BodyPartEntity entity) { + List nil = Nil$.MODULE$; + Map additionalDispositionParams = Collections.emptyMap(); + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$Builder$.MODULE$.create(name, (akka.http.scaladsl.model.BodyPartEntity) entity, + convertMapToScala(additionalDispositionParams), nil); + } + + /** + * Creates a BodyPart. + */ + public static Multipart.FormData.BodyPart createFormDataBodyPart(String name, BodyPartEntity entity, Map additionalDispositionParams) { + List nil = Nil$.MODULE$; + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$Builder$.MODULE$.create(name, (akka.http.scaladsl.model.BodyPartEntity) entity, + convertMapToScala(additionalDispositionParams), nil); + } + + /** + * Creates a BodyPart. + */ + public static Multipart.FormData.BodyPart createFormDataBodyPart(String name, BodyPartEntity entity, Map additionalDispositionParams, java.util.List headers) { + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$Builder$.MODULE$.create(name, (akka.http.scaladsl.model.BodyPartEntity) entity, + convertMapToScala(additionalDispositionParams), toScalaSeq(headers)); + } + + /** + * Creates a BodyPart.Strict. + */ + public static Multipart.FormData.BodyPart.Strict createFormDataBodyPartStrict(String name, HttpEntity.Strict entity) { + List nil = Nil$.MODULE$; + Map additionalDispositionParams = Collections.emptyMap(); + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$StrictBuilder$.MODULE$.createStrict(name, (akka.http.scaladsl.model.HttpEntity.Strict) entity, + convertMapToScala(additionalDispositionParams), nil); + } + + /** + * Creates a BodyPart.Strict. + */ + public static Multipart.FormData.BodyPart.Strict createFormDataBodyPartStrict(String name, HttpEntity.Strict entity, Map additionalDispositionParams) { + List nil = Nil$.MODULE$; + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$StrictBuilder$.MODULE$.createStrict(name, (akka.http.scaladsl.model.HttpEntity.Strict) entity, + convertMapToScala(additionalDispositionParams), nil); + } + + /** + * Creates a BodyPart.Strict. + */ + public static Multipart.FormData.BodyPart.Strict createFormDataBodyPartStrict(String name, HttpEntity.Strict entity, Map additionalDispositionParams, java.util.List headers) { + return akka.http.scaladsl.model.Multipart$FormData$BodyPart$StrictBuilder$.MODULE$.createStrict(name, (akka.http.scaladsl.model.HttpEntity.Strict) entity, + convertMapToScala(additionalDispositionParams), toScalaSeq(headers)); + } + + private static scala.collection.immutable.Map toScalaMap(Map map) { + return emptyMap.$plus$plus(scala.collection.JavaConverters.mapAsScalaMapConverter(map).asScala()); + } + + private static scala.collection.Iterable toScalaSeq(java.util.List _headers) { + return scala.collection.JavaConverters.collectionAsScalaIterableConverter(_headers).asScala(); + } +} diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/BodyPartRenderer.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/BodyPartRenderer.scala index 0ee220a276..aa1b34bb64 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/BodyPartRenderer.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/BodyPartRenderer.scala @@ -139,4 +139,14 @@ private[http] object BodyPartRenderer { random.nextBytes(array) Base64.custom.encodeToString(array, false) } + + /** + * Creates a new random number of default length and base64 encodes it (using a custom "safe" alphabet). + */ + def randomBoundaryWithDefaults(): String = randomBoundary() + + /** + * Creates a new random number of the given length and base64 encodes it (using a custom "safe" alphabet). + */ + def randomBoundaryWithDefaultRandom(length: Int): String = randomBoundary(length) } diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala index 63029fbaa2..e57b1578ba 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/Multipart.scala @@ -212,7 +212,7 @@ object Multipart { /** Java API */ override def toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[jm.Multipart.General.Strict] = - super.toStrict(timeoutMillis, materializer).asInstanceOf[Future[jm.Multipart.General.Strict]].toJava + super.toStrict(timeoutMillis, materializer).toScala.asInstanceOf[Future[jm.Multipart.General.Strict]].toJava } object General { def apply(mediaType: MediaType.Multipart, parts: BodyPart.Strict*): Strict = Strict(mediaType, parts.toVector) @@ -258,7 +258,7 @@ object Multipart { /** Java API */ override def toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[jm.Multipart.General.BodyPart.Strict] = - super.toStrict(timeoutMillis, materializer).asInstanceOf[Future[jm.Multipart.General.BodyPart.Strict]].toJava + super.toStrict(timeoutMillis, materializer).toScala.asInstanceOf[Future[jm.Multipart.General.BodyPart.Strict]].toJava private[BodyPart] def tryCreateFormDataBodyPart[T](f: (String, Map[String, String], immutable.Seq[HttpHeader]) ⇒ T): Try[T] = { val params = dispositionParams @@ -323,12 +323,22 @@ object Multipart { /** Java API */ override def toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[jm.Multipart.FormData.Strict] = - super.toStrict(timeoutMillis, materializer).asInstanceOf[Future[jm.Multipart.FormData.Strict]].toJava + super.toStrict(timeoutMillis, materializer).toScala.asInstanceOf[Future[jm.Multipart.FormData.Strict]].toJava } object FormData { def apply(parts: Multipart.FormData.BodyPart.Strict*): Multipart.FormData.Strict = Strict(parts.toVector) def apply(parts: Multipart.FormData.BodyPart*): Multipart.FormData = Multipart.FormData(Source(parts.toVector)) + // FIXME: SI-2991 workaround - two functions below. Remove when (hopefully) this issue is fixed + /** INTERNAL API */ + private[akka] def createStrict(parts: Multipart.FormData.BodyPart.Strict*): Multipart.FormData.Strict = Strict(parts.toVector) + /** INTERNAL API */ + private[akka] def createNonStrict(parts: Multipart.FormData.BodyPart*): Multipart.FormData = Multipart.FormData(Source(parts.toVector)) + /** INTERNAL API */ + private[akka] def createStrict(fields: Map[String, akka.http.javadsl.model.HttpEntity.Strict]): Multipart.FormData.Strict = Multipart.FormData.Strict { + fields.map { case (name, entity: akka.http.scaladsl.model.HttpEntity.Strict) ⇒ Multipart.FormData.BodyPart.Strict(name, entity) }(collection.breakOut) + } + def apply(fields: Map[String, HttpEntity.Strict]): Multipart.FormData.Strict = Multipart.FormData.Strict { fields.map { case (name, entity) ⇒ Multipart.FormData.BodyPart.Strict(name, entity) }(collection.breakOut) } @@ -426,7 +436,7 @@ object Multipart { /** Java API */ override def toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[jm.Multipart.FormData.BodyPart.Strict] = - super.toStrict(timeoutMillis, materializer).asInstanceOf[Future[jm.Multipart.FormData.BodyPart.Strict]].toJava + super.toStrict(timeoutMillis, materializer).toScala.asInstanceOf[Future[jm.Multipart.FormData.BodyPart.Strict]].toJava } object BodyPart { def apply(_name: String, _entity: BodyPartEntity, @@ -467,6 +477,26 @@ object Multipart { FastFuture.successful(this) override def productPrefix = "FormData.BodyPart.Strict" } + + /** INTERNAL API */ + private[akka] object Builder { + def create(_name: String, _entity: BodyPartEntity, + _additionalDispositionParams: Map[String, String], + _additionalHeaders: Iterable[akka.http.javadsl.model.HttpHeader]): Multipart.FormData.BodyPart = { + val _headers = _additionalHeaders.to[immutable.Seq] map { case h: akka.http.scaladsl.model.HttpHeader ⇒ h } + apply(_name, _entity, _additionalDispositionParams, _headers) + } + } + + /** INTERNAL API */ + private[akka] object StrictBuilder { + def createStrict(_name: String, _entity: HttpEntity.Strict, + _additionalDispositionParams: Map[String, String], + _additionalHeaders: Iterable[akka.http.javadsl.model.HttpHeader]): Multipart.FormData.BodyPart.Strict = { + val _headers = _additionalHeaders.to[immutable.Seq] map { case h: akka.http.scaladsl.model.HttpHeader ⇒ h } + Strict(_name, _entity, _additionalDispositionParams, _headers) + } + } } } @@ -488,7 +518,7 @@ object Multipart { /** Java API */ override def toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[jm.Multipart.ByteRanges.Strict] = - super.toStrict(timeoutMillis, materializer).asInstanceOf[Future[jm.Multipart.ByteRanges.Strict]].toJava + super.toStrict(timeoutMillis, materializer).toScala.asInstanceOf[Future[jm.Multipart.ByteRanges.Strict]].toJava } object ByteRanges { def apply(parts: Multipart.ByteRanges.BodyPart.Strict*): Strict = Strict(parts.toVector) @@ -563,7 +593,7 @@ object Multipart { /** Java API */ override def toStrict(timeoutMillis: Long, materializer: Materializer): CompletionStage[jm.Multipart.ByteRanges.BodyPart.Strict] = - super.toStrict(timeoutMillis, materializer).asInstanceOf[Future[jm.Multipart.ByteRanges.BodyPart.Strict]].toJava + super.toStrict(timeoutMillis, materializer).toScala.asInstanceOf[Future[jm.Multipart.ByteRanges.BodyPart.Strict]].toJava } object BodyPart { def apply(_contentRange: ContentRange, _entity: BodyPartEntity, _rangeUnit: RangeUnit = RangeUnits.Bytes, diff --git a/akka-http-core/src/test/scala/akka/http/javadsl/model/MultipartsSpec.scala b/akka-http-core/src/test/scala/akka/http/javadsl/model/MultipartsSpec.scala new file mode 100644 index 0000000000..227822b570 --- /dev/null +++ b/akka-http-core/src/test/scala/akka/http/javadsl/model/MultipartsSpec.scala @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016-2016 Lightbend Inc. + */ + +package akka.http.javadsl.model + +import java.util +import com.typesafe.config.{ Config, ConfigFactory } +import scala.concurrent.Await +import scala.concurrent.duration._ +import org.scalatest.{ BeforeAndAfterAll, Inside, Matchers, WordSpec } +import akka.stream.ActorMaterializer +import akka.actor.ActorSystem +import scala.compat.java8.FutureConverters + +class MultipartsSpec extends WordSpec with Matchers with Inside with BeforeAndAfterAll { + + val testConf: Config = ConfigFactory.parseString(""" + akka.event-handlers = ["akka.testkit.TestEventListener"] + akka.loglevel = WARNING""") + implicit val system = ActorSystem(getClass.getSimpleName, testConf) + implicit val materializer = ActorMaterializer() + override def afterAll() = system.terminate() + + "Multiparts.createFormDataFromParts" should { + "create a model from Multiparts.createFormDataBodyPartparts" in { + val streamed = Multiparts.createFormDataFromParts( + Multiparts.createFormDataBodyPart("foo", HttpEntities.create("FOO")), + Multiparts.createFormDataBodyPart("bar", HttpEntities.create("BAR"))) + val strictCS = streamed.toStrict(1000, materializer) + val strict = Await.result(FutureConverters.toScala(strictCS), 1.second) + + strict shouldEqual akka.http.scaladsl.model.Multipart.FormData( + Map("foo" → akka.http.scaladsl.model.HttpEntity("FOO"), "bar" → akka.http.scaladsl.model.HttpEntity("BAR"))) + } + } + + "Multiparts.createFormDataFromFields" should { + "create a model from a map of fields" in { + val fields = new util.HashMap[String, HttpEntity.Strict] + fields.put("foo", HttpEntities.create("FOO")) + val streamed = Multiparts.createFormDataFromFields(fields) + val strictCS = streamed.toStrict(1000, materializer) + val strict = Await.result(FutureConverters.toScala(strictCS), 1.second) + + strict shouldEqual akka.http.scaladsl.model.Multipart.FormData( + Map("foo" → akka.http.scaladsl.model.HttpEntity("FOO"))) + } + } + + "Multiparts.createStrictFormDataFromParts" should { + "create a strict model from Multiparts.createFormDataBodyPartStrict parts" in { + val streamed = Multiparts.createStrictFormDataFromParts( + Multiparts.createFormDataBodyPartStrict("foo", HttpEntities.create("FOO")), + Multiparts.createFormDataBodyPartStrict("bar", HttpEntities.create("BAR"))) + val strict = streamed + + strict shouldEqual akka.http.scaladsl.model.Multipart.FormData( + Map("foo" → akka.http.scaladsl.model.HttpEntity("FOO"), "bar" → akka.http.scaladsl.model.HttpEntity("BAR"))) + } + } +}