diff options
-rw-r--r-- | build.sbt | 13 | ||||
-rw-r--r-- | core/src/main/scala/com/softwaremill/sttp/package.scala | 1 | ||||
-rw-r--r-- | docs/json.rst | 27 | ||||
-rw-r--r-- | json/circe/src/main/scala/com/softwaremill/sttp/circe/package.scala (renamed from circe/src/main/scala/com/softwaremill/sttp/circe/package.scala) | 1 | ||||
-rw-r--r-- | json/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala (renamed from circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala) | 0 | ||||
-rw-r--r-- | json/json4s/src/main/scala/com/softwaremill/sttp/json4s/package.scala | 14 | ||||
-rw-r--r-- | json/json4s/src/test/scala/com/softwaremill/sttp/Json4sTests.scala | 75 |
7 files changed, 128 insertions, 3 deletions
@@ -58,6 +58,7 @@ lazy val rootProject = (project in file(".")) okhttpBackend, okhttpMonixBackend, circe, + json4s, tests ) @@ -151,7 +152,7 @@ lazy val okhttpMonixBackend: Project = (project in file("okhttp-backend/monix")) libraryDependencies ++= Seq(monix) ) dependsOn okhttpBackend -lazy val circe: Project = (project in file("circe")) +lazy val circe: Project = (project in file("json/circe")) .settings(commonSettings: _*) .settings( name := "circe", @@ -162,6 +163,16 @@ lazy val circe: Project = (project in file("circe")) ) ) dependsOn core +lazy val json4s: Project = (project in file("json/json4s")) + .settings(commonSettings: _*) + .settings( + name := "json4s", + libraryDependencies ++= Seq( + "org.json4s" %% "json4s-native" % "3.5.3", + scalaTest % "test" + ) + ) dependsOn core + lazy val tests: Project = (project in file("tests")) .settings(commonSettings: _*) .settings( diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index c1178d9..ed685fa 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -51,6 +51,7 @@ package object sttp { "application/x-www-form-urlencoded" private[sttp] val TextPlainContentType = "text/plain" private[sttp] val MultipartFormDataContentType = "multipart/form-data" + private[sttp] val ApplicationJsonContentType = "application/json" // entry points diff --git a/docs/json.rst b/docs/json.rst index 032c120..0d3a8ce 100644 --- a/docs/json.rst +++ b/docs/json.rst @@ -30,4 +30,29 @@ This module adds a method to the request and a function that can be given to a r .response(asJson[Response]) .send() - +Json4s +------ + +To encode and decode json using json4s-native, add the following dependency to your project:: + + "com.softwaremill.sttp" %% "json4s" % "0.0.20" + +Using this module it is possible to set request bodies and read response bodies as case classes, using the implicitly available ``org.json4s.Formats`` (which defaults to ``org.json4s.DefaultFormats``). + +Usage example:: + + import com.softwaremill.sttp._ + import com.softwaremill.sttp.json4s._ + + implicit val backend = HttpURLConnectionBackend() + + case class Payload(...) + val requestPayload: Payload = Payload(...) + + val response: Response[Payload] = + sttp + .post(uri"...") + .body(requestPayload) + .response(asJson[Response]) + .send() + diff --git a/circe/src/main/scala/com/softwaremill/sttp/circe/package.scala b/json/circe/src/main/scala/com/softwaremill/sttp/circe/package.scala index 77a926d..ddc9583 100644 --- a/circe/src/main/scala/com/softwaremill/sttp/circe/package.scala +++ b/json/circe/src/main/scala/com/softwaremill/sttp/circe/package.scala @@ -4,7 +4,6 @@ import io.circe.parser._ import io.circe.{Decoder, Encoder} package object circe { - private[sttp] val ApplicationJsonContentType = "application/json" implicit def circeBodySerializer[B]( implicit encoder: Encoder[B]): BodySerializer[B] = diff --git a/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala b/json/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala index 1e7dddc..1e7dddc 100644 --- a/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala +++ b/json/circe/src/test/scala/com/softwaremill/sttp/CirceTests.scala diff --git a/json/json4s/src/main/scala/com/softwaremill/sttp/json4s/package.scala b/json/json4s/src/main/scala/com/softwaremill/sttp/json4s/package.scala new file mode 100644 index 0000000..4c7aa36 --- /dev/null +++ b/json/json4s/src/main/scala/com/softwaremill/sttp/json4s/package.scala @@ -0,0 +1,14 @@ +package com.softwaremill.sttp + +import org.json4s._ +import org.json4s.native.Serialization.{read, write} + +package object json4s { + implicit def json4sBodySerializer[B <: AnyRef]( + implicit formats: Formats = DefaultFormats): BodySerializer[B] = + b => StringBody(write(b), Utf8, Some(ApplicationJsonContentType)) + + def asJson[B: Manifest]( + implicit formats: Formats = DefaultFormats): ResponseAs[B, Nothing] = + asString(Utf8).map(s => read[B](s)) +} diff --git a/json/json4s/src/test/scala/com/softwaremill/sttp/Json4sTests.scala b/json/json4s/src/test/scala/com/softwaremill/sttp/Json4sTests.scala new file mode 100644 index 0000000..bb4a774 --- /dev/null +++ b/json/json4s/src/test/scala/com/softwaremill/sttp/Json4sTests.scala @@ -0,0 +1,75 @@ +package com.softwaremill.sttp + +import org.json4s.ParserUtil.ParseException +import org.scalatest._ + +import scala.language.higherKinds + +class Json4sTests extends FlatSpec with Matchers with EitherValues { + import json4s._ + import Json4sTests._ + + "The json4s module" should "encode arbitrary json bodies" in { + val body = Outer(Inner(42, true, "horses"), "cats") + val expected = """{"foo":{"a":42,"b":true,"c":"horses"},"bar":"cats"}""" + + val req = sttp.body(body) + + extractBody(req) shouldBe expected + } + + it should "decode arbitrary bodies" in { + val body = """{"foo":{"a":42,"b":true,"c":"horses"},"bar":"cats"}""" + val expected = Outer(Inner(42, true, "horses"), "cats") + + val responseAs = asJson[Outer] + + runJsonResponseAs(responseAs)(body) shouldBe expected + } + + it should "fail to decode invalid json" in { + val body = """not valid json""" + + val responseAs = asJson[Outer] + + an[ParseException] should be thrownBy runJsonResponseAs(responseAs)(body) + } + + it should "set the content type" in { + val body = Outer(Inner(42, true, "horses"), "cats") + val req = sttp.body(body) + + val ct = req.headers.toMap.get("Content-Type") + + ct shouldBe Some(contentTypeWithEncoding(ApplicationJsonContentType, Utf8)) + } + + def extractBody[A[_], B, C](request: RequestT[A, B, C]): String = + request.body match { + 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 = + responseAs match { + case responseAs: MappedResponseAs[_, A, Nothing] => + responseAs.raw match { + case ResponseAsString("utf-8") => + responseAs.g + case ResponseAsString(encoding) => + fail( + s"MappedResponseAs wraps a ResponseAsString with wrong encoding: $encoding") + case _ => + fail("MappedResponseAs does not wrap a ResponseAsString") + } + case _ => fail("ResponseAs is not a MappedResponseAs") + } +} + +object Json4sTests { + case class Inner(a: Int, b: Boolean, c: String) + case class Outer(foo: Inner, bar: String) +} |