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 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