git.iko.soy/typeable/servant
on master help sign in
Remove bird-tracks
commit8b1bf02af843ef603a746191e87504ed89d02763 authorJulian K. Arni <jkarni@gmail.com> dateWed 27 Jan 2016, 22:28 (11 years ago) parent7af73d6
5 files changed+888−742
doc/tutorial/api-type.lhs+80−56 doc/tutorial/client.lhs+85−73 doc/tutorial/docs.lhs+100−86 doc/tutorial/javascript.lhs+112−96 doc/tutorial/server.lhs+511−431
doc/tutorial/api-type.lhs+80−56
@@ -6,13 +6,15 @@ toc: true
66The source for this tutorial section is a literate haskell file, so first we
77need to have some language extensions and imports:
88
9−> {-# LANGUAGE DataKinds #-}
10−> {-# LANGUAGE TypeOperators #-}
11−>
12−> module ApiType where
13−>
14−> import Data.Text
15−> import Servant.API
9+``` haskell
10+{-# LANGUAGE DataKinds #-}
11+{-# LANGUAGE TypeOperators #-}
12+
13+module ApiType where
14+
15+import Data.Text
16+import Servant.API
17+```
1618
1719Consider the following informal specification of an API:
1820
@@ -29,14 +31,16 @@ getting some client libraries, and documentation (and in the future, who knows
2931How would we describe it with servant? As mentioned earlier, an endpoint
3032description is a good old Haskell **type**:
3133
32−> type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
33−>
34−> data SortBy = Age | Name
35−>
36−> data User = User {
37−> name :: String,
38−> age :: Int
39−> }
34+``` haskell
35+type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
36+
37+data SortBy = Age | Name
38+
39+data User = User {
40+ name :: String,
41+ age :: Int
42+}
43+```
4044
4145Let's break that down:
4246
@@ -61,8 +65,10 @@ equivalent to `/`, but sometimes it just lets you chain another combinator.
6165We can also describe APIs with multiple endpoints by using the `:<|>`
6266combinators. Here's an example:
6367
64−> type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
65−> :<|> "list-all" :> "users" :> Get '[JSON] [User]
68+``` haskell
69+type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
70+ :<|> "list-all" :> "users" :> Get '[JSON] [User]
71+```
6672
6773*servant* provides a fair amount of combinators out-of-the-box, but you can
6874always write your own when you need it. Here's a quick overview of all the
@@ -78,9 +84,11 @@ As you've already seen, you can use type-level strings (enabled with the
7884`DataKinds` language extension) for static path fragments. Chaining
7985them amounts to `/`-separating them in a URL.
8086
81−> type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
82−> -- describes an endpoint reachable at:
83−> -- /users/list-all/now
87+``` haskell
88+type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
89+ -- describes an endpoint reachable at:
90+ -- /users/list-all/now
91+```
8492
8593`Delete`, `Get`, `Patch`, `Post` and `Put`
8694------------------------------------------
@@ -99,8 +107,10 @@ data Put (contentTypes :: [*]) a
99107An endpoint ends with one of the 5 combinators above (unless you write your
100108own). Examples:
101109
102−> type UserAPI4 = "users" :> Get '[JSON] [User]
103−> :<|> "admins" :> Get '[JSON] [User]
110+``` haskell
111+type UserAPI4 = "users" :> Get '[JSON] [User]
112+ :<|> "admins" :> Get '[JSON] [User]
113+```
104114
105115`Capture`
106116---------
@@ -127,13 +137,15 @@ class, which the captured value must be an instance of.
127137
128138Examples:
129139
130−> type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
131−> -- equivalent to 'GET /user/:userid'
132−> -- except that we explicitly say that "userid"
133−> -- must be an integer
134−>
135−> :<|> "user" :> Capture "userid" Integer :> Delete '[] ()
136−> -- equivalent to 'DELETE /user/:userid'
140+``` haskell
141+type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
142+ -- equivalent to 'GET /user/:userid'
143+ -- except that we explicitly say that "userid"
144+ -- must be an integer
145+
146+ :<|> "user" :> Capture "userid" Integer :> Delete '[] ()
147+ -- equivalent to 'DELETE /user/:userid'
148+```
137149
138150`QueryParam`, `QueryParams`, `QueryFlag`, `MatrixParam`, `MatrixParams` and `MatrixFlag`
139151----------------------------------------------------------------------------------------
@@ -179,11 +191,13 @@ data MatrixFlag (sym :: Symbol)
179191
180192Examples:
181193
182−> type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
183−> -- equivalent to 'GET /users?sortby={age, name}'
184−>
185−> :<|> "users" :> MatrixParam "sortby" SortBy :> Get '[JSON] [User]
186−> -- equivalent to 'GET /users;sortby={age, name}'
194+``` haskell
195+type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
196+ -- equivalent to 'GET /users?sortby={age, name}'
197+
198+ :<|> "users" :> MatrixParam "sortby" SortBy :> Get '[JSON] [User]
199+ -- equivalent to 'GET /users;sortby={age, name}'
200+```
187201
188202Again, your handlers don't have to deserialize these things (into, for example,
189203a `SortBy`). *servant* takes care of it.
@@ -212,17 +226,19 @@ data ReqBody (contentTypes :: [*]) a
212226
213227Examples:
214228
215−> type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
216−> -- - equivalent to 'POST /users' with a JSON object
217−> -- describing a User in the request body
218−> -- - returns a User encoded in JSON
219−>
220−> :<|> "users" :> Capture "userid" Integer
221−> :> ReqBody '[JSON] User
222−> :> Put '[JSON] User
223−> -- - equivalent to 'PUT /users/:userid' with a JSON
224−> -- object describing a User in the request body
225−> -- - returns a User encoded in JSON
229+``` haskell
230+type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
231+ -- - equivalent to 'POST /users' with a JSON object
232+ -- describing a User in the request body
233+ -- - returns a User encoded in JSON
234+
235+ :<|> "users" :> Capture "userid" Integer
236+ :> ReqBody '[JSON] User
237+ :> Put '[JSON] User
238+ -- - equivalent to 'PUT /users/:userid' with a JSON
239+ -- object describing a User in the request body
240+ -- - returns a User encoded in JSON
241+```
226242
227243Request `Header`s
228244-----------------
@@ -243,7 +259,9 @@ Here's an example where we declare that an endpoint makes use of the
243259`User-Agent` header which specifies the name of the software/library used by
244260the client to send the request.
245261
246−> type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
262+``` haskell
263+type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
264+```
247265
248266Content types
249267-------------
@@ -257,7 +275,9 @@ Four content-types are provided out-of-the-box by the core *servant* package:
257275reason you wanted one of your endpoints to make your user data available under
258276those 4 formats, you would write the API type as below:
259277
260−> type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
278+``` haskell
279+type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
280+```
261281
262282We also provide an HTML content-type, but since there's no single library
263283that everyone uses, we decided to release 2 packages, *servant-lucid* and
@@ -281,7 +301,9 @@ data Headers (ls :: [*]) a
281301If you want to describe an endpoint that returns a "User-Count" header in each
282302response, you could write it as below:
283303
284−> type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
304+``` haskell
305+type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
306+```
285307
286308Interoperability with other WAI `Application`s: `Raw`
287309-----------------------------------------------------
@@ -290,14 +312,16 @@ Finally, we also include a combinator named `Raw` that can be used for two reaso
290312
291313- You want to serve static files from a given directory. In that case you can just say:
292314
293−> type UserAPI11 = "users" :> Get '[JSON] [User]
294−> -- a /users endpoint
295−>
296−> :<|> Raw
297−> -- requests to anything else than /users
298−> -- go here, where the server will try to
299−> -- find a file with the right name
300−> -- at the right path
315+``` haskell
316+type UserAPI11 = "users" :> Get '[JSON] [User]
317+ -- a /users endpoint
318+
319+ :<|> Raw
320+ -- requests to anything else than /users
321+ -- go here, where the server will try to
322+ -- find a file with the right name
323+ -- at the right path
324+```
301325
302326- You more generally want to plug a [WAI `Application`](http://hackage.haskell.org/package/wai)
303327into your webservice. Static file serving is a specific example of that. The API type would look the
doc/tutorial/client.lhs+85−73
@@ -11,75 +11,85 @@ and friends. By *derive*, we mean that there's no code generation involved, the
1111The source for this tutorial section is a literate haskell file, so first we
1212need to have some language extensions and imports:
1313
14−> {-# LANGUAGE DataKinds #-}
15−> {-# LANGUAGE DeriveGeneric #-}
16−> {-# LANGUAGE TypeOperators #-}
17−>
18−> module Client where
19−>
20−> import Control.Monad.Trans.Either
21−> import Data.Aeson
22−> import Data.Proxy
23−> import GHC.Generics
24−> import Servant.API
25−> import Servant.Client
14+``` haskell
15+{-# LANGUAGE DataKinds #-}
16+{-# LANGUAGE DeriveGeneric #-}
17+{-# LANGUAGE TypeOperators #-}
18+
19+module Client where
20+
21+import Control.Monad.Trans.Either
22+import Data.Aeson
23+import Data.Proxy
24+import GHC.Generics
25+import Servant.API
26+import Servant.Client
27+```
2628
2729Also, we need examples for some domain specific data types:
2830
29−> data Position = Position
30−> { x :: Int
31−> , y :: Int
32−> } deriving (Show, Generic)
33−>
34−> instance FromJSON Position
35−>
36−> newtype HelloMessage = HelloMessage { msg :: String }
37−> deriving (Show, Generic)
38−>
39−> instance FromJSON HelloMessage
40−>
41−> data ClientInfo = ClientInfo
42−> { clientName :: String
43−> , clientEmail :: String
44−> , clientAge :: Int
45−> , clientInterestedIn :: [String]
46−> } deriving Generic
47−>
48−> instance ToJSON ClientInfo
49−>
50−> data Email = Email
51−> { from :: String
52−> , to :: String
53−> , subject :: String
54−> , body :: String
55−> } deriving (Show, Generic)
56−>
57−> instance FromJSON Email
31+``` haskell
32+data Position = Position
33+ { x :: Int
34+ , y :: Int
35+ } deriving (Show, Generic)
36+
37+instance FromJSON Position
38+
39+newtype HelloMessage = HelloMessage { msg :: String }
40+ deriving (Show, Generic)
41+
42+instance FromJSON HelloMessage
43+
44+data ClientInfo = ClientInfo
45+ { clientName :: String
46+ , clientEmail :: String
47+ , clientAge :: Int
48+ , clientInterestedIn :: [String]
49+ } deriving Generic
50+
51+instance ToJSON ClientInfo
52+
53+data Email = Email
54+ { from :: String
55+ , to :: String
56+ , subject :: String
57+ , body :: String
58+ } deriving (Show, Generic)
59+
60+instance FromJSON Email
61+```
5862
5963Enough chitchat, let's see an example. Consider the following API type from the previous section:
6064
61−> type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
62−> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
63−> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
65+``` haskell
66+type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
67+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
68+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
69+```
6470
6571What we are going to get with *servant-client* here is 3 functions, one to query each endpoint:
6672
67−> position :: Int -- ^ value for "x"
68−> -> Int -- ^ value for "y"
69−> -> EitherT ServantError IO Position
70−>
71−> hello :: Maybe String -- ^ an optional value for "name"
72−> -> EitherT ServantError IO HelloMessage
73−>
74−> marketing :: ClientInfo -- ^ value for the request body
75−> -> EitherT ServantError IO Email
73+``` haskell
74+position :: Int -- ^ value for "x"
75+ -> Int -- ^ value for "y"
76+ -> EitherT ServantError IO Position
77+
78+hello :: Maybe String -- ^ an optional value for "name"
79+ -> EitherT ServantError IO HelloMessage
80+
81+marketing :: ClientInfo -- ^ value for the request body
82+ -> EitherT ServantError IO Email
83+```
7684
7785Each function makes available as an argument any value that the response may depend on, as evidenced in the API type. How do we get these functions? Just give a `Proxy` to your API and a host to make the requests to:
7886
79−> api :: Proxy API
80−> api = Proxy
81−>
82−> position :<|> hello :<|> marketing = client api (BaseUrl Http "localhost" 8081)
87+``` haskell
88+api :: Proxy API
89+api = Proxy
90+
91+position :<|> hello :<|> marketing = client api (BaseUrl Http "localhost" 8081)
92+```
8393
8494As you can see in the code above, we just "pattern match our way" to these functions. If we try to derive less or more functions than there are endpoints in the API, we obviously get an error. The `BaseUrl` value there is just:
8595
@@ -101,22 +111,24 @@ data BaseUrl = BaseUrl
101111
102112That's it. Let's now write some code that uses our client functions.
103113
104−> queries :: EitherT ServantError IO (Position, HelloMessage, Email)
105−> queries = do
106−> pos <- position 10 10
107−> msg <- hello (Just "servant")
108−> em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
109−> return (pos, msg, em)
110−>
111−> run :: IO ()
112−> run = do
113−> res <- runEitherT queries
114−> case res of
115−> Left err -> putStrLn $ "Error: " ++ show err
116−> Right (pos, msg, em) -> do
117−> print pos
118−> print msg
119−> print em
114+``` haskell
115+queries :: EitherT ServantError IO (Position, HelloMessage, Email)
116+queries = do
117+ pos <- position 10 10
118+ msg <- hello (Just "servant")
119+ em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
120+ return (pos, msg, em)
121+
122+run :: IO ()
123+run = do
124+ res <- runEitherT queries
125+ case res of
126+ Left err -> putStrLn $ "Error: " ++ show err
127+ Right (pos, msg, em) -> do
128+ print pos
129+ print msg
130+ print em
131+```
120132
121133You can now run `dist/build/tutorial/tutorial 8` (the server) and
122134`dist/build/t8-main/t8-main` (the client) to see them both in action.
doc/tutorial/docs.lhs+100−86
@@ -6,89 +6,99 @@ toc: true
66The source for this tutorial section is a literate haskell file, so first we
77need to have some language extensions and imports:
88
9−> {-# LANGUAGE DataKinds #-}
10−> {-# LANGUAGE DeriveGeneric #-}
11−> {-# LANGUAGE FlexibleInstances #-}
12−> {-# LANGUAGE MultiParamTypeClasses #-}
13−> {-# LANGUAGE OverloadedStrings #-}
14−> {-# LANGUAGE TypeOperators #-}
15−> {-# OPTIONS_GHC -fno-warn-orphans #-}
16−>
17−> module Docs where
18−>
19−> import Data.ByteString.Lazy (ByteString)
20−> import Data.Proxy
21−> import Data.Text.Lazy.Encoding (encodeUtf8)
22−> import Data.Text.Lazy (pack)
23−> import Network.HTTP.Types
24−> import Network.Wai
25−> import Servant.API
26−> import Servant.Docs
27−> import Servant.Server
9+``` haskell
10+{-# LANGUAGE DataKinds #-}
11+{-# LANGUAGE DeriveGeneric #-}
12+{-# LANGUAGE FlexibleInstances #-}
13+{-# LANGUAGE MultiParamTypeClasses #-}
14+{-# LANGUAGE OverloadedStrings #-}
15+{-# LANGUAGE TypeOperators #-}
16+{-# OPTIONS_GHC -fno-warn-orphans #-}
17+
18+module Docs where
19+
20+import Data.ByteString.Lazy (ByteString)
21+import Data.Proxy
22+import Data.Text.Lazy.Encoding (encodeUtf8)
23+import Data.Text.Lazy (pack)
24+import Network.HTTP.Types
25+import Network.Wai
26+import Servant.API
27+import Servant.Docs
28+import Servant.Server
29+```
2830
2931And we'll import some things from one of our earlier modules
3032([Serving an API](/tutorial/server.html)):
3133
32−> import Server (Email(..), ClientInfo(..), Position(..), HelloMessage(..),
33−> server3, emailForClient)
34+``` haskell
35+import Server (Email(..), ClientInfo(..), Position(..), HelloMessage(..),
36+ server3, emailForClient)
37+```
3438
3539Like client function generation, documentation generation amounts to inspecting the API type and extracting all the data we need to then present it in some format to users of your API.
3640
3741This time however, we have to assist *servant*. While it is able to deduce a lot of things about our API, it can't magically come up with descriptions of the various pieces of our APIs that are human-friendly and explain what's going on "at the business-logic level". A good example to study for documentation generation is our webservice with the `/position`, `/hello` and `/marketing` endpoints from earlier:
3842
39−> type ExampleAPI = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
40−> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
41−> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
42−>
43−> exampleAPI :: Proxy ExampleAPI
44−> exampleAPI = Proxy
43+``` haskell
44+type ExampleAPI = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
45+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
46+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
47+
48+exampleAPI :: Proxy ExampleAPI
49+exampleAPI = Proxy
50+```
4551
4652While *servant* can see e.g. that there are 3 endpoints and that the response bodies will be in JSON, it doesn't know what influence the captures, parameters, request bodies and other combinators have on the webservice. This is where some manual work is required.
4753
4854For every capture, request body, response body, query param, we have to give some explanations about how it influences the response, what values are possible and the likes. Here's how it looks like for the parameters we have above.
4955
50−> instance ToCapture (Capture "x" Int) where
51−> toCapture _ =
52−> DocCapture "x" -- name
53−> "(integer) position on the x axis" -- description
54−>
55−> instance ToCapture (Capture "y" Int) where
56−> toCapture _ =
57−> DocCapture "y" -- name
58−> "(integer) position on the y axis" -- description
59−>
60−> instance ToSample Position Position where
61−> toSample _ = Just (Position 3 14) -- example of output
62−>
63−> instance ToParam (QueryParam "name" String) where
64−> toParam _ =
65−> DocQueryParam "name" -- name
66−> ["Alp", "John Doe", "..."] -- example of values (not necessarily exhaustive)
67−> "Name of the person to say hello to." -- description
68−> Normal -- Normal, List or Flag
69−>
70−> instance ToSample HelloMessage HelloMessage where
71−> toSamples _ =
72−> [ ("When a value is provided for 'name'", HelloMessage "Hello, Alp")
73−> , ("When 'name' is not specified", HelloMessage "Hello, anonymous coward")
74−> ]
75−> -- mutliple examples to display this time
76−>
77−> ci :: ClientInfo
78−> ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
79−>
80−> instance ToSample ClientInfo ClientInfo where
81−> toSample _ = Just ci
82−>
83−> instance ToSample Email Email where
84−> toSample _ = Just (emailForClient ci)
56+``` haskell
57+instance ToCapture (Capture "x" Int) where
58+ toCapture _ =
59+ DocCapture "x" -- name
60+ "(integer) position on the x axis" -- description
61+
62+instance ToCapture (Capture "y" Int) where
63+ toCapture _ =
64+ DocCapture "y" -- name
65+ "(integer) position on the y axis" -- description
66+
67+instance ToSample Position Position where
68+ toSample _ = Just (Position 3 14) -- example of output
69+
70+instance ToParam (QueryParam "name" String) where
71+ toParam _ =
72+ DocQueryParam "name" -- name
73+ ["Alp", "John Doe", "..."] -- example of values (not necessarily exhaustive)
74+ "Name of the person to say hello to." -- description
75+ Normal -- Normal, List or Flag
76+
77+instance ToSample HelloMessage HelloMessage where
78+ toSamples _ =
79+ [ ("When a value is provided for 'name'", HelloMessage "Hello, Alp")
80+ , ("When 'name' is not specified", HelloMessage "Hello, anonymous coward")
81+ ]
82+ -- mutliple examples to display this time
83+
84+ci :: ClientInfo
85+ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
86+
87+instance ToSample ClientInfo ClientInfo where
88+ toSample _ = Just ci
89+
90+instance ToSample Email Email where
91+ toSample _ = Just (emailForClient ci)
92+```
8593
8694Types that are used as request or response bodies have to instantiate the `ToSample` typeclass which lets you specify one or more examples of values. `Capture`s and `QueryParam`s have to instantiate their respective `ToCapture` and `ToParam` classes and provide a name and some information about the concrete meaning of that argument, as illustrated in the code above.
8795
8896With all of this, we can derive docs for our API.
8997
90−> apiDocs :: API
91−> apiDocs = docs exampleAPI
98+``` haskell
99+apiDocs :: API
100+apiDocs = docs exampleAPI
101+```
92102
93103`API` is a type provided by *servant-docs* that stores all the information one needs about a web API in order to generate documentation in some format. Out of the box, *servant-docs* only provides a pretty documentation printer that outputs [Markdown](http://en.wikipedia.org/wiki/Markdown), but the [servant-pandoc](http://hackage.haskell.org/package/servant-pandoc) package can be used to target many useful formats.
94104
@@ -192,33 +202,37 @@ That lets us see what our API docs look down in markdown, by looking at `markdow
192202
193203However, we can also add one or more introduction sections to the document. We just need to tweak the way we generate `apiDocs`. We will also convert the content to a lazy `ByteString` since this is what *wai* expects for `Raw` endpoints.
194204
195−> docsBS :: ByteString
196−> docsBS = encodeUtf8
197−> . pack
198−> . markdown
199−> $ docsWithIntros [intro] exampleAPI
200−>
201−> where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
205+``` haskell
206+docsBS :: ByteString
207+docsBS = encodeUtf8
208+ . pack
209+ . markdown
210+ $ docsWithIntros [intro] exampleAPI
211+
212+ where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
213+```
202214
203215`docsWithIntros` just takes an additional parameter, a list of `DocIntro`s that must be displayed before any endpoint docs.
204216
205217We can now serve the API *and* the API docs with a simple server.
206218
207−> type DocsAPI = ExampleAPI :<|> Raw
208−>
209−> api :: Proxy DocsAPI
210−> api = Proxy
211−>
212−> server :: Server DocsAPI
213−> server = Server.server3 :<|> serveDocs
214−>
215−> where serveDocs _ respond =
216−> respond $ responseLBS ok200 [plain] docsBS
217−>
218−> plain = ("Content-Type", "text/plain")
219−>
220−> app :: Application
221−> app = serve api server
219+``` haskell
220+type DocsAPI = ExampleAPI :<|> Raw
221+
222+api :: Proxy DocsAPI
223+api = Proxy
224+
225+server :: Server DocsAPI
226+server = Server.server3 :<|> serveDocs
227+
228+ where serveDocs _ respond =
229+ respond $ responseLBS ok200 [plain] docsBS
230+
231+ plain = ("Content-Type", "text/plain")
232+
233+app :: Application
234+app = serve api server
235+```
222236
223237And if you spin up this server with `dist/build/tutorial/tutorial 10` and go to anywhere else than `/position`, `/hello` and `/marketing`, you will see the API docs in markdown. This is because `serveDocs` is attempted if the 3 other endpoints don't match and systematically succeeds since its definition is to just return some fixed bytestring with the `text/plain` content type.
224238
doc/tutorial/javascript.lhs+112−96
@@ -22,117 +22,131 @@ query your API.
2222The source for this tutorial section is a literate haskell file, so first we
2323need to have some language extensions and imports:
2424
25−> {-# LANGUAGE DataKinds #-}
26−> {-# LANGUAGE DeriveGeneric #-}
27−> {-# LANGUAGE OverloadedStrings #-}
28−> {-# LANGUAGE TypeOperators #-}
29−>
30−> module Javascript where
31−>
32−> import Control.Monad.IO.Class
33−> import Data.Aeson
34−> import Data.Proxy
35−> import Data.Text (Text)
36−> import qualified Data.Text as T
37−> import GHC.Generics
38−> import Language.Javascript.JQuery
39−> import Network.Wai
40−> import Servant
41−> import Servant.JQuery
42−> import System.Random
25+``` haskell
26+{-# LANGUAGE DataKinds #-}
27+{-# LANGUAGE DeriveGeneric #-}
28+{-# LANGUAGE OverloadedStrings #-}
29+{-# LANGUAGE TypeOperators #-}
30+
31+module Javascript where
32+
33+import Control.Monad.IO.Class
34+import Data.Aeson
35+import Data.Proxy
36+import Data.Text (Text)
37+import qualified Data.Text as T
38+import GHC.Generics
39+import Language.Javascript.JQuery
40+import Network.Wai
41+import Servant
42+import Servant.JQuery
43+import System.Random
44+```
4345
4446Now let's have the API type(s) and the accompanying datatypes.
4547
46−> type API = "point" :> Get '[JSON] Point
47−> :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
48−>
49−> type API' = API :<|> Raw
50−>
51−> data Point = Point
52−> { x :: Double
53−> , y :: Double
54−> } deriving Generic
55−>
56−> instance ToJSON Point
57−>
58−> data Search a = Search
59−> { query :: Text
60−> , results :: [a]
61−> } deriving Generic
62−>
63−> mkSearch :: Text -> [a] -> Search a
64−> mkSearch = Search
65−>
66−> instance ToJSON a => ToJSON (Search a)
67−>
68−> data Book = Book
69−> { author :: Text
70−> , title :: Text
71−> , year :: Int
72−> } deriving Generic
73−>
74−> instance ToJSON Book
75−>
76−> book :: Text -> Text -> Int -> Book
77−> book = Book
48+``` haskell
49+type API = "point" :> Get '[JSON] Point
50+ :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
51+
52+type API' = API :<|> Raw
53+
54+data Point = Point
55+ { x :: Double
56+ , y :: Double
57+ } deriving Generic
58+
59+instance ToJSON Point
60+
61+data Search a = Search
62+ { query :: Text
63+ , results :: [a]
64+ } deriving Generic
65+
66+mkSearch :: Text -> [a] -> Search a
67+mkSearch = Search
68+
69+instance ToJSON a => ToJSON (Search a)
70+
71+data Book = Book
72+ { author :: Text
73+ , title :: Text
74+ , year :: Int
75+ } deriving Generic
76+
77+instance ToJSON Book
78+
79+book :: Text -> Text -> Int -> Book
80+book = Book
81+```
7882
7983We need a "book database". For the purpose of this guide, let's restrict ourselves to the following books.
8084
81−> books :: [Book]
82−> books =
83−> [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
84−> , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
85−> , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
86−> , book "Graham Hutton" "Programming in Haskell" 2007
87−> , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
88−> , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
89−> ]
85+``` haskell
86+books :: [Book]
87+books =
88+ [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
89+ , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008
90+ , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
91+ , book "Graham Hutton" "Programming in Haskell" 2007
92+ , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
93+ , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
94+ ]
95+```
9096
9197Now, given an optional search string `q`, we want to perform a case insensitive search in that list of books. We're obviously not going to try and implement the best possible algorithm, this is out of scope for this tutorial. The following simple linear scan will do, given how small our list is.
9298
93−> searchBook :: Monad m => Maybe Text -> m (Search Book)
94−> searchBook Nothing = return (mkSearch "" books)
95−> searchBook (Just q) = return (mkSearch q books')
96−>
97−> where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
98−> || q' `T.isInfixOf` T.toLower (title b)
99−> )
100−> books
101−> q' = T.toLower q
99+``` haskell
100+searchBook :: Monad m => Maybe Text -> m (Search Book)
101+searchBook Nothing = return (mkSearch "" books)
102+searchBook (Just q) = return (mkSearch q books')
103+
104+ where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
105+ || q' `T.isInfixOf` T.toLower (title b)
106+ )
107+ books
108+ q' = T.toLower q
109+```
102110
103111We also need an endpoint that generates random points `(x, y)` with `-1 <= x,y <= 1`. The code below uses [random](http://hackage.haskell.org/package/random)'s `System.Random`.
104112
105−> randomPoint :: MonadIO m => m Point
106−> randomPoint = liftIO . getStdRandom $ \g ->
107−> let (rx, g') = randomR (-1, 1) g
108−> (ry, g'') = randomR (-1, 1) g'
109−> in (Point rx ry, g'')
113+``` haskell
114+randomPoint :: MonadIO m => m Point
115+randomPoint = liftIO . getStdRandom $ \g ->
116+ let (rx, g') = randomR (-1, 1) g
117+ (ry, g'') = randomR (-1, 1) g'
118+ in (Point rx ry, g'')
119+```
110120
111121If we add static file serving, our server is now complete.
112122
113−> api :: Proxy API
114−> api = Proxy
115−>
116−> api' :: Proxy API'
117−> api' = Proxy
118−>
119−> server :: Server API
120−> server = randomPoint
121−> :<|> searchBook
122−>
123−> server' :: Server API'
124−> server' = server
125−> :<|> serveDirectory "tutorial/t9"
126−>
127−> app :: Application
128−> app = serve api' server'
123+``` haskell
124+api :: Proxy API
125+api = Proxy
126+
127+api' :: Proxy API'
128+api' = Proxy
129+
130+server :: Server API
131+server = randomPoint
132+ :<|> searchBook
133+
134+server' :: Server API'
135+server' = server
136+ :<|> serveDirectory "tutorial/t9"
137+
138+app :: Application
139+app = serve api' server'
140+```
129141
130142Why two different API types, proxies and servers though? Simply because we don't want to generate javascript functions for the `Raw` part of our API type, so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
131143
132144Very similarly to how one can derive haskell functions, we can derive the javascript with just a simple function call to `jsForAPI` from `Servant.JQuery`.
133145
134−> apiJS :: String
135−> apiJS = jsForAPI api
146+``` haskell
147+apiJS :: String
148+apiJS = jsForAPI api
149+```
136150
137151This `String` contains 2 Javascript functions:
138152
@@ -161,11 +175,13 @@ function getbooks(q, onSuccess, onError)
161175
162176Right before starting up our server, we will need to write this `String` to a file, say `api.js`, along with a copy of the *jQuery* library, as provided by the [js-jquery](http://hackage.haskell.org/package/js-jquery) package.
163177
164−> writeJSFiles :: IO ()
165−> writeJSFiles = do
166−> writeFile "getting-started/gs9/api.js" apiJS
167−> jq <- readFile =<< Language.Javascript.JQuery.file
168−> writeFile "getting-started/gs9/jq.js" jq
178+``` haskell
179+writeJSFiles :: IO ()
180+writeJSFiles = do
181+ writeFile "getting-started/gs9/api.js" apiJS
182+ jq <- readFile =<< Language.Javascript.JQuery.file
183+ writeFile "getting-started/gs9/jq.js" jq
184+```
169185
170186And we're good to go. Start the server with `dist/build/tutorial/tutorial 9` and go to `http://localhost:8081/`. Start typing in the name of one of the authors in our database or part of a book title, and check out how long it takes to approximate &pi; using the method mentioned above.
171187
doc/tutorial/server.lhs+511−431
@@ -34,39 +34,41 @@ Equipped with some basic knowledge about the way we represent API, let's now wri
3434The source for this tutorial section is a literate haskell file, so first we
3535need to have some language extensions and imports:
3636
37−> {-# LANGUAGE DataKinds #-}
38−> {-# LANGUAGE DeriveGeneric #-}
39−> {-# LANGUAGE FlexibleInstances #-}
40−> {-# LANGUAGE GeneralizedNewtypeDeriving #-}
41−> {-# LANGUAGE MultiParamTypeClasses #-}
42−> {-# LANGUAGE OverloadedStrings #-}
43−> {-# LANGUAGE ScopedTypeVariables #-}
44−> {-# LANGUAGE TypeOperators #-}
45−>
46−> module Server where
47−>
48−> import Control.Monad.IO.Class
49−> import Control.Monad.Reader
50−> import Control.Monad.Trans.Either
51−> import Data.Aeson
52−> import Data.Aeson.Types
53−> import Data.Attoparsec.ByteString
54−> import Data.ByteString (ByteString)
55−> import Data.Int
56−> import Data.List
57−> import Data.String.Conversions
58−> import Data.Time.Calendar
59−> import GHC.Generics
60−> import Lucid
61−> import Network.HTTP.Media ((//), (/:))
62−> import Network.Wai
63−> import Network.Wai.Handler.Warp
64−> import Servant
65−> import System.Directory
66−> import Text.Blaze
67−> import Text.Blaze.Html.Renderer.Utf8
68−> import qualified Data.Aeson.Parser
69−> import qualified Text.Blaze.Html
37+``` haskell
38+{-# LANGUAGE DataKinds #-}
39+{-# LANGUAGE DeriveGeneric #-}
40+{-# LANGUAGE FlexibleInstances #-}
41+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
42+{-# LANGUAGE MultiParamTypeClasses #-}
43+{-# LANGUAGE OverloadedStrings #-}
44+{-# LANGUAGE ScopedTypeVariables #-}
45+{-# LANGUAGE TypeOperators #-}
46+
47+module Server where
48+
49+import Control.Monad.IO.Class
50+import Control.Monad.Reader
51+import Control.Monad.Trans.Either
52+import Data.Aeson
53+import Data.Aeson.Types
54+import Data.Attoparsec.ByteString
55+import Data.ByteString (ByteString)
56+import Data.Int
57+import Data.List
58+import Data.String.Conversions
59+import Data.Time.Calendar
60+import GHC.Generics
61+import Lucid
62+import Network.HTTP.Media ((//), (/:))
63+import Network.Wai
64+import Network.Wai.Handler.Warp
65+import Servant
66+import System.Directory
67+import Text.Blaze
68+import Text.Blaze.Html.Renderer.Utf8
69+import qualified Data.Aeson.Parser
70+import qualified Text.Blaze.Html
71+```
7072
7173``` haskell ignore
7274{-# LANGUAGE TypeFamilies #-}
@@ -76,7 +78,9 @@ need to have some language extensions and imports:
7678
7779We will write a server that will serve the following API.
7880
79−> type UserAPI1 = "users" :> Get '[JSON] [User]
81+``` haskell
82+type UserAPI1 = "users" :> Get '[JSON] [User]
83+```
8084
8185Here's what we would like to see when making a GET request to `/users`.
8286
@@ -88,22 +92,26 @@ Here's what we would like to see when making a GET request to `/users`.
8892
8993Now let's define our `User` data type and write some instances for it.
9094
91−> data User = User
92−> { name :: String
93−> , age :: Int
94−> , email :: String
95−> , registration_date :: Day
96−> } deriving (Eq, Show, Generic)
97−>
98−> instance ToJSON User
95+``` haskell
96+data User = User
97+ { name :: String
98+ , age :: Int
99+ , email :: String
100+ , registration_date :: Day
101+ } deriving (Eq, Show, Generic)
102+
103+instance ToJSON User
104+```
99105
100106Nothing funny going on here. But we now can define our list of two users.
101107
102−> users1 :: [User]
103−> users1 =
104−> [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
105−> , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
106−> ]
108+``` haskell
109+users1 :: [User]
110+users1 =
111+ [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
112+ , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
113+ ]
114+```
107115
108116Let's also write our API type.
109117
@@ -129,27 +137,33 @@ HTTP method combinator used for the corresponding endpoint. In our case, it
129137means we must provide a handler of type `EitherT ServantErr IO [User]`. Well,
130138we have a monad, let's just `return` our list:
131139
132−> server1 :: Server UserAPI1
133−> server1 = return users1
140+``` haskell
141+server1 :: Server UserAPI1
142+server1 = return users1
143+```
134144
135145That's it. Now we can turn `server` into an actual webserver using [wai](http://hackage.haskell.org/package/wai) and [warp](http://hackage.haskell.org/package/warp):
136146
137−> userAPI :: Proxy UserAPI1
138−> userAPI = Proxy
139−>
140−> -- 'serve' comes from servant and hands you a WAI Application,
141−> -- which you can think of as an "abstract" web application,
142−> -- not yet a webserver.
143−> app1 :: Application
144−> app1 = serve userAPI server1
147+``` haskell
148+userAPI :: Proxy UserAPI1
149+userAPI = Proxy
150+
151+-- 'serve' comes from servant and hands you a WAI Application,
152+-- which you can think of as an "abstract" web application,
153+-- not yet a webserver.
154+app1 :: Application
155+app1 = serve userAPI server1
156+```
145157
146158The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
147159But that's about as much boilerplate as you get.
148160
149161And we're done! Let's run our webservice on the port 8081.
150162
151−> main :: IO ()
152−> main = run 8081 app1
163+``` haskell
164+main :: IO ()
165+main = run 8081 app1
166+```
153167
154168You can put this all into a file or just grab [servant's
155169repo](http://github.com/haskell-servant/servant) and look at the
@@ -170,29 +184,35 @@ More endpoints
170184
171185What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
172186
173−> type UserAPI2 = "users" :> Get '[JSON] [User]
174−> :<|> "albert" :> Get '[JSON] User
175−> :<|> "isaac" :> Get '[JSON] User
187+``` haskell
188+type UserAPI2 = "users" :> Get '[JSON] [User]
189+ :<|> "albert" :> Get '[JSON] User
190+ :<|> "isaac" :> Get '[JSON] User
191+```
176192
177193And let's adapt our code a bit.
178194
179−> isaac :: User
180−> isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
181−>
182−> albert :: User
183−> albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
184−>
185−> users2 :: [User]
186−> users2 = [isaac, albert]
195+``` haskell
196+isaac :: User
197+isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
198+
199+albert :: User
200+albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
201+
202+users2 :: [User]
203+users2 = [isaac, albert]
204+```
187205
188206Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
189207are going to separate the handlers with `:<|>` too! They must be provided in
190208the same order as the one they appear in in the API type.
191209
192−> server2 :: Server UserAPI2
193−> server2 = return users2
194−> :<|> return albert
195−> :<|> return isaac
210+``` haskell
211+server2 :: Server UserAPI2
212+server2 = return users2
213+ :<|> return albert
214+ :<|> return isaac
215+```
196216
197217And that's it! You can run this example with
198218`dist/build/tutorial/tutorial 2` and check out the data available
@@ -211,70 +231,74 @@ decoding/encoding data from/to JSON. Never.
211231
212232We are going to use the following data types and functions to implement a server for `API`.
213233
214−> type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
215−> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
216−> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
217−>
218−> data Position = Position
219−> { x :: Int
220−> , y :: Int
221−> } deriving Generic
222−>
223−> instance ToJSON Position
224−>
225−> newtype HelloMessage = HelloMessage { msg :: String }
226−> deriving Generic
227−>
228−> instance ToJSON HelloMessage
229−>
230−> data ClientInfo = ClientInfo
231−> { clientName :: String
232−> , clientEmail :: String
233−> , clientAge :: Int
234−> , clientInterestedIn :: [String]
235−> } deriving Generic
236−>
237−> instance FromJSON ClientInfo
238−> instance ToJSON ClientInfo
239−>
240−> data Email = Email
241−> { from :: String
242−> , to :: String
243−> , subject :: String
244−> , body :: String
245−> } deriving Generic
246−>
247−> instance ToJSON Email
248−>
249−> emailForClient :: ClientInfo -> Email
250−> emailForClient c = Email from' to' subject' body'
251−>
252−> where from' = "great@company.com"
253−> to' = clientEmail c
254−> subject' = "Hey " ++ clientName c ++ ", we miss you!"
255−> body' = "Hi " ++ clientName c ++ ",\n\n"
256−> ++ "Since you've recently turned " ++ show (clientAge c)
257−> ++ ", have you checked out our latest "
258−> ++ intercalate ", " (clientInterestedIn c)
259−> ++ " products? Give us a visit!"
234+``` haskell
235+type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
236+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
237+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
238+
239+data Position = Position
240+ { x :: Int
241+ , y :: Int
242+ } deriving Generic
243+
244+instance ToJSON Position
245+
246+newtype HelloMessage = HelloMessage { msg :: String }
247+ deriving Generic
248+
249+instance ToJSON HelloMessage
250+
251+data ClientInfo = ClientInfo
252+ { clientName :: String
253+ , clientEmail :: String
254+ , clientAge :: Int
255+ , clientInterestedIn :: [String]
256+ } deriving Generic
257+
258+instance FromJSON ClientInfo
259+instance ToJSON ClientInfo
260+
261+data Email = Email
262+ { from :: String
263+ , to :: String
264+ , subject :: String
265+ , body :: String
266+ } deriving Generic
267+
268+instance ToJSON Email
269+
270+emailForClient :: ClientInfo -> Email
271+emailForClient c = Email from' to' subject' body'
272+
273+ where from' = "great@company.com"
274+ to' = clientEmail c
275+ subject' = "Hey " ++ clientName c ++ ", we miss you!"
276+ body' = "Hi " ++ clientName c ++ ",\n\n"
277+ ++ "Since you've recently turned " ++ show (clientAge c)
278+ ++ ", have you checked out our latest "
279+ ++ intercalate ", " (clientInterestedIn c)
280+ ++ " products? Give us a visit!"
281+```
260282
261283We can implement handlers for the three endpoints:
262284
263−> server3 :: Server API
264−> server3 = position
265−> :<|> hello
266−> :<|> marketing
267−>
268−> where position :: Int -> Int -> EitherT ServantErr IO Position
269−> position x y = return (Position x y)
270−>
271−> hello :: Maybe String -> EitherT ServantErr IO HelloMessage
272−> hello mname = return . HelloMessage $ case mname of
273−> Nothing -> "Hello, anonymous coward"
274−> Just n -> "Hello, " ++ n
275−>
276−> marketing :: ClientInfo -> EitherT ServantErr IO Email
277−> marketing clientinfo = return (emailForClient clientinfo)
285+``` haskell
286+server3 :: Server API
287+server3 = position
288+ :<|> hello
289+ :<|> marketing
290+
291+ where position :: Int -> Int -> EitherT ServantErr IO Position
292+ position x y = return (Position x y)
293+
294+ hello :: Maybe String -> EitherT ServantErr IO HelloMessage
295+ hello mname = return . HelloMessage $ case mname of
296+ Nothing -> "Hello, anonymous coward"
297+ Just n -> "Hello, " ++ n
298+
299+ marketing :: ClientInfo -> EitherT ServantErr IO Email
300+ marketing clientinfo = return (emailForClient clientinfo)
301+```
278302
279303Did you see that? The types for your handlers changed to be just what we
280304needed! In particular:
@@ -337,29 +361,31 @@ decoded to provides a `FromText` instance, it will Just Work. *servant*
337361provides a decent number of instances, but here are some examples of defining
338362your own.
339363
340−> -- A typical enumeration
341−> data Direction
342−> = Up
343−> | Down
344−> | Left
345−> | Right
346−>
347−> instance FromText Direction where
348−> -- requires {-# LANGUAGE OverloadedStrings #-}
349−> fromText "up" = Just Up
350−> fromText "down" = Just Down
351−> fromText "left" = Just Server.Left
352−> fromText "right" = Just Server.Right
353−> fromText _ = Nothing
354−>
355−> instance ToText Direction where
356−> toText Up = "up"
357−> toText Down = "down"
358−> toText Server.Left = "left"
359−> toText Server.Right = "right"
360−>
361−> newtype UserId = UserId Int64
362−> deriving (FromText, ToText)
364+``` haskell
365+-- A typical enumeration
366+data Direction
367+ = Up
368+ | Down
369+ | Left
370+ | Right
371+
372+instance FromText Direction where
373+ -- requires {-# LANGUAGE OverloadedStrings #-}
374+ fromText "up" = Just Up
375+ fromText "down" = Just Down
376+ fromText "left" = Just Server.Left
377+ fromText "right" = Just Server.Right
378+ fromText _ = Nothing
379+
380+instance ToText Direction where
381+ toText Up = "up"
382+ toText Down = "down"
383+ toText Server.Left = "left"
384+ toText Server.Right = "right"
385+
386+newtype UserId = UserId Int64
387+ deriving (FromText, ToText)
388+```
363389
364390or writing the instances by hand:
365391
@@ -464,10 +490,12 @@ our own little function around *aeson* and *attoparsec* that allows any type of
464490JSON value at the toplevel of a "JSON document". Here's the definition in case
465491you are curious.
466492
467−> eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
468−> eitherDecodeLenient input = do
469−> v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
470−> parseEither parseJSON v
493+``` haskell
494+eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
495+eitherDecodeLenient input = do
496+ v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
497+ parseEither parseJSON v
498+```
471499
472500This function is exactly what we need for our `MimeUnrender` instance.
473501
@@ -492,7 +520,9 @@ or [lucid](http://hackage.haskell.org/package/lucid). The best option for
492520*servant* is obviously to support both (and hopefully other templating
493521solutions!).
494522
495−> data HTMLLucid
523+``` haskell
524+data HTMLLucid
525+```
496526
497527Once again, the data type is just there as a symbol for the encoding/decoding
498528functions, except that this time we will only worry about encoding since
@@ -500,8 +530,10 @@ functions, except that this time we will only worry about encoding since
500530
501531Both packages also have the same `Accept` instance for their `HTMLLucid` type.
502532
503−> instance Accept HTMLLucid where
504−> contentType _ = "text" // "html" /: ("charset", "utf-8")
533+``` haskell
534+instance Accept HTMLLucid where
535+ contentType _ = "text" // "html" /: ("charset", "utf-8")
536+```
505537
506538Note that this instance uses the `(/:)` operator from *http-media* which lets
507539us specify additional information about a content-type, like the charset here.
@@ -512,31 +544,35 @@ then write that to a `ByteString`.
512544
513545For *lucid*:
514546
515−> instance ToHtml a => MimeRender HTMLLucid a where
516−> mimeRender _ = renderBS . toHtml
517−>
518−> -- let's also provide an instance for lucid's
519−> -- 'Html' wrapper.
520−> instance MimeRender HTMLLucid (Html a) where
521−> mimeRender _ = renderBS
547+``` haskell
548+instance ToHtml a => MimeRender HTMLLucid a where
549+ mimeRender _ = renderBS . toHtml
550+
551+-- let's also provide an instance for lucid's
552+-- 'Html' wrapper.
553+instance MimeRender HTMLLucid (Html a) where
554+ mimeRender _ = renderBS
555+```
522556
523557For *blaze-html*:
524558
525−> -- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
526−> -- distinct. Usually you would stick to one html rendering library and then
527−> -- you can go with one 'HTML' type.
528−> data HTMLBlaze
529−>
530−> instance Accept HTMLBlaze where
531−> contentType _ = "text" // "html" /: ("charset", "utf-8")
532−>
533−> instance ToMarkup a => MimeRender HTMLBlaze a where
534−> mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
535−>
536−> -- while we're at it, just like for lucid we can
537−> -- provide an instance for rendering blaze's 'Html' type
538−> instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
539−> mimeRender _ = renderHtml
559+``` haskell
560+-- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
561+-- distinct. Usually you would stick to one html rendering library and then
562+-- you can go with one 'HTML' type.
563+data HTMLBlaze
564+
565+instance Accept HTMLBlaze where
566+ contentType _ = "text" // "html" /: ("charset", "utf-8")
567+
568+instance ToMarkup a => MimeRender HTMLBlaze a where
569+ mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
570+
571+-- while we're at it, just like for lucid we can
572+-- provide an instance for rendering blaze's 'Html' type
573+instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
574+ mimeRender _ = renderHtml
575+```
540576
541577Both [servant-blaze](http://hackage.haskell.org/package/servant-blaze) and
542578[servant-lucid](http://hackage.haskell.org/package/servant-lucid) let you use
@@ -548,59 +584,67 @@ content type in action. First off, imports and pragmas as usual.
548584
549585We will be serving the following API:
550586
551−> type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
587+``` haskell
588+type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
589+```
552590
553591where `Person` is defined as follows:
554592
555−> data Person = Person
556−> { firstName :: String
557−> , lastName :: String
558−> } deriving Generic -- for the JSON instance
559−>
560−> instance ToJSON Person
593+``` haskell
594+data Person = Person
595+ { firstName :: String
596+ , lastName :: String
597+ } deriving Generic -- for the JSON instance
598+
599+instance ToJSON Person
600+```
561601
562602Now, let's teach *lucid* how to render a `Person` as a row in a table, and then
563603a list of `Person`s as a table with a row per person.
564604
565−> -- HTML serialization of a single person
566−> instance ToHtml Person where
567−> toHtml person =
568−> tr_ $ do
569−> td_ (toHtml $ firstName person)
570−> td_ (toHtml $ lastName person)
571−>
572−> -- do not worry too much about this
573−> toHtmlRaw = toHtml
574−>
575−> -- HTML serialization of a list of persons
576−> instance ToHtml [Person] where
577−> toHtml persons = table_ $ do
578−> tr_ $ do
579−> th_ "first name"
580−> th_ "last name"
581−>
582−> -- this just calls toHtml on each person of the list
583−> -- and concatenates the resulting pieces of HTML together
584−> foldMap toHtml persons
585−>
586−> toHtmlRaw = toHtml
605+``` haskell
606+-- HTML serialization of a single person
607+instance ToHtml Person where
608+ toHtml person =
609+ tr_ $ do
610+ td_ (toHtml $ firstName person)
611+ td_ (toHtml $ lastName person)
612+
613+ -- do not worry too much about this
614+ toHtmlRaw = toHtml
615+
616+-- HTML serialization of a list of persons
617+instance ToHtml [Person] where
618+ toHtml persons = table_ $ do
619+ tr_ $ do
620+ th_ "first name"
621+ th_ "last name"
622+
623+ -- this just calls toHtml on each person of the list
624+ -- and concatenates the resulting pieces of HTML together
625+ foldMap toHtml persons
626+
627+ toHtmlRaw = toHtml
628+```
587629
588630We create some `Person` values and serve them as a list:
589631
590−> persons :: [Person]
591−> persons =
592−> [ Person "Isaac" "Newton"
593−> , Person "Albert" "Einstein"
594−> ]
595−>
596−> personAPI :: Proxy PersonAPI
597−> personAPI = Proxy
598−>
599−> server4 :: Server PersonAPI
600−> server4 = return persons
601−>
602−> app2 :: Application
603−> app2 = serve personAPI server4
632+``` haskell
633+persons :: [Person]
634+persons =
635+ [ Person "Isaac" "Newton"
636+ , Person "Albert" "Einstein"
637+ ]
638+
639+personAPI :: Proxy PersonAPI
640+personAPI = Proxy
641+
642+server4 :: Server PersonAPI
643+server4 = return persons
644+
645+app2 :: Application
646+app2 = serve personAPI server4
647+```
604648
605649And we're good to go. You can run this example with `dist/build/tutorial/tutorial 4`.
606650
@@ -666,18 +710,20 @@ class Monad m => MonadIO m where
666710
667711Obviously, the `IO` monad provides a `MonadIO` instance. Hence for any type `e`, `EitherT e IO` has a `MonadIO` instance. So if you want to run any kind of IO computation in your handlers, just use `liftIO`:
668712
669−> type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
670−>
671−> newtype FileContent = FileContent
672−> { content :: String }
673−> deriving Generic
674−>
675−> instance ToJSON FileContent
676−>
677−> server5 :: Server IOAPI1
678−> server5 = do
679−> filecontent <- liftIO (readFile "myfile.txt")
680−> return (FileContent filecontent)
713+``` haskell
714+type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
715+
716+newtype FileContent = FileContent
717+ { content :: String }
718+ deriving Generic
719+
720+instance ToJSON FileContent
721+
722+server5 :: Server IOAPI1
723+server5 = do
724+ filecontent <- liftIO (readFile "myfile.txt")
725+ return (FileContent filecontent)
726+```
681727
682728Failing, through `ServantErr`
683729-----------------------------
@@ -701,23 +747,27 @@ Many standard values are provided out of the box by the `Servant.Server`
701747module. If you want to use these values but add a body or some headers, just
702748use record update syntax:
703749
704−> failingHandler :: EitherT ServantErr IO ()
705−> failingHandler = left myerr
706−>
707−> where myerr :: ServantErr
708−> myerr = err503 { errBody = "Sorry dear user." }
750+``` haskell
751+failingHandler :: EitherT ServantErr IO ()
752+failingHandler = left myerr
753+
754+ where myerr :: ServantErr
755+ myerr = err503 { errBody = "Sorry dear user." }
756+```
709757
710758Here's an example where we return a customised 404-Not-Found error message in
711759the response body if "myfile.txt" isn't there:
712760
713−> server6 :: Server IOAPI1
714−> server6 = do
715−> exists <- liftIO (doesFileExist "myfile.txt")
716−> if exists
717−> then liftIO (readFile "myfile.txt") >>= return . FileContent
718−> else left custom404Err
719−>
720−> where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
761+``` haskell
762+server6 :: Server IOAPI1
763+server6 = do
764+ exists <- liftIO (doesFileExist "myfile.txt")
765+ if exists
766+ then liftIO (readFile "myfile.txt") >>= return . FileContent
767+ else left custom404Err
768+
769+ where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
770+```
721771
722772Let's run this server (`dist/build/tutorial/tutorial 5`) and
723773query it, first without the file and then with the file.
@@ -758,10 +808,12 @@ Response headers
758808To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
759809Note that this changes the type of your API, as we can see in the following example:
760810
761−> type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
762−>
763−> myHandler :: Server MyHandler
764−> myHandler = return $ addHeader 1797 albert
811+``` haskell
812+type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
813+
814+myHandler :: Server MyHandler
815+myHandler = return $ addHeader 1797 albert
816+```
765817
766818
767819Serving static files
@@ -786,18 +838,24 @@ getting-started.
786838
787839The API type will be the following.
788840
789−> type CodeAPI = "code" :> Raw
841+``` haskell
842+type CodeAPI = "code" :> Raw
843+```
790844
791845And the server:
792846
793−> codeAPI :: Proxy CodeAPI
794−> codeAPI = Proxy
847+``` haskell
848+codeAPI :: Proxy CodeAPI
849+codeAPI = Proxy
850+```
851+
852+``` haskell
853+server7 :: Server CodeAPI
854+server7 = serveDirectory "tutorial"
795855
796−> server7 :: Server CodeAPI
797−> server7 = serveDirectory "tutorial"
798−>
799−> app3 :: Application
800−> app3 = serve codeAPI server7
856+app3 :: Application
857+app3 = serve codeAPI server7
858+```
801859
802860This server will match any request whose path starts with `/code` and will look for a file at the path described by the rest of the request path, inside the *tutorial/* directory of the path you run the program from.
803861
@@ -903,18 +961,22 @@ Nested APIs
903961
904962Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
905963
906−> type UserAPI3 = -- view the user with given userid, in JSON
907−> Capture "userid" Int :> Get '[JSON] User
908−>
909−> :<|> -- delete the user with given userid. empty response
910−> Capture "userid" Int :> Delete '[] ()
964+``` haskell
965+type UserAPI3 = -- view the user with given userid, in JSON
966+ Capture "userid" Int :> Get '[JSON] User
967+
968+ :<|> -- delete the user with given userid. empty response
969+ Capture "userid" Int :> Delete '[] ()
970+```
911971
912972We can instead factor out the `userid`:
913973
914−> type UserAPI4 = Capture "userid" Int :>
915−> ( Get '[JSON] User
916−> :<|> Delete '[] ()
917−> )
974+``` haskell
975+type UserAPI4 = Capture "userid" Int :>
976+ ( Get '[JSON] User
977+ :<|> Delete '[] ()
978+ )
979+```
918980
919981However, you have to be aware that this has an effect on the type of the corresponding `Server`:
920982
@@ -930,146 +992,158 @@ Server UserAPI4 = Int -> ( EitherT ServantErr IO User
930992In the first case, each handler receives the *userid* argument. In the latter,
931993the whole `Server` takes the *userid* and has handlers that are just computations in `EitherT`, with no arguments. In other words:
932994
933−> server8 :: Server UserAPI3
934−> server8 = getUser :<|> deleteUser
935−>
936−> where getUser :: Int -> EitherT ServantErr IO User
937−> getUser _userid = error "..."
938−>
939−> deleteUser :: Int -> EitherT ServantErr IO ()
940−> deleteUser _userid = error "..."
941−>
942−> -- notice how getUser and deleteUser
943−> -- have a different type! no argument anymore,
944−> -- the argument directly goes to the whole Server
945−> server9 :: Server UserAPI4
946−> server9 userid = getUser userid :<|> deleteUser userid
947−>
948−> where getUser :: Int -> EitherT ServantErr IO User
949−> getUser = error "..."
950−>
951−> deleteUser :: Int -> EitherT ServantErr IO ()
952−> deleteUser = error "..."
995+``` haskell
996+server8 :: Server UserAPI3
997+server8 = getUser :<|> deleteUser
998+
999+ where getUser :: Int -> EitherT ServantErr IO User
1000+ getUser _userid = error "..."
1001+
1002+ deleteUser :: Int -> EitherT ServantErr IO ()
1003+ deleteUser _userid = error "..."
1004+
1005+-- notice how getUser and deleteUser
1006+-- have a different type! no argument anymore,
1007+-- the argument directly goes to the whole Server
1008+server9 :: Server UserAPI4
1009+server9 userid = getUser userid :<|> deleteUser userid
1010+
1011+ where getUser :: Int -> EitherT ServantErr IO User
1012+ getUser = error "..."
1013+
1014+ deleteUser :: Int -> EitherT ServantErr IO ()
1015+ deleteUser = error "..."
1016+```
9531017
9541018Note that there's nothing special about `Capture` that lets you "factor it out": this can be done with any combinator. Here are a few examples of APIs with a combinator factored out for which we can write a perfectly valid `Server`.
9551019
956−> -- we just factor out the "users" path fragment
957−> type API1 = "users" :>
958−> ( Get '[JSON] [User] -- user listing
959−> :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
960−> )
961−>
962−> -- we factor out the Request Body
963−> type API2 = ReqBody '[JSON] User :>
964−> ( Get '[JSON] User -- just display the same user back, don't register it
965−> :<|> Post '[JSON] () -- register the user. empty response
966−> )
967−>
968−> -- we factor out a Header
969−> type API3 = Header "Authorization" Token :>
970−> ( Get '[JSON] SecretData -- get some secret data, if authorized
971−> :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
972−> )
973−>
974−> newtype Token = Token ByteString
975−> newtype SecretData = SecretData ByteString
1020+``` haskell
1021+-- we just factor out the "users" path fragment
1022+type API1 = "users" :>
1023+ ( Get '[JSON] [User] -- user listing
1024+ :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
1025+ )
1026+
1027+-- we factor out the Request Body
1028+type API2 = ReqBody '[JSON] User :>
1029+ ( Get '[JSON] User -- just display the same user back, don't register it
1030+ :<|> Post '[JSON] () -- register the user. empty response
1031+ )
1032+
1033+-- we factor out a Header
1034+type API3 = Header "Authorization" Token :>
1035+ ( Get '[JSON] SecretData -- get some secret data, if authorized
1036+ :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
1037+ )
1038+
1039+newtype Token = Token ByteString
1040+newtype SecretData = SecretData ByteString
1041+```
9761042
9771043This approach lets you define APIs modularly and assemble them all into one big API type only at the end.
9781044
979−> type UsersAPI =
980−> Get '[JSON] [User] -- list users
981−> :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
982−> :<|> Capture "userid" Int :>
983−> ( Get '[JSON] User -- view a user
984−> :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
985−> :<|> Delete '[] () -- delete a user
986−> )
987−>
988−> usersServer :: Server UsersAPI
989−> usersServer = getUsers :<|> newUser :<|> userOperations
990−>
991−> where getUsers :: EitherT ServantErr IO [User]
992−> getUsers = error "..."
993−>
994−> newUser :: User -> EitherT ServantErr IO ()
995−> newUser = error "..."
996−>
997−> userOperations userid =
998−> viewUser userid :<|> updateUser userid :<|> deleteUser userid
999−>
1000−> where
1001−> viewUser :: Int -> EitherT ServantErr IO User
1002−> viewUser = error "..."
1003−>
1004−> updateUser :: Int -> User -> EitherT ServantErr IO ()
1005−> updateUser = error "..."
1006−>
1007−> deleteUser :: Int -> EitherT ServantErr IO ()
1008−> deleteUser = error "..."
1009−
1010−> type ProductsAPI =
1011−> Get '[JSON] [Product] -- list products
1012−> :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
1013−> :<|> Capture "productid" Int :>
1014−> ( Get '[JSON] Product -- view a product
1015−> :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
1016−> :<|> Delete '[] () -- delete a product
1017−> )
1018−>
1019−> data Product = Product { productId :: Int }
1020−>
1021−> productsServer :: Server ProductsAPI
1022−> productsServer = getProducts :<|> newProduct :<|> productOperations
1023−>
1024−> where getProducts :: EitherT ServantErr IO [Product]
1025−> getProducts = error "..."
1026−>
1027−> newProduct :: Product -> EitherT ServantErr IO ()
1028−> newProduct = error "..."
1029−>
1030−> productOperations productid =
1031−> viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1032−>
1033−> where
1034−> viewProduct :: Int -> EitherT ServantErr IO Product
1035−> viewProduct = error "..."
1036−>
1037−> updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1038−> updateProduct = error "..."
1039−>
1040−> deleteProduct :: Int -> EitherT ServantErr IO ()
1041−> deleteProduct = error "..."
1042−
1043−> type CombinedAPI = "users" :> UsersAPI
1044−> :<|> "products" :> ProductsAPI
1045−>
1046−> server10 :: Server CombinedAPI
1047−> server10 = usersServer :<|> productsServer
1045+``` haskell
1046+type UsersAPI =
1047+ Get '[JSON] [User] -- list users
1048+ :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
1049+ :<|> Capture "userid" Int :>
1050+ ( Get '[JSON] User -- view a user
1051+ :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
1052+ :<|> Delete '[] () -- delete a user
1053+ )
1054+
1055+usersServer :: Server UsersAPI
1056+usersServer = getUsers :<|> newUser :<|> userOperations
1057+
1058+ where getUsers :: EitherT ServantErr IO [User]
1059+ getUsers = error "..."
1060+
1061+ newUser :: User -> EitherT ServantErr IO ()
1062+ newUser = error "..."
1063+
1064+ userOperations userid =
1065+ viewUser userid :<|> updateUser userid :<|> deleteUser userid
1066+
1067+ where
1068+ viewUser :: Int -> EitherT ServantErr IO User
1069+ viewUser = error "..."
1070+
1071+ updateUser :: Int -> User -> EitherT ServantErr IO ()
1072+ updateUser = error "..."
1073+
1074+ deleteUser :: Int -> EitherT ServantErr IO ()
1075+ deleteUser = error "..."
1076+```
1077+
1078+``` haskell
1079+type ProductsAPI =
1080+ Get '[JSON] [Product] -- list products
1081+ :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
1082+ :<|> Capture "productid" Int :>
1083+ ( Get '[JSON] Product -- view a product
1084+ :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
1085+ :<|> Delete '[] () -- delete a product
1086+ )
1087+
1088+data Product = Product { productId :: Int }
1089+
1090+productsServer :: Server ProductsAPI
1091+productsServer = getProducts :<|> newProduct :<|> productOperations
1092+
1093+ where getProducts :: EitherT ServantErr IO [Product]
1094+ getProducts = error "..."
1095+
1096+ newProduct :: Product -> EitherT ServantErr IO ()
1097+ newProduct = error "..."
1098+
1099+ productOperations productid =
1100+ viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1101+
1102+ where
1103+ viewProduct :: Int -> EitherT ServantErr IO Product
1104+ viewProduct = error "..."
1105+
1106+ updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1107+ updateProduct = error "..."
1108+
1109+ deleteProduct :: Int -> EitherT ServantErr IO ()
1110+ deleteProduct = error "..."
1111+```
1112+
1113+``` haskell
1114+type CombinedAPI = "users" :> UsersAPI
1115+ :<|> "products" :> ProductsAPI
1116+
1117+server10 :: Server CombinedAPI
1118+server10 = usersServer :<|> productsServer
1119+```
10481120
10491121Finally, we can realize the user and product APIs are quite similar and abstract that away:
10501122
1051−> -- API for values of type 'a'
1052−> -- indexed by values of type 'i'
1053−> type APIFor a i =
1054−> Get '[JSON] [a] -- list 'a's
1055−> :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
1056−> :<|> Capture "id" i :>
1057−> ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1058−> :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
1059−> :<|> Delete '[] () -- delete an 'a'
1060−> )
1061−>
1062−> -- Build the appropriate 'Server'
1063−> -- given the handlers of the right type.
1064−> serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1065−> -> (a -> EitherT ServantErr IO ()) -- handler for adding an 'a'
1066−> -> (i -> EitherT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1067−> -> (i -> a -> EitherT ServantErr IO ()) -- updating an 'a' with given id
1068−> -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1069−> -> Server (APIFor a i)
1070−> serverFor = error "..."
1071−> -- implementation left as an exercise. contact us on IRC
1072−> -- or the mailing list if you get stuck!
1123+``` haskell
1124+-- API for values of type 'a'
1125+-- indexed by values of type 'i'
1126+type APIFor a i =
1127+ Get '[JSON] [a] -- list 'a's
1128+ :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
1129+ :<|> Capture "id" i :>
1130+ ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1131+ :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
1132+ :<|> Delete '[] () -- delete an 'a'
1133+ )
1134+
1135+-- Build the appropriate 'Server'
1136+-- given the handlers of the right type.
1137+serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1138+ -> (a -> EitherT ServantErr IO ()) -- handler for adding an 'a'
1139+ -> (i -> EitherT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1140+ -> (i -> a -> EitherT ServantErr IO ()) -- updating an 'a' with given id
1141+ -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1142+ -> Server (APIFor a i)
1143+serverFor = error "..."
1144+-- implementation left as an exercise. contact us on IRC
1145+-- or the mailing list if you get stuck!
1146+```
10731147
10741148Using another monad for your handlers
10751149=====================================
@@ -1112,28 +1186,32 @@ computation by supplying it with a `String`, like `"hi"`. We get an `a` out
11121186from that and can then just `return` it into `EitherT`. We can then just wrap
11131187that function with the `Nat` constructor to make it have the fancier type.
11141188
1115−> readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1116−> readerToEither' r = return (runReader r "hi")
1117−>
1118−> readerToEither :: Reader String :~> EitherT ServantErr IO
1119−> readerToEither = Nat readerToEither'
1189+``` haskell
1190+readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1191+readerToEither' r = return (runReader r "hi")
1192+
1193+readerToEither :: Reader String :~> EitherT ServantErr IO
1194+readerToEither = Nat readerToEither'
1195+```
11201196
11211197We can write some simple webservice with the handlers running in `Reader String`.
11221198
1123−> type ReaderAPI = "a" :> Get '[JSON] Int
1124−> :<|> "b" :> Get '[JSON] String
1125−>
1126−> readerAPI :: Proxy ReaderAPI
1127−> readerAPI = Proxy
1128−>
1129−> readerServerT :: ServerT ReaderAPI (Reader String)
1130−> readerServerT = a :<|> b
1131−>
1132−> where a :: Reader String Int
1133−> a = return 1797
1134−>
1135−> b :: Reader String String
1136−> b = ask
1199+``` haskell
1200+type ReaderAPI = "a" :> Get '[JSON] Int
1201+ :<|> "b" :> Get '[JSON] String
1202+
1203+readerAPI :: Proxy ReaderAPI
1204+readerAPI = Proxy
1205+
1206+readerServerT :: ServerT ReaderAPI (Reader String)
1207+readerServerT = a :<|> b
1208+
1209+ where a :: Reader String Int
1210+ a = return 1797
1211+
1212+ b :: Reader String String
1213+ b = ask
1214+```
11371215
11381216We unfortunately can't use `readerServerT` as an argument of `serve`, because
11391217`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `EitherT
@@ -1150,11 +1228,13 @@ and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
11501228
11511229In our case, we can wrap up our little webservice by using `enter readerToEither` on our handlers.
11521230
1153−> readerServer :: Server ReaderAPI
1154−> readerServer = enter readerToEither readerServerT
1155−>
1156−> app4 :: Application
1157−> app4 = serve readerAPI readerServer
1231+``` haskell
1232+readerServer :: Server ReaderAPI
1233+readerServer = enter readerToEither readerServerT
1234+
1235+app4 :: Application
1236+app4 = serve readerAPI readerServer
1237+```
11581238
11591239And we can indeed see this webservice in action by running `dist/build/tutorial/tutorial 7`.
11601240