From 060cd745cdaa72ef433720fa09845c1132a516d8 Mon Sep 17 00:00:00 2001 From: Bjørn Madsen Date: Sun, 6 Aug 2017 00:28:33 +0200 Subject: Add circe support module --- core/src/main/scala/com/softwaremill/sttp/RequestT.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'core/src/main/scala/com/softwaremill/sttp/RequestT.scala') diff --git a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala index ff80dd5..4e38662 100644 --- a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala +++ b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala @@ -234,9 +234,9 @@ case class RequestT[U[_], T, +S]( handler.send(this.asInstanceOf[RequestT[Id, T, S]]) } - private def hasContentType: Boolean = + private[sttp] def hasContentType: Boolean = headers.exists(_._1.equalsIgnoreCase(ContentTypeHeader)) - private def setContentTypeIfMissing(ct: String): RequestT[U, T, S] = + private[sttp] def setContentTypeIfMissing(ct: String): RequestT[U, T, S] = if (hasContentType) this else contentType(ct) private def hasContentLength: Boolean = -- cgit v1.2.3 From 18db8e9d1ee240d11b558cdb9b5c850c5b063080 Mon Sep 17 00:00:00 2001 From: Bjørn Madsen Date: Tue, 8 Aug 2017 08:16:36 +0200 Subject: Expand BodySerializer to carry a default content type And update circe module to use this --- README.md | 2 +- .../main/scala/com/softwaremill/sttp/circe.scala | 22 ++++------------ .../scala/com/softwaremill/sttp/CirceTests.scala | 8 +++--- .../com/softwaremill/sttp/BodySerializer.scala | 29 ++++++++++++++++++++++ .../scala/com/softwaremill/sttp/RequestT.scala | 8 +++--- .../main/scala/com/softwaremill/sttp/package.scala | 6 ----- 6 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala (limited to 'core/src/main/scala/com/softwaremill/sttp/RequestT.scala') diff --git a/README.md b/README.md index 084d27d..99135ee 100644 --- a/README.md +++ b/README.md @@ -400,7 +400,7 @@ val requestPayload: Payload = ??? val response: Either[io.circe.Error, Response] = sttp .post(uri"...") - .jsonBody(requestPayload) + .body(requestPayload) .response(asJson[Response]) .send() ``` diff --git a/circe/src/main/scala/com/softwaremill/sttp/circe.scala b/circe/src/main/scala/com/softwaremill/sttp/circe.scala index 0233f08..f273e42 100644 --- a/circe/src/main/scala/com/softwaremill/sttp/circe.scala +++ b/circe/src/main/scala/com/softwaremill/sttp/circe.scala @@ -1,11 +1,6 @@ package com.softwaremill.sttp -import com.softwaremill.sttp.model.{ - RequestBody, - ResponseAs, - SerializableBody, - StringBody -} +import com.softwaremill.sttp.model.{ResponseAs, StringBody} import io.circe.parser._ import io.circe.{Decoder, Encoder} @@ -14,17 +9,10 @@ import scala.language.higherKinds package object circe { private[sttp] val ApplicationJsonContentType = "application/json" - private def serializeBody[B](body: B)( - implicit encoder: Encoder[B]): RequestBody[Nothing] = - SerializableBody((b: B) => StringBody(encoder(b).noSpaces, Utf8), body) - - implicit final class CirceRequestTOps[U[_], T, +S](val req: RequestT[U, T, S]) - extends AnyVal { - def jsonBody[B: Encoder](body: B): RequestT[U, T, S] = - req - .setContentTypeIfMissing(ApplicationJsonContentType) - .copy(body = serializeBody(body)) - } + implicit def circeBodySerializer[B: Encoder]: BodySerializer[B] = + BodySerializer.instance( + body => StringBody(Encoder[B].apply(body).noSpaces, Utf8), + ApplicationJsonContentType) def asJson[B: Decoder]: ResponseAs[Either[io.circe.Error, B], Nothing] = asString(Utf8).map(decode[B]) diff --git a/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala b/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala index abd20ef..81b90e6 100644 --- a/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala +++ b/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala @@ -13,7 +13,7 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { val body = Outer(Inner(42, true, "horses"), "cats") val expected = """{"foo":{"a":42,"b":true,"c":"horses"},"bar":"cats"}""" - val req = sttp.jsonBody(body) + val req = sttp.body(body) extractBody(req) shouldBe expected } @@ -38,7 +38,7 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { it should "should encode and decode back to the same thing" in { val outer = Outer(Inner(42, true, "horses"), "cats") - val encoded = extractBody(sttp.jsonBody(outer)) + val encoded = extractBody(sttp.body(outer)) val decoded = runJsonResponseAs(asJson[Outer])(encoded) decoded.right.value shouldBe outer @@ -46,7 +46,7 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { it should "set the content type" in { val body = Outer(Inner(42, true, "horses"), "cats") - val req = sttp.jsonBody(body) + val req = sttp.body(body) val ct = req.headers.toMap.get("Content-Type") @@ -55,7 +55,7 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { it should "only set the content type if it was not set earlier" in { val body = Outer(Inner(42, true, "horses"), "cats") - val req = sttp.contentType("horses/cats").jsonBody(body) + val req = sttp.contentType("horses/cats").body(body) val ct = req.headers.toMap.get("Content-Type") diff --git a/core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala b/core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala new file mode 100644 index 0000000..040c563 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala @@ -0,0 +1,29 @@ +package com.softwaremill.sttp + +import com.softwaremill.sttp.model.BasicRequestBody + +/** + * Provide an implicit value of this type to serialize arbitrary classes into a request body. + * Handlers might also provide special logic for serializer instances which they define (e.g. to handle streaming). + */ +trait BodySerializer[B] { + def apply(body: B): BasicRequestBody + def defaultContentType: Option[String] +} + +object BodySerializer { + final def apply[B](implicit instance: BodySerializer[B]): BodySerializer[B] = + instance + + final def instance[B](f: B => BasicRequestBody) = + new BodySerializer[B] { + def apply(body: B): BasicRequestBody = f(body) + val defaultContentType: Option[String] = None + } + + final def instance[B](f: B => BasicRequestBody, contentType: String) = + new BodySerializer[B] { + def apply(body: B): BasicRequestBody = f(body) + val defaultContentType: Option[String] = Option(contentType) + } +} diff --git a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala index 4e38662..c17be59 100644 --- a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala +++ b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala @@ -203,9 +203,11 @@ case class RequestT[U[_], T, +S]( * If content type is not yet specified, will be set to * `application/octet-stream`. */ - def body[B: BodySerializer](b: B): RequestT[U, T, S] = - setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( - body = SerializableBody(implicitly[BodySerializer[B]], b)) + def body[B](b: B)(implicit serializer: BodySerializer[B]): RequestT[U, T, S] = + setContentTypeIfMissing( + serializer.defaultContentType.getOrElse( + ApplicationOctetStreamContentType)) + .copy(body = SerializableBody(serializer, b)) //def multipartData(parts: MultiPart*): RequestTemplate[U] = ??? diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index 884d2f9..cca86dd 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -22,12 +22,6 @@ package object sttp { ".get(...), .post(...) etc. to obtain a non-partial request.") private[sttp] type IsIdInRequest[U[_]] = U[Unit] =:= Id[Unit] - /** - * Provide an implicit value of this type to serialize arbitrary classes into a request body. - * Handlers might also provide special logic for serializer instances which they define (e.g. to handle streaming). - */ - type BodySerializer[B] = B => BasicRequestBody - // constants private[sttp] val ContentTypeHeader = "Content-Type" -- cgit v1.2.3 From 85f93f2ccd282cdfceda8ae76cdf3cbb36d20883 Mon Sep 17 00:00:00 2001 From: Bjørn Madsen Date: Wed, 9 Aug 2017 22:17:53 +0200 Subject: Move default content type to BasicRequestBody Revert BodySerializer to be a function, and remove SerializableBody --- .../sttp/akkahttp/AkkaHttpSttpHandler.scala | 13 +++--- .../asynchttpclient/AsyncHttpClientHandler.scala | 13 ++---- .../main/scala/com/softwaremill/sttp/circe.scala | 11 ++--- .../scala/com/softwaremill/sttp/CirceTests.scala | 20 +++----- .../com/softwaremill/sttp/BodySerializer.scala | 29 ------------ .../sttp/HttpURLConnectionSttpHandler.scala | 13 ++---- .../scala/com/softwaremill/sttp/RequestT.scala | 54 ++++++++++++---------- .../com/softwaremill/sttp/model/RequestBody.scala | 41 ++++++++++++---- .../main/scala/com/softwaremill/sttp/package.scala | 6 +++ .../sttp/okhttp/OkHttpClientHandler.scala | 14 +++--- 10 files changed, 99 insertions(+), 115 deletions(-) delete mode 100644 core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala (limited to 'core/src/main/scala/com/softwaremill/sttp/RequestT.scala') diff --git a/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala b/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala index 0a2a467..d1dcc22 100644 --- a/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala +++ b/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala @@ -132,20 +132,19 @@ class AkkaHttpSttpHandler private (actorSystem: ActorSystem, getContentTypeOrOctetStream(r).map { ct => def doSet(body: RequestBody[S]): HttpRequest = body match { case NoBody => ar - case StringBody(b, encoding) => + case StringBody(b, encoding, _) => val ctWithEncoding = HttpCharsets .getForKey(encoding) .map(hc => ContentType.apply(ct.mediaType, () => hc)) .getOrElse(ct) ar.withEntity(ctWithEncoding, b.getBytes(encoding)) - case ByteArrayBody(b) => ar.withEntity(b) - case ByteBufferBody(b) => ar.withEntity(ByteString(b)) - case InputStreamBody(b) => + case ByteArrayBody(b, _) => ar.withEntity(b) + case ByteBufferBody(b, _) => ar.withEntity(ByteString(b)) + case InputStreamBody(b, _) => ar.withEntity( HttpEntity(ct, StreamConverters.fromInputStream(() => b))) - case PathBody(b) => ar.withEntity(ct, b) - case StreamBody(s) => ar.withEntity(HttpEntity(ct, s)) - case SerializableBody(f, t) => doSet(f(t)) + case PathBody(b, _) => ar.withEntity(ct, b) + case StreamBody(s) => ar.withEntity(HttpEntity(ct, s)) } doSet(body) diff --git a/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala b/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala index 8f75030..7b74c29 100644 --- a/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala +++ b/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala @@ -159,24 +159,21 @@ abstract class AsyncHttpClientHandler[R[_], S](asyncHttpClient: AsyncHttpClient, body match { case NoBody => // skip - case StringBody(b, encoding) => + case StringBody(b, encoding, _) => rb.setBody(b.getBytes(encoding)) - case ByteArrayBody(b) => + case ByteArrayBody(b, _) => rb.setBody(b) - case ByteBufferBody(b) => + case ByteBufferBody(b, _) => rb.setBody(b) - case InputStreamBody(b) => + case InputStreamBody(b, _) => rb.setBody(b) - case PathBody(b) => + case PathBody(b, _) => rb.setBody(b.toFile) - case SerializableBody(f, t) => - setBody(r, f(t), rb) - case StreamBody(s) => val cl = r.headers .find(_._1.equalsIgnoreCase(ContentLengthHeader)) diff --git a/circe/src/main/scala/com/softwaremill/sttp/circe.scala b/circe/src/main/scala/com/softwaremill/sttp/circe.scala index f273e42..95a72e1 100644 --- a/circe/src/main/scala/com/softwaremill/sttp/circe.scala +++ b/circe/src/main/scala/com/softwaremill/sttp/circe.scala @@ -1,18 +1,15 @@ package com.softwaremill.sttp -import com.softwaremill.sttp.model.{ResponseAs, StringBody} +import com.softwaremill.sttp.model._ import io.circe.parser._ import io.circe.{Decoder, Encoder} -import scala.language.higherKinds - package object circe { private[sttp] val ApplicationJsonContentType = "application/json" - implicit def circeBodySerializer[B: Encoder]: BodySerializer[B] = - BodySerializer.instance( - body => StringBody(Encoder[B].apply(body).noSpaces, Utf8), - ApplicationJsonContentType) + implicit def circeBodySerializer[B]( + implicit encoder: Encoder[B]): BodySerializer[B] = + b => StringBody(encoder(b).noSpaces, Utf8, Some(ApplicationJsonContentType)) def asJson[B: Decoder]: ResponseAs[Either[io.circe.Error, B], Nothing] = asString(Utf8).map(decode[B]) diff --git a/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala b/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala index 81b90e6..19a317e 100644 --- a/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala +++ b/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala @@ -35,7 +35,7 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { runJsonResponseAs(responseAs)(body).left.value shouldBe an[io.circe.Error] } - it should "should encode and decode back to the same thing" in { + it should "encode and decode back to the same thing" in { val outer = Outer(Inner(42, true, "horses"), "cats") val encoded = extractBody(sttp.body(outer)) @@ -50,7 +50,7 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { val ct = req.headers.toMap.get("Content-Type") - ct shouldBe Some("application/json") + ct shouldBe Some(contentTypeWithEncoding(ApplicationJsonContentType, Utf8)) } it should "only set the content type if it was not set earlier" in { @@ -82,18 +82,10 @@ class CirceTests extends FlatSpec with Matchers with EitherValues { def extractBody[A[_], B, C](request: RequestT[A, B, C]): String = request.body match { - case SerializableBody(serializer, body) => - serializer(body) match { - case StringBody(body, "utf-8") => - body - case StringBody(_, encoding) => - fail( - s"Request body serializes to StringBody with wrong encoding: $encoding") - case _ => - fail("Request body does not serialize to StringBody") - } - case _ => - fail("Request body is not SerializableBody") + case StringBody(body, "utf-8", Some(ApplicationJsonContentType)) => + body + case wrongBody => + fail(s"Request body does not serialize to correct StringBody: $wrongBody") } def runJsonResponseAs[A](responseAs: ResponseAs[A, Nothing]): String => A = diff --git a/core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala b/core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala deleted file mode 100644 index 040c563..0000000 --- a/core/src/main/scala/com/softwaremill/sttp/BodySerializer.scala +++ /dev/null @@ -1,29 +0,0 @@ -package com.softwaremill.sttp - -import com.softwaremill.sttp.model.BasicRequestBody - -/** - * Provide an implicit value of this type to serialize arbitrary classes into a request body. - * Handlers might also provide special logic for serializer instances which they define (e.g. to handle streaming). - */ -trait BodySerializer[B] { - def apply(body: B): BasicRequestBody - def defaultContentType: Option[String] -} - -object BodySerializer { - final def apply[B](implicit instance: BodySerializer[B]): BodySerializer[B] = - instance - - final def instance[B](f: B => BasicRequestBody) = - new BodySerializer[B] { - def apply(body: B): BasicRequestBody = f(body) - val defaultContentType: Option[String] = None - } - - final def instance[B](f: B => BasicRequestBody, contentType: String) = - new BodySerializer[B] { - def apply(body: B): BasicRequestBody = f(body) - val defaultContentType: Option[String] = Option(contentType) - } -} diff --git a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionSttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionSttpHandler.scala index 29da886..dd208f4 100644 --- a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionSttpHandler.scala +++ b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionSttpHandler.scala @@ -56,28 +56,25 @@ object HttpURLConnectionSttpHandler extends SttpHandler[Id, Nothing] { body match { case NoBody => // skip - case StringBody(b, encoding) => + case StringBody(b, encoding, _) => val writer = new OutputStreamWriter(c.getOutputStream, encoding) try writer.write(b) finally writer.close() - case ByteArrayBody(b) => + case ByteArrayBody(b, _) => c.getOutputStream.write(b) - case ByteBufferBody(b) => + case ByteBufferBody(b, _) => val channel = Channels.newChannel(c.getOutputStream) try channel.write(b) finally channel.close() - case InputStreamBody(b) => + case InputStreamBody(b, _) => copyStream(b, c.getOutputStream) - case PathBody(b) => + case PathBody(b, _) => Files.copy(b, c.getOutputStream) - case SerializableBody(f, t) => - setBody(f(t), c) - case StreamBody(s) => // we have an instance of nothing - everything's possible! s diff --git a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala index c17be59..6b1bf0c 100644 --- a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala +++ b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala @@ -100,10 +100,8 @@ case class RequestT[U[_], T, +S]( * bytes in the string using the given encoding. */ def body(b: String, encoding: String): RequestT[U, T, S] = - setContentTypeIfMissing( - contentTypeWithEncoding(TextPlainContentType, encoding)) + withBasicBody(StringBody(b, encoding)) .setContentLengthIfMissing(b.getBytes(encoding).length) - .copy(body = StringBody(b, encoding)) /** * If content type is not yet specified, will be set to @@ -113,25 +111,22 @@ case class RequestT[U[_], T, +S]( * of the given array. */ def body(b: Array[Byte]): RequestT[U, T, S] = - setContentTypeIfMissing(ApplicationOctetStreamContentType) + withBasicBody(ByteArrayBody(b)) .setContentLengthIfMissing(b.length) - .copy(body = ByteArrayBody(b)) /** * If content type is not yet specified, will be set to * `application/octet-stream`. */ def body(b: ByteBuffer): RequestT[U, T, S] = - setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( - body = ByteBufferBody(b)) + withBasicBody(ByteBufferBody(b)) /** * If content type is not yet specified, will be set to * `application/octet-stream`. */ def body(b: InputStream): RequestT[U, T, S] = - setContentTypeIfMissing(ApplicationOctetStreamContentType).copy( - body = InputStreamBody(b)) + withBasicBody(InputStreamBody(b)) /** * If content type is not yet specified, will be set to @@ -151,9 +146,8 @@ case class RequestT[U[_], T, +S]( * of the given file. */ def body(b: Path): RequestT[U, T, S] = - setContentTypeIfMissing(ApplicationOctetStreamContentType) + withBasicBody(PathBody(b)) .setContentLengthIfMissing(b.toFile.length()) - .copy(body = PathBody(b)) /** * Encodes the given parameters as form data using `utf-8`. @@ -203,16 +197,11 @@ case class RequestT[U[_], T, +S]( * If content type is not yet specified, will be set to * `application/octet-stream`. */ - def body[B](b: B)(implicit serializer: BodySerializer[B]): RequestT[U, T, S] = - setContentTypeIfMissing( - serializer.defaultContentType.getOrElse( - ApplicationOctetStreamContentType)) - .copy(body = SerializableBody(serializer, b)) - - //def multipartData(parts: MultiPart*): RequestTemplate[U] = ??? + def body[B: BodySerializer](b: B): RequestT[U, T, S] = + withBasicBody(implicitly[BodySerializer[B]].apply(b)) def streamBody[S2 >: S](b: S2): RequestT[U, T, S2] = - this.copy[U, T, S2](body = StreamBody(b)) + copy[U, T, S2](body = StreamBody(b)) /** * What's the target type to which the response body should be read. @@ -236,11 +225,25 @@ case class RequestT[U[_], T, +S]( handler.send(this.asInstanceOf[RequestT[Id, T, S]]) } - private[sttp] def hasContentType: Boolean = + private def hasContentType: Boolean = headers.exists(_._1.equalsIgnoreCase(ContentTypeHeader)) - private[sttp] def setContentTypeIfMissing(ct: String): RequestT[U, T, S] = + private def setContentTypeIfMissing(ct: String): RequestT[U, T, S] = if (hasContentType) this else contentType(ct) + private def withBasicBody(body: BasicRequestBody) = { + if (hasContentType) this + else + body match { + case StringBody(_, encoding, Some(ct)) => + contentType(ct, encoding) + case body => + body.defaultContentType match { + case Some(ct) => contentType(ct) + case None => this + } + } + }.copy(body = body) + private def hasContentLength: Boolean = headers.exists(_._1.equalsIgnoreCase(ContentLengthHeader)) private def setContentLengthIfMissing(l: => Long): RequestT[U, T, S] = @@ -249,10 +252,11 @@ case class RequestT[U[_], T, +S]( private def formDataBody(fs: Seq[(String, String)], encoding: String): RequestT[U, T, S] = { val b = fs - .map( - p => - URLEncoder.encode(p._1, encoding) + "=" + URLEncoder - .encode(p._2, encoding)) + .map { + case (key, value) => + URLEncoder.encode(key, encoding) + "=" + + URLEncoder.encode(value, encoding) + } .mkString("&") setContentTypeIfMissing(ApplicationFormContentType) .setContentLengthIfMissing(b.getBytes(encoding).length) diff --git a/core/src/main/scala/com/softwaremill/sttp/model/RequestBody.scala b/core/src/main/scala/com/softwaremill/sttp/model/RequestBody.scala index 0c737f0..7499048 100644 --- a/core/src/main/scala/com/softwaremill/sttp/model/RequestBody.scala +++ b/core/src/main/scala/com/softwaremill/sttp/model/RequestBody.scala @@ -4,18 +4,39 @@ import java.io.InputStream import java.nio.ByteBuffer import java.nio.file.Path -import com.softwaremill.sttp.BodySerializer +import com.softwaremill.sttp._ sealed trait RequestBody[+S] case object NoBody extends RequestBody[Nothing] -case class SerializableBody[T](f: BodySerializer[T], t: T) - extends RequestBody[Nothing] - -sealed trait BasicRequestBody extends RequestBody[Nothing] -case class StringBody(s: String, encoding: String) extends BasicRequestBody -case class ByteArrayBody(b: Array[Byte]) extends BasicRequestBody -case class ByteBufferBody(b: ByteBuffer) extends BasicRequestBody -case class InputStreamBody(b: InputStream) extends BasicRequestBody -case class PathBody(f: Path) extends BasicRequestBody + +sealed trait BasicRequestBody extends RequestBody[Nothing] { + def defaultContentType: Option[String] +} + +case class StringBody( + s: String, + encoding: String, + defaultContentType: Option[String] = Some(TextPlainContentType) +) extends BasicRequestBody + +case class ByteArrayBody( + b: Array[Byte], + defaultContentType: Option[String] = Some(ApplicationOctetStreamContentType) +) extends BasicRequestBody + +case class ByteBufferBody( + b: ByteBuffer, + defaultContentType: Option[String] = Some(ApplicationOctetStreamContentType) +) extends BasicRequestBody + +case class InputStreamBody( + b: InputStream, + defaultContentType: Option[String] = Some(ApplicationOctetStreamContentType) +) extends BasicRequestBody + +case class PathBody( + f: Path, + defaultContentType: Option[String] = Some(ApplicationOctetStreamContentType) +) extends BasicRequestBody case class StreamBody[S](s: S) extends RequestBody[S] diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index cca86dd..884d2f9 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -22,6 +22,12 @@ package object sttp { ".get(...), .post(...) etc. to obtain a non-partial request.") private[sttp] type IsIdInRequest[U[_]] = U[Unit] =:= Id[Unit] + /** + * Provide an implicit value of this type to serialize arbitrary classes into a request body. + * Handlers might also provide special logic for serializer instances which they define (e.g. to handle streaming). + */ + type BodySerializer[B] = B => BasicRequestBody + // constants private[sttp] val ContentTypeHeader = "Content-Type" diff --git a/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala b/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala index 79ca98b..f7c4466 100644 --- a/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala +++ b/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala @@ -49,19 +49,19 @@ abstract class OkHttpClientHandler[R[_], S](client: OkHttpClient) private def setBody(requestBody: RequestBody[S]): Option[OkHttpRequestBody] = { requestBody match { case NoBody => None - case StringBody(b, encoding) => + case StringBody(b, encoding, _) => Some(OkHttpRequestBody.create(MediaType.parse(encoding), b)) - case ByteArrayBody(b) => Some(OkHttpRequestBody.create(null, b)) - case ByteBufferBody(b) => Some(OkHttpRequestBody.create(null, b.array())) - case InputStreamBody(b) => + case ByteArrayBody(b, _) => Some(OkHttpRequestBody.create(null, b)) + case ByteBufferBody(b, _) => + Some(OkHttpRequestBody.create(null, b.array())) + case InputStreamBody(b, _) => Some(new OkHttpRequestBody() { override def writeTo(sink: BufferedSink): Unit = sink.writeAll(Okio.source(b)) override def contentType(): MediaType = null }) - case PathBody(b) => Some(OkHttpRequestBody.create(null, b.toFile)) - case SerializableBody(f, t) => setBody(f(t)) - case StreamBody(s) => None + case PathBody(b, _) => Some(OkHttpRequestBody.create(null, b.toFile)) + case StreamBody(s) => None } } -- cgit v1.2.3 From ec62e95a5f827cec7ba638a41680e78592e0192c Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 10 Aug 2017 10:31:25 +0200 Subject: Small simplification of the withBasicBody method --- .../main/scala/com/softwaremill/sttp/RequestT.scala | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'core/src/main/scala/com/softwaremill/sttp/RequestT.scala') diff --git a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala index 6b1bf0c..b785e7c 100644 --- a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala +++ b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala @@ -231,18 +231,15 @@ case class RequestT[U[_], T, +S]( if (hasContentType) this else contentType(ct) private def withBasicBody(body: BasicRequestBody) = { - if (hasContentType) this - else - body match { - case StringBody(_, encoding, Some(ct)) => - contentType(ct, encoding) - case body => - body.defaultContentType match { - case Some(ct) => contentType(ct) - case None => this - } - } - }.copy(body = body) + val defaultCt = body match { + case StringBody(_, encoding, Some(ct)) => + Some(contentTypeWithEncoding(ct, encoding)) + case _ => + body.defaultContentType + } + + defaultCt.fold(this)(setContentTypeIfMissing).copy(body = body) + } private def hasContentLength: Boolean = headers.exists(_._1.equalsIgnoreCase(ContentLengthHeader)) -- cgit v1.2.3