Skip to content

Commit

Permalink
Merge pull request #1917 from Leammas/add_implicit_decoder
Browse files Browse the repository at this point in the history
Implement optional implicit allowing usage of circe decoders without EntityDecoder definition
  • Loading branch information
rossabaker committed Jun 26, 2018
2 parents c062219 + 4d73de7 commit a53dbb1
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 8 deletions.
3 changes: 3 additions & 0 deletions circe/src/main/scala/org/http4s/circe/CirceEntityCodec.scala
@@ -0,0 +1,3 @@
package org.http4s.circe

object CirceEntityCodec extends CirceEntityDecoder with CirceEntityEncoder
14 changes: 14 additions & 0 deletions circe/src/main/scala/org/http4s/circe/CirceEntityDecoder.scala
@@ -0,0 +1,14 @@
package org.http4s.circe

import cats.effect.Sync
import io.circe.Decoder
import org.http4s.EntityDecoder

/**
* Derive [[EntityDecoder]] if implicit [[Decoder]] is in the scope without need to explicitly call `jsonOf`
*/
trait CirceEntityDecoder {
implicit def circeEntityDecoder[F[_]: Sync, A: Decoder]: EntityDecoder[F, A] = jsonOf[F, A]
}

object CirceEntityDecoder extends CirceEntityDecoder
15 changes: 15 additions & 0 deletions circe/src/main/scala/org/http4s/circe/CirceEntityEncoder.scala
@@ -0,0 +1,15 @@
package org.http4s.circe

import cats.Applicative
import io.circe.Encoder
import org.http4s.EntityEncoder

/**
* Derive [[EntityEncoder]] if implicit [[Encoder]] is in the scope without need to explicitly call `jsonEncoderOf`
*/
trait CirceEntityEncoder {
implicit def circeEntityEncoder[F[_]: Applicative, A: Encoder]: EntityEncoder[F, A] =
jsonEncoderOf[F, A]
}

object CirceEntityEncoder extends CirceEntityEncoder
10 changes: 4 additions & 6 deletions circe/src/main/scala/org/http4s/circe/CirceInstances.scala
Expand Up @@ -79,23 +79,21 @@ trait CirceInstances {

protected def defaultPrinter: Printer

implicit def jsonEncoder[F[_]: EntityEncoder[?[_], String]: Applicative]: EntityEncoder[F, Json] =
implicit def jsonEncoder[F[_]: Applicative]: EntityEncoder[F, Json] =
jsonEncoderWithPrinter(defaultPrinter)

def jsonEncoderWithPrinter[F[_]: EntityEncoder[?[_], String]: Applicative](
printer: Printer): EntityEncoder[F, Json] =
def jsonEncoderWithPrinter[F[_]: Applicative](printer: Printer): EntityEncoder[F, Json] =
EntityEncoder[F, Chunk[Byte]]
.contramap[Json] { json =>
val bytes = printer.prettyByteBuffer(json)
Chunk.byteBuffer(bytes)
}
.withContentType(`Content-Type`(MediaType.application.json))

def jsonEncoderOf[F[_]: EntityEncoder[?[_], String]: Applicative, A](
implicit encoder: Encoder[A]): EntityEncoder[F, A] =
def jsonEncoderOf[F[_]: Applicative, A](implicit encoder: Encoder[A]): EntityEncoder[F, A] =
jsonEncoderWithPrinterOf(defaultPrinter)

def jsonEncoderWithPrinterOf[F[_]: EntityEncoder[?[_], String]: Applicative, A](printer: Printer)(
def jsonEncoderWithPrinterOf[F[_]: Applicative, A](printer: Printer)(
implicit encoder: Encoder[A]): EntityEncoder[F, A] =
jsonEncoderWithPrinter[F](printer).contramap[A](encoder.apply)

Expand Down
14 changes: 14 additions & 0 deletions circe/src/test/scala/org/http4s/circe/CirceSpec.scala
Expand Up @@ -169,5 +169,19 @@ class CirceSpec extends JawnDecodeSupportSpec[Json] {
}
}

"CirceEntityEncDec" should {
"decode json without defining EntityDecoder" in {
import org.http4s.circe.CirceEntityDecoder._
val request = Request[IO]().withEntity(Json.obj("bar" -> Json.fromDoubleOrNull(42)))
val result = request.attemptAs[Foo]
result.value.unsafeRunSync must_== Right(Foo(42))
}

"encode without defining EntityEncoder using default printer" in {
import org.http4s.circe.CirceEntityEncoder._
writeToString(foo) must_== """{"bar":42}"""
}
}

checkAll("EntityCodec[IO, Json]", EntityCodecTests[IO, Json].entityCodec)
}
23 changes: 21 additions & 2 deletions docs/src/main/tut/json.md
Expand Up @@ -162,6 +162,18 @@ Ok(Hello("Alice").asJson).unsafeRunSync
POST(uri("/hello"), User("Bob").asJson).unsafeRunSync
```

If within some route we serve json only, we can use:

```tut:book
{
import org.http4s.circe.CirceEntityEncoder._
}
```

Thus there's no more need in calling `asJson` on result.
However, it may introduce ambiguity errors when we also build
some json by hand within the same scope.

## Receiving raw JSON

Just as we needed an `EntityEncoder[JSON]` to send JSON from a server
Expand Down Expand Up @@ -194,10 +206,10 @@ POST(uri("/hello"), """{"name":"Bob"}""").flatMap(_.as[User]).unsafeRunSync
```

If we are always decoding from JSON to a typed model, we can use
the following one-liner:
the following import:

```tut:book
implicit def decoders[F[_]: Sync, A: Decoder]: EntityDecoder[F, A] = jsonOf[F, A]
import org.http4s.circe.CirceEntityDecoder._
```

This creates an `EntityDecoder[A]` for every `A` that has a `Decoder` instance.
Expand All @@ -206,6 +218,13 @@ However, be cautious when using this. Having this implicit
in scope does mean that we would always try to decode HTTP entities
from JSON (even if it is XML or plain text, for instance).

For more convenience there is import combining both encoding
and decoding derivation:

```tut:book
import org.http4s.circe.CirceEntityCodec._
```

## Putting it all together

### A Hello world service
Expand Down

0 comments on commit a53dbb1

Please sign in to comment.