Search code examples
web-servicesscalafinaglefinch

how to bind request body in Finch


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


Solution

  • 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.