Here's a suggestion: design the SDK such that calling a method returns you everything you need to construct the REST call yourself: the verb, the url, the query params, the form params, and perhaps some headers.
case class HttpCall[OutputType](
verb: String,
url: String,
queryParams: List[(String,String)],
formParams: List[(String,String)],
resultType: Class[OutputType])
trait Api {
def getMyTasks(): HttpCall[List[Task]]
def getTask(taskId: String): HttpCall[Task]
}
The SDK can also provide a driver for making the http calls: trait Driver {
def call[Out](httpCall: HttpCall[Out]): Out
}
So now api invocations look like this: val myTasks: List[Task] = driver.call(api.getMyTasks())
val aTask: Task = driver.call(api.getTask("foo"))
What's nice about this design is that the consumer can write their own driver if they don't like yours. Prefer asynchronous over synchronous? No problem! trait Driver {
def call[Out](httpCall: HttpCall[Out]): Future[Out]
}
Everything in the article except for grep-ability can be achieved with this kind of design.