tutorial: read through ApiType.lhs

This commit is contained in:
Sönke Hahn 2016-02-23 13:42:48 +01:00
parent 140da7a7b0
commit 434c163aa1
2 changed files with 35 additions and 42 deletions

View file

@ -8,8 +8,9 @@ then using those API specifications to:
- write servers (this part of **servant** can be considered a web framework), - write servers (this part of **servant** can be considered a web framework),
- obtain client functions (in haskell), - obtain client functions (in haskell),
- generate client functions for other programming languages and - generate client functions for other programming languages,
- generate documentation for your web applications. - generate documentation for your web applications
- and more...
All in a type-safe manner. All in a type-safe manner.

View file

@ -24,8 +24,8 @@ You *should* be able to formalize that. And then use the formalized version to
get you much of the way towards writing a web app. And all the way towards get you much of the way towards writing a web app. And all the way towards
getting some client libraries, and documentation, and more. getting some client libraries, and documentation, and more.
How would we describe it with servant? As mentioned earlier, an endpoint How would we describe it with **servant**? An endpoint description is a good old
description is a good old Haskell **type**: Haskell **type**:
``` haskell ``` haskell
type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User] type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
@ -66,15 +66,14 @@ type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
:<|> "list-all" :> "users" :> Get '[JSON] [User] :<|> "list-all" :> "users" :> Get '[JSON] [User]
``` ```
*servant* provides a fair amount of combinators out-of-the-box, but you can **servant** provides a fair amount of combinators out-of-the-box, but you can
always write your own when you need it. Here's a quick overview of all the always write your own when you need it. Here's a quick overview of the most
combinators that servant comes with. often needed the combinators that **servant** comes with.
## Combinators ## Combinators
### Static strings ### Static strings
As you've already seen, you can use type-level strings (enabled with the As you've already seen, you can use type-level strings (enabled with the
`DataKinds` language extension) for static path fragments. Chaining `DataKinds` language extension) for static path fragments. Chaining
them amounts to `/`-separating them in a URL. them amounts to `/`-separating them in a URL.
@ -87,7 +86,6 @@ type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
### `Delete`, `Get`, `Patch`, `Post` and `Put` ### `Delete`, `Get`, `Patch`, `Post` and `Put`
The `Get` combinator is defined in terms of the more general `Verb`: The `Get` combinator is defined in terms of the more general `Verb`:
``` haskell ignore ``` haskell ignore
data Verb method (statusCode :: Nat) (contentType :: [*]) a data Verb method (statusCode :: Nat) (contentType :: [*]) a
@ -120,15 +118,14 @@ type UserAPI4 = "users" :> Get '[JSON] [User]
### `Capture` ### `Capture`
URL captures are segments of the path of a URL that are variable and whose actual value is
URL captures are parts of the URL that are variable and whose actual value is
captured and passed to the request handlers. In many web frameworks, you'll see captured and passed to the request handlers. In many web frameworks, you'll see
it written as in `/users/:userid`, with that leading `:` denoting that `userid` it written as in `/users/:userid`, with that leading `:` denoting that `userid`
is just some kind of variable name or placeholder. For instance, if `userid` is is just some kind of variable name or placeholder. For instance, if `userid` is
supposed to range over all integers greater or equal to 1, our endpoint will supposed to range over all integers greater or equal to 1, our endpoint will
match requests made to `/users/1`, `/users/143` and so on. match requests made to `/users/1`, `/users/143` and so on.
The `Capture` combinator in servant takes a (type-level) string representing The `Capture` combinator in **servant** takes a (type-level) string representing
the "name of the variable" and a type, which indicates the type we want to the "name of the variable" and a type, which indicates the type we want to
decode the "captured value" to. decode the "captured value" to.
@ -155,17 +152,16 @@ type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
In the second case, `DeleteNoContent` specifies a 204 response code, In the second case, `DeleteNoContent` specifies a 204 response code,
`JSON` specifies the content types on which the handler will match, `JSON` specifies the content types on which the handler will match,
and `NoContent` is a Haskell type isomorphic to `()` used to represent and `NoContent` says that the response will always be empty.
a trivial piece of information.
### `QueryParam`, `QueryParams`, `QueryFlag` ### `QueryParam`, `QueryParams`, `QueryFlag`
`QueryParam`, `QueryParams` and `QueryFlag` are about query string `QueryParam`, `QueryParams` and `QueryFlag` are about parameters in the query string,
parameters, i.e., those parameters that come after the question mark i.e., those parameters that come after the question mark
(`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is (`?`) in URLs, like `sortby` in `/users?sortby=age`, whose value is
set to `age`. `QueryParams` lets you specify that the query parameter set to `age`. `QueryParams` lets you specify that the query parameter
is actually a list of values, which can be specified using is actually a list of values, which can be specified using
`?param[]=value1&param[]=value2`. This represents a list of values `?param=value1&param=value2`. This represents a list of values
composed of `value1` and `value2`. `QueryFlag` lets you specify a composed of `value1` and `value2`. `QueryFlag` lets you specify a
boolean-like query parameter where a client isn't forced to specify a boolean-like query parameter where a client isn't forced to specify a
value. The absence or presence of the parameter's name in the query value. The absence or presence of the parameter's name in the query
@ -190,7 +186,7 @@ type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
``` ```
Again, your handlers don't have to deserialize these things (into, for example, Again, your handlers don't have to deserialize these things (into, for example,
a `SortBy`). *servant* takes care of it. a `SortBy`). **servant** takes care of it.
### `ReqBody` ### `ReqBody`
@ -201,9 +197,9 @@ users: instead of passing each field of the user as a separate query string
parameter or something dirty like that, we can group all the data into a JSON parameter or something dirty like that, we can group all the data into a JSON
object. This has the advantage of supporting nested objects. object. This has the advantage of supporting nested objects.
*servant*'s `ReqBody` combinator takes a list of content types in which the **servant**'s `ReqBody` combinator takes a list of content types in which the
data encoded in the request body can be represented and the type of that data. data encoded in the request body can be represented and the type of that data.
And, as you might have guessed, you don't have to check the content-type And, as you might have guessed, you don't have to check the content type
header, and do the deserialization yourself. We do it for you. And return `Bad header, and do the deserialization yourself. We do it for you. And return `Bad
Request` or `Unsupported Content Type` as appropriate. Request` or `Unsupported Content Type` as appropriate.
@ -231,12 +227,11 @@ type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
### Request `Header`s ### Request `Header`s
Request headers are used for various purposes, from caching to carrying Request headers are used for various purposes, from caching to carrying
auth-related data. They consist of a header name and an associated value. An auth-related data. They consist of a header name and an associated value. An
example would be `Accept: application/json`. example would be `Accept: application/json`.
The `Header` combinator in servant takes a type-level string for the header The `Header` combinator in **servant** takes a type-level string for the header
name and the type to which we want to decode the header's value (from some name and the type to which we want to decode the header's value (from some
textual representation), as illustrated below: textual representation), as illustrated below:
@ -255,10 +250,10 @@ type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
### Content types ### Content types
So far, whenever we have used a combinator that carries a list of content So far, whenever we have used a combinator that carries a list of content
types, we've always specified `'[JSON]`. However, *servant* lets you use several types, we've always specified `'[JSON]`. However, **servant** lets you use several
content types, and also lets you define your own content types. content types, and also lets you define your own content types.
Four content-types are provided out-of-the-box by the core *servant* package: Four content types are provided out-of-the-box by the core **servant** package:
`JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure `JSON`, `PlainText`, `FormUrlEncoded` and `OctetStream`. If for some obscure
reason you wanted one of your endpoints to make your user data available under reason you wanted one of your endpoints to make your user data available under
those 4 formats, you would write the API type as below: those 4 formats, you would write the API type as below:
@ -267,18 +262,18 @@ those 4 formats, you would write the API type as below:
type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User] type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
``` ```
We also provide an HTML content-type, but since there's no single library (There are other packages that provide other content types. For example
that everyone uses, we decided to release 2 packages, *servant-lucid* and **servant-lucid** and **servant-blaze** allow to generate html pages (using
*servant-blaze*, to provide HTML encoding of your data. **lucid** and **blaze-html**) and both come with a content type for html.)
We will further explain how these content types and your data types can play We will further explain how these content types and your data types can play
together in the [section about serving an API](/tutorial/server.html). together in the [section about serving an API](Server.html).
### Response `Headers` ### Response `Headers`
Just like an HTTP request, the response generated by a webserver can carry Just like an HTTP request, the response generated by a webserver can carry
headers too. *servant* provides a `Headers` combinator that carries a list of headers too. **servant** provides a `Headers` combinator that carries a list of
`Header` and can be used by simply wrapping the "return type" of an endpoint `Header` types and can be used by simply wrapping the "return type" of an endpoint
with it. with it.
``` haskell ignore ``` haskell ignore
@ -292,11 +287,12 @@ response, you could write it as below:
type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User]) type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
``` ```
### Interoperability with other WAI `Application`s: `Raw` ### Interoperability with `wai`: `Raw`
Finally, we also include a combinator named `Raw` that can be used for two reasons: Finally, we also include a combinator named `Raw` that provides an escape hatch
to the underlying low-level web library `wai`. It can be used when
- You want to serve static files from a given directory. In that case you can just say: you want to plug a [wai `Application`](http://hackage.haskell.org/package/wai)
into your webservice:
``` haskell ``` haskell
type UserAPI11 = "users" :> Get '[JSON] [User] type UserAPI11 = "users" :> Get '[JSON] [User]
@ -309,11 +305,7 @@ type UserAPI11 = "users" :> Get '[JSON] [User]
-- at the right path -- at the right path
``` ```
- You more generally want to plug a [WAI `Application`](http://hackage.haskell.org/package/wai) One example for this is if you want to serve a directory of static files along
into your webservice. Static file serving is a specific example of that. The API type would look the with the rest of your API. But you can plug in everything that is an
same as above though. (You can even combine *servant* with other web frameworks `Application`, e.g. a whole web application written in any of the web
this way!) frameworks that support `wai`.
<div style="text-align: center;">
<a href="/tutorial/server.html">Next page: Serving an API</a>
</div>