Add json body to http4s Request - scala

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?

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.

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)

Related

Scala, Hammock - retrieve http response headers and convert JSON to custom object

I have created a simple program which use Hammock(https://github.com/pepegar/hammock) and now I would like to get response from github API with reposne's headers. I created a code like this:
object GitHttpClient extends App {
implicit val decoder = jsonOf[IO, List[GitRepository]]
implicit val interpreter = ApacheInterpreter.instance[IO]
val response = Hammock
.request(Method.GET, uri"https://api.github.com/orgs/github/repos?per_page=3", Map())
.as[List[GitRepository]]
.exec[IO]
.unsafeRunSync()
println(response)
}
case class GitRepository(full_name: String, contributors_url: String)
And it works fine, I got Git data mapped to my object. But now I also want to get headers from response and I cannot do this by simple response.headers. Only when I remove .as[List[GitRepository]] line and have whole HttpResponse I could access headers. Is it possible to get headers without parsing whole HttpResponse?
I solved this problem by using Decoder after received reponse:
val response = Hammock
.request(Method.GET, uri"https://api.github.com/orgs/github/repos?per_page=3", Map())
.exec[IO]
.unsafeRunSync()
println(response.headers("Link") contains ("next"))
println(HammockDecoder[List[GitRepository]].decode(response.entity))

Cannot mock WSRequest.post() using scalamock

I am writing unit tests for Play application using Scalamock and Scalatest.
My original code looks like:
// Here ws is an injected WSClient
val req = Json.toJson(someRequestObject)
val resp: Future[WSResponse] = ws.url(remoteURL).post(Json.toJson(req))
In a part I have to mock external calls to a web service, which I am trying to do using scalamock:
ws = stub[WSClient]
wsReq = stub[WSRequest]
wsResp = stub[WSResponse]
ws.url _ when(*) returns wsReq
wsReq.withRequestTimeout _ when(*) returns wsReq
(wsReq.post (_: java.io.File)).when(*) returns Future(wsResp)
I am successfully able to mock post requests using a file, but I cannot mock post requests using JSON.
I tried putting stub function references separately like:
val f: StubFunction1[java.io.File, Future[WSResponse]] = wsReq.post (_: java.io.File)
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)
I get the compile error for second line: Unable to resolve overloaded method post
What am I missing here? Why cannot I mock one overloaded method but not the other one?
play.api.libs.ws.WSRequest has two post methods (https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.libs.ws.WSRequest), taking:
File
T (where T has an implicit bounds on Writeable)
The compiler is failing because you are trying to calling post with a single parameter, which only matches version 1. However, JsValue cannot be substituted with File.
You actually want to call the 2nd version, but this is a curried method that takes two sets of parameters (albeit the 2nd are implicit). Therefore you need to explicitly provide the mock value that you expect for the implicit, i.e.
val j: StubFunction1[JsValue, Future[WSResponse]] = wsReq.post(_: JsValue)(implicitly[Writeable[JsValue]])
Therefore a working solution would be:
(wsReq.post(_)(_)).when(*) returns Future(wsResp)
Old answer:
WSRequest provides 4 overloads of post method (https://www.playframework.com/documentation/2.5.8/api/java/play/libs/ws/WSRequest.html), taking:
String
JsonNode
InputStream
File
You can mock with a File because it matches overload 4, but JsValue does not match (this is part of the Play JSON model, whereas JsonNode is part of the Jackson JSON model). If you convert to a String or JsonNode, then it will resolve the correct overload and compile.
My best guess is that your WSRequest is actually a play.libs.ws.WSRequest which is part of the Java API, instead you should use play.api.libs.ws.WSRequest which is the Scala API.
The method WSRequest.post exists and BodyWritable[JsValue] is implicitly provided by WSBodyWritables in the Scala API but not in the Java API.
Another cause could be that your JsValue is not a play.api.libs.json.JsValue but something else (e.g. spray.json.JsValue).
I'll quote an example where I have successfully achieved what you are trying to do, the main difference is that I used mock instead of stub.
The important part is:
val ws = mock[WSClient]
val responseBody = "{...}"
...
"availableBooks" should {
"retrieve available books" in {
val expectedBooks = "BTC_DASH ETH_DASH USDT_LTC BNB_LTC".split(" ").map(Book.fromString).map(_.get).toList
val request = mock[WSRequest]
val response = mock[WSResponse]
val json = Json.parse(responseBody)
when(ws.url(anyString)).thenReturn(request)
when(response.status).thenReturn(200)
when(response.json).thenReturn(json)
when(request.get()).thenReturn(Future.successful(response))
whenReady(service.availableBooks()) { books =>
books.size mustEqual expectedBooks.size
books.sortBy(_.string) mustEqual expectedBooks.sortBy(_.string)
}
}
}
An you could see the complete test at: BinanceServiceSpec
I guess it should work fine, if you mock a response that is JsValue.
when(wsReq.post(Json.parse("""{...json request...}"""))).thenReturn(Future(wsResp))
Here Json.parse returns JsValue. Yo should pass the json string that you expect in the request body.

How to use akka-http-circe on client side calls?

I'm trying to do a simple REST API call using akka-http, circe and akka-http-json (akka-http-circe in particular).
import io.circe.generic.auto._
object Blah extends FailFastCirceSupport {
//...
val fut: Future[Json] = Http().singleRequest(HttpRequest(uri = uri))
I'm expecting akka-http-circe to figure out how to unmarshal a HttpResponse to my wanted type (here, just Json). But it doesn't compile.
So I looked at some documentation and samples around, and tried this:
val fut: Future[Json] = Http().singleRequest(HttpRequest(uri = uri)).flatMap(Unmarshal(_).to)
Gives: type mismatch; found 'Future[HttpResponse]', required 'Future[Json]'. How should I expose an unmarshaller for the fetch?
Scala 2.12.4, akka-http 10.0.10, akka-http-circe 1.18.0
The working code seems to be:
val fut: Future[Json] = Http().singleRequest(HttpRequest(uri = uri)).flatMap(Unmarshal(_).to[Json])
When the target type is explicitly given, the compiler finds the necessary unmarshallers and this works. It works equally with a custom type in place of Json.
Further question: Is this the best way to do it?

Scala Play 2.5 Form bindFromRequest: Cannot find any HTTP Request here?

I have one controller action implemented like this:
def doChangePassword = deadbolt.Restrict(List(Array(Application.USER_ROLE_KEY)))()
{ request => // <<<<<<<<<<<< here is the request
Future {
val context = JavaHelpers.createJavaContext(request)
com.feth.play.module.pa.controllers.AuthenticateBase.noCache(context.response())
val filledForm = Account.PasswordChangeForm.bindFromRequest
// compilation error here, it can't see the request ^^^^^^^
if (filledForm.hasErrors) {
// User did not select whether to link or not link
BadRequest(views.html.account.password_change(userService, filledForm))
} else {
val Some(user: UserRow) = userService.getUser(context.session)
val newPassword = filledForm.get.password
userService.changePassword(user, new MyUsernamePasswordAuthUser(newPassword), true)
Redirect(routes.Application.profile).flashing(
Application.FLASH_MESSAGE_KEY -> messagesApi.preferred(request)("playauthenticate.change_password.success")
)
}
}
}
the implementation above leads to the compilation error:
[error] /home/bravegag/code/play-authenticate-usage-scala/app/controllers/Account.scala:74: Cannot find any HTTP Request here
[error] val filledForm = Account.PasswordChangeForm.bindFromRequest
[error] ^
[error] one error found
However, if I change line 2 from:
{ request => // <<<<<<<<<<<< here is the request
to
{ implicit request => // <<<<<<<<<<<< here is the request
then it compiles ... but why?
What you are looking for are Implicit Parameters. In short:
Implicit Parameters can be passed just like regular or explicit parameters. In case you do not provide an implicit parameter explicitly, then the compiler will try to pass one for you. Implicits can come from various places. From the FAQ Where does Scala look for implicits?:
First look in current scope
Implicits defined in current scope
Explicit imports
wildcard imports
Now look at associated types in
Companion objects of a type
Implicit scope of an argument’s type (2.9.1)
Implicit scope of type arguments (2.8.0)
Outer objects for nested types
Other dimensions
Implicits under number 1. take precedence over those under number 2.
By marking request as implicit in your example, you are declaring an "Implicit defined in current scope". You need to have an implicit request in place because bindFormRequest "asks" you to pass one. See its signature:
bindFromRequest()(implicit request: Request[_]): Form[T]
Now that you have an implicit request in scope, the compiler will automatically pass it to bindFormRequerst.
As I mentioned in the beginning, you could also pass request explicitly:
val filledForm = Account.PasswordChangeForm.bindFromRequest()(request)
In the latter case there is no need to declare request as implicit as you are obviously passing request explicitly. Both variants are equal. It's up to you which one you prefer.
you need an implicit request in scope, like this: https://github.com/pedrorijo91/play-slick3-steps/blob/master/app/controllers/ApplicationController.scala#L11

Add element to JsValue?

I'm trying to add in a new element to a JsValue, but I'm not sure how to go about it.
val rJson = Json.parse(response)
val imgId = //stuff to get the id :Long
rJson.apply("imgId", imgId)
Json.stringify(rJson)
Should I be converting to a JSONObject or is there some method that can be applied directly to the JsValue to insert a new element to the JSON?
Edit:
response is coming from another server, but I do have control over it. So, if I need to add an empty "imgId" element to the JSON Object, that's fine.
You can do this as a JsObject, which extends JsValue and has a + method:
val rJson: JsValue = Json.parse(response)
val imgId = ...
val returnJson: JsObject = rJson.as[JsObject] + ("imgId" -> Json.toJson(imgId))
Json.stringify(returnJson)
I use the following helper in a project I'm working on:
/** Insert a new value at the given path */
def insert(path: JsPath, value: JsValue) =
__.json.update(path.json.put(value))
, which can be used as a JSON transformer as such:
val rJson = Json.parse(response)
val imgId = //stuff to get the id :Long
Json.stringify(rJson.transform(insert(__ \ 'imgId, imgId)))
You could definitely just use the body of that insert method, but I personally find the transformer API to be really counterintuitive.
The nice thing about this is that any number of transforms can be composed using andThen. We typically use this to convert API responses to the format of our models so that we can use Reads deserializers to instantiate model instances. We use this insert helper to mock parts of the API response that don't yet exist, so that we can build models we need ahead of the API.
Even though the API is convoluted, I highly, highly recommend investing some time in reading the Play framework docs on JSON handling, all 5 pages. They're not the world's greatest docs, but they actually are pretty thorough.