Building REST Clients

Previously, we looked at how featherbed’s request specifications are evaluated lazily, and can be re-used to generate multiple requests of the same specification. In this segment, we’ll see how this functionality can be leveraged to build resource-oriented client APIs for a REST service.

As an example, we’ll build a Scala client for a subset of the JSONPlaceholder API. The JSONPlaceholder defines the following data types:

case class Post(userId: Int, id: Int, title: String, body: String)
// defined class Post

case class Comment(postId: Int, id: Int, name: String, email: String, body: String)
// defined class Comment

We need the usual imports:

import featherbed.circe._
// import featherbed.circe._

import io.circe.generic.auto._
// import io.circe.generic.auto._

import shapeless.Coproduct
// import shapeless.Coproduct

import com.twitter.util.Await
// import com.twitter.util.Await

import java.net.URL
// import java.net.URL

And we can define a class for our API client:

class JSONPlaceholderAPI(baseUrl: URL) {
  import featherbed.circe._
  import io.circe.generic.auto._

  private val client = new featherbed.Client(baseUrl)

  object posts {

    private val listRequest = client.get("posts").accept("application/json")
    private val getRequest = (id: Int) => client.get(s"posts/$id").accept("application/json")

    def list() = listRequest.send[Seq[Post]]()
    def get(id: Int) = getRequest(id).send[Post]()

  }

  object comments {
    private val listRequest = client.get("comments").accept("application/json")
    private val getRequest = (id: Int) => client.get(s"comments/$id").accept("application/json")

    def list() = listRequest.send[Seq[Comment]]()
    def get(id: Int) = getRequest(id).send[Comment]()
  }
}
// defined class JSONPlaceholderAPI

val apiClient = new JSONPlaceholderAPI(new URL("http://jsonplaceholder.typicode.com/"))
// apiClient: JSONPlaceholderAPI = JSONPlaceholderAPI@3ac689dd

Await.result(apiClient.posts.list())
// res0: Seq[Post] =
// Vector(Post(1,1,sunt aut facere repellat provident occaecati excepturi optio reprehenderit,quia et suscipit
// suscipit recusandae consequuntur expedita et cum
// reprehenderit molestiae ut ut quas totam
// nostrum rerum est autem sunt rem eveniet architecto), Post(1,2,qui est esse,est rerum tempore vitae
// sequi sint nihil reprehenderit dolor beatae ea dolores neque
// fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis
// qui aperiam non debitis possimus qui neque nisi nulla), Post(1,3,ea molestias quasi exercitationem repellat qui ipsa sit aut,et iusto sed quo iure
// voluptatem occaecati omnis eligendi aut ad
// voluptatem doloribus vel accusantium quis pariatur
// molestiae porro eius odio et labore et velit aut), Post(1,4,eum et est occaecati,ulla...

Await.result(apiClient.posts.get(1))
// res1: Post =
// Post(1,1,sunt aut facere repellat provident occaecati excepturi optio reprehenderit,quia et suscipit
// suscipit recusandae consequuntur expedita et cum
// reprehenderit molestiae ut ut quas totam
// nostrum rerum est autem sunt rem eveniet architecto)