Type-Level Web Apis with Servant an Exercise in Domain-Specific Generic Programming

Type-Level Web Apis with Servant an Exercise in Domain-Specific Generic Programming

Type-level Web APIs with Servant An exercise in domain-specific generic programming Alp Mestanogullari Sönke Hahn, Julian K. Arni Andres Löh The Kitty Knows Ltd Zalora SEA Well-Typed LLP [email protected] {soenke.hahn, julian.arni}@zalora.com [email protected] Abstract makes it difficult to say that two different servers implement the We describe the design and motivation for Servant, an extensible, same API, or make precise the differences in the API they imple- ment. We also cannot easily implement a client for this API and type-level DSL for describing Web APIs. Servant APIs are Haskell express in the type system that the API of the client matches that of types. An API type can be interpreted in several different ways: as a server that processes requests, interprets them and dispatches the server. them to appropriate handlers; as a client that can correctly query There are also other, more general-purpose, API description lan- guages. However, they suffer from their own set of disadvantages. the endpoints of the API; as systematic documentation for the API; They often lack the abstraction capabilities that a DSL embedded and more. Servant is fully extensible: the API language can be augmented with new constructs, and new interpretations can be into a language like Haskell can offer. Also, because such descrip- defined. The key Haskell features making all this possible are data tions are completely detached from an implementation, it can be a kinds, (open) type families and (open) type classes. The techniques lot of work to establish that an implemented server (or client) actu- we use are reminiscent of general-purpose generic programming. ally conforms to the described API. However, where most generic programming libraries are interested With Servant, we make APIs first-class citizens that are indepen- in automatically deriving programs for a large class of datatypes dent of, but can be checked against their implementation(s). This from many different domains, we are only interested in a small allows us to compare different implementations of a single API, to express that a client and a server are API-compatible, and to perform class of datatypes that is used in the DSL for describing APIs. many other interesting operations on APIs. 1. Introduction 1.1 An introductory example The interface of a web application is described by its API. For Here is the API of a minimalistic “echo” service in Servant: example, an HTTP API describes what kind of requests are accepted type Echo= "echo" by an application, what constraints are being imposed on the URLs, :> ReqBody’[PlainText] String the request headers, the body of input requests, and what kind of :> Get’[PlainText] String responses can be expected. 1 In this paper, we introduce Servant, an extensible type-level DSL The Haskell type Echo describes an API that accepts only GET for Haskell that aims at making web APIs first-class. A Servant web requests directed at the route /echo. It expects a request body of API is just a Haskell type. It can be named, passed to or returned by content type text/plain, and produces a result of the same content (type) functions, composed, exported by modules, and more. type. In the Haskell world, the content will be treated as a value of There are already quite a number of other web DSLs that allow type String. the concise description of a web server: the program is structured The challenge we want to address in Servant is not just promot- in such a way that the API can be easily read off the way in which ing APIs to first-class entities, but to do so in a way that is both the handler functions are written and structured. Sometimes, this type-safe and extensible. approach is even taken further, and the server is augmented with By type safety, we mean that we want to be able to have the additional information such that API documentation can be gener- compiler determine statically whether our code conforms to the ated automatically from the same code. However, there are limits API. For example, a server implementing the Echo API really gets a to this approach: the API here exists implicitly and is determined by String as input, and has to return a String as output. Defining the the server implementation; the documentation and the server imple- code and having it type-checked could not be any simpler: mentation are mangled together and cannot easily be separated; it handleEcho:: Server Echo is difficult to extend the language. For example, such an approach handleEcho txt = return txt Here, the type Server Echo expands to (essentially) String -> IO String. By extensibility, we mean two different aspects: First, the API description language itself is not closed. Servant defines operations such as :>, ReqBody, Get and many more, but if that is not suffi- 1 In Servant, we make use of a number of Haskell extensions that GHC implements. The string "echo" is a type-level string. The syntax ’[PlainText] denotes a one-element type-level list, not the type of lists. Both are made available by the DataKinds GHC extension. And :> is an [Copyright notice will appear here once ’preprint’ option is removed.] infix operator on the type-level, requiring TypeOperators. 1 2015/5/23 cient, then it is easy to define new constructs without touching the in development. Servant is used in production right now at Zalora core library. Next to the obvious advantages in structuring the code and by several other users. of Servant itself, this also lifts some pressure from the developers: users of Servant who lack a feature can likely implement it on their 2. Using Servant own, without having to change Servant itself. Second, the number of interpretations associated with an API In the introduction, we’ve provided a very simple example of an is also open. Servant itself comes with quite a number of inter- “echo” server API and its implementation. In this section, we are pretations already: we can supply suitable handlers to run a web going to look at a slightly more involved example, and explain in service conforming to the API; we can generate clients in two dif- a bit more detail how the existing features of Servant can be put to ferent ways, either as a number of Haskell functions that can be use. We are not yet concerned with how everything works internally used as part of a larger client application, or as JavaScript functions – this will be covered in Sections 3 to 6. that can be used for quickly testing the API interactively from the 2.1 Stepping a counter browser; there is the option to generate Markdown documentation of the API; we can generate type-safe hyperlinks for routes within For a start, consider the following informally specified API: an API. But there are many other things we can imagine doing with GET / obtain the current value of a counter web APIs: generating automatic test suites, generating documenta- POST /step increment the current value of said counter tion in the format expected by general-purpose API specification In Servant, one way to specify this API is as follows: languages, generating servers for other low-level HTTP server im- plementations and many more. With Servant, adding these is easily newtype CounterVal= CounterVal{getCounterVal:: Int} possible. deriving (Show, Num, FromJSON, ToJSON) The challenge of achieving extensiblity in these two ways is type GetCounter= Get’[JSON] CounterVal commonly known as the expression problem (Wadler 1998), and type StepCounter= "step" :> Post ’[] () the implementation of Servant is a solution of one particular in- type Counter= GetCounter :<|> StepCounter stance of this problem. We introduce a newtype for counter values to provide some extra 1.2 Implementation techniques type safety. (Such semantic typing is also useful for implementing The techniques we are using in order to implement Servant are rem- type class instances that should only apply to CounterVal, and iniscent of datatype-generic programming (Rodriguez et al. 2008). not to Int in general.) We derive several type classes that Int is CounterVal We use our API types as codes in a universe, and have several inter- a member of for . pretations of such codes as various other types as well as associated The GetCounter type specifies the GET part of the API, as generic functions on them. However, where datatype-generic pro- indicated by the use of Get. It is accessible via route /. The result gramming usually aims at representing as many datatypes as possi- is a Haskell CounterVal, which will be transformed to a JSON ble via codes, Servant is deliberately domain-specific: we are only number. The StepCounter type is for the POST part, as indicated by interested in a very limited (yet extensible) set of codes. the use of Post. This part is accessible under the route /step, and Furthermore, we are using quite a number of advanced features it returns an empty response. In Counter, both routes are combined of the Haskell type system: type operators to provide a concise into a single API by means of :<|> (which is provided by Servant). look-and-feel to the API language; strings on the type level (in 2.2 Generating documentation other words, data kinds and kind polymorphism); most essentially, we use (open) type families and (open) type classes to obtain the If you are implementing a web service, it is very convenient to have exensibility we so desire. up-to-date documentation of its API. With Servant, we can generate We believe that Servant provides evidence that these techniques documentation directly from an API, and augment it as needed with are feasible for solving the expression problem and designing type- additional information.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    12 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us