Add json body to http4s Request
Asked Answered
M

2

15

This tut shows how to create an http4s Request: https://http4s.org/v0.18/dsl/#testing-the-service

I would like to change this request to a POST method and add a literal json body using circe. I tried the following code:

val body = json"""{"hello":"world"}"""
val req = Request[IO](method = Method.POST, uri = Uri.uri("/"), body = body)

This gives me a type mismatch error:

[error]  found   : io.circe.Json
[error]  required: org.http4s.EntityBody[cats.effect.IO]
[error]     (which expands to)  fs2.Stream[cats.effect.IO,Byte]
[error]     val entity: EntityBody[IO] = body

I understand the error, but I cannot figure out how to convert io.circe.Json into an EntityBody. Most examples I have seen use an EntityEncoder, which does not provide the required type.

How can I convert io.circe.Json into an EntityBody?

Mosier answered 9/4, 2018 at 5:36 Comment(2)
I think that's exactly what's covered here: http4s.org/v0.18/jsonHershey
That page is showing how to use EntityEncoders to create a cats.effect.IO[org.http4s.Entity[cats.effect.IO]] type.Mosier
G
12

Oleg's link mostly covers it, but here's how you'd do it for a custom request body:

import org.http4s.circe._

val body = json"""{"hello":"world"}"""
val req = Request[IO](method = Method.POST, uri = Uri.uri("/"))
  .withBody(body)
  .unsafeRunSync()

Explanation:

The parameter body on the request class is of type EntityBody[IO] which is an alias for Stream[IO, Byte]. You can't directly assign a String or Json object to it, you need to use the withBody method instead.

withBody takes an implicit EntityEncoder instance, so your comment about not wanting to use an EntityEncoder doesn't make sense - you have to use one if you don't want to create a byte stream yourself. However, the http4s library has predefined ones for a number of types, and the one for type Json lives in org.http4s.circe._. Hence the import statement.

Lastly, you need to call .unsafeRunSync() here to pull out a Request object because withBody returns an IO[Request[IO]]. The better way to handle this would of course be via chaining the result with other IO operations.

Goon answered 10/4, 2018 at 8:41 Comment(1)
I edited my question to make it clear that it was not that I did not want to use an EntityEncoder, but that it was not providing the required type.Mosier
K
13

As of http4s 20.0, withEntity overwrites the existing body (which defaults to empty) with the new body. The EntityEncoder is still required, and can be found with an import of org.http4s.circe._:

import org.http4s.circe._

val body = json"""{"hello":"world"}"""

val req = Request[IO](
  method = Method.POST,
  uri = Uri.uri("/")
)
.withEntity(body)
Kingsize answered 25/4, 2019 at 23:0 Comment(0)
G
12

Oleg's link mostly covers it, but here's how you'd do it for a custom request body:

import org.http4s.circe._

val body = json"""{"hello":"world"}"""
val req = Request[IO](method = Method.POST, uri = Uri.uri("/"))
  .withBody(body)
  .unsafeRunSync()

Explanation:

The parameter body on the request class is of type EntityBody[IO] which is an alias for Stream[IO, Byte]. You can't directly assign a String or Json object to it, you need to use the withBody method instead.

withBody takes an implicit EntityEncoder instance, so your comment about not wanting to use an EntityEncoder doesn't make sense - you have to use one if you don't want to create a byte stream yourself. However, the http4s library has predefined ones for a number of types, and the one for type Json lives in org.http4s.circe._. Hence the import statement.

Lastly, you need to call .unsafeRunSync() here to pull out a Request object because withBody returns an IO[Request[IO]]. The better way to handle this would of course be via chaining the result with other IO operations.

Goon answered 10/4, 2018 at 8:41 Comment(1)
I edited my question to make it clear that it was not that I did not want to use an EntityEncoder, but that it was not providing the required type.Mosier

© 2022 - 2024 — McMap. All rights reserved.