7

Here is the code to bind request param to the router.

val testReader: Endpoint[Test] = Endpoint.derive[Test].fromParams
val test: Endpoint[String] = post("test" ? testReader) { t : Test => {
    Created("OK")
  }}

I am using the method fromParams. This method can bind request parameters in a very cool way. However, I dont konw which similiar way I can bind request body in the finch

Many thanks in advance

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
Xiaohe Dong
  • 4,953
  • 6
  • 24
  • 53

1 Answers1

9

For the sake of providing a complete working example I'll assume a case class like this:

case class Test(foo: Int, bar: String)

And some requests like this:

import com.twitter.finagle.http.{ Method, Request, RequestBuilder }
import com.twitter.io.{ Buf, Reader }

val queryParamPost = Request(Method.Post, "/test?foo=1&bar=whatever")

val testJsonBuf = Buf.Utf8("""{ "foo": 1, "bar": "whatever" }""")

val bodyPost = RequestBuilder().url("http://localhost:8080/test").buildPost(testJsonBuf)

Now when you write the following…

import io.finch._

val testParams: Endpoint[Test] = Endpoint.derive[Test].fromParams
val test: Endpoint[Test] = post("test" ? testParams) { test: Test =>
  Created(test)
}

What's happening is that Finch is using generic derivation (powered by Shapeless) to determine (at compile time) how to parse the query params as a Test. You can then test the endpoint like this:

import io.finch.circe._
import io.circe.generic.auto._

test.toService.apply(queryParamPost).onSuccess { response =>
  println(s"$response: ${ response.contentString }")
}

Which will print:

Response("HTTP/1.1 Status(201)"): {"foo":1,"bar":"whatever"}

Here I'm using Circe's generic derivation to automatically encode the "created" Test as JSON for the response.

You can also use Circe to derive a reader for the request body:

val testBody: Endpoint[Test] = body.as[Test]
val test2: Endpoint[Test] = post("test" :: testBody) { test: Test =>
  Created(test)
}

This is almost exactly the same as test above, but we're using body to get an Endpoint[String] that will read the request body and then as to specify that we want the content parsed as JSON and decoded as a Test value. We can test this new version like this:

test2.toService.apply(bodyPost).onSuccess { response =>
  println(s"$response: ${ response.contentString }")
}

And we'll get the answer we expect again.

In general when you want to read a certain kind of information of an incoming request, you'll use one of the basic Endpoints provided by Finch (see the docs for a more complete list), and then use methods like as, map, etc. on the Endpoint to turn it into the shape you need.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • I didnt realize /, ? and :: are actually doing the same thing :) – Xiaohe Dong May 01 '16 at 23:55
  • @Travis Brown Can you edit your answer and add a short example with custom decoder? In my use case, `Test` has a field of type `scalaz.Maybe`. Having it as `Option` works, but how can I make it work with `Maybe` (which I prefer over `Option` for a couple of reasons)? I keep getting "Attempt to decode value on failed cursor". – slouc Feb 13 '17 at 13:14
  • @slouc That's probably worth a new question (which I'd be happy to answer). I'd probably define a single generic instance for `scalaz.Maybe[A: Decoder]`, but need more detail. – Travis Brown Feb 13 '17 at 14:46
  • @Travis Brown Yeah, I figured it should probably be a new question, but I was a bit lazy. Anyways, I managed to solve it in the meantime. I posted a question and answered it myself; not sure if SO admins consider this ok or not, but if it's ok then let's keep it for others. Please feel free to add something from your side: http://stackoverflow.com/questions/42209195/circe-decoder-for-scalaz-maybe/42209211#42209211 – slouc Feb 13 '17 at 16:48