git.iko.soy/typeable/servant
on master help sign in
Remove bird-tracks
8b1bf02Julian K. Arni
doc/tutorial/api-type.lhs+80−56
@@ -6,13 +6,15 @@ toc: true
6The source for this tutorial section is a literate haskell file, so first we
6The source for this tutorial section is a literate haskell file, so first we
7need to have some language extensions and imports:
7need to have some language extensions and imports:
8
8
9+``` haskell
9−> {-# LANGUAGE DataKinds #-}
10+{-# LANGUAGE DataKinds #-}
10−> {-# LANGUAGE TypeOperators #-}
11+{-# LANGUAGE TypeOperators #-}
11−>
12+
12−> module ApiType where
13+module ApiType where
13−>
14+
14−> import Data.Text
15+import Data.Text
15−> import Servant.API
16+import Servant.API
17+```
16
18
17Consider the following informal specification of an API:
19Consider the following informal specification of an API:
18
20
@@ -29,14 +31,16 @@ getting some client libraries, and documentation (and in the future, who knows
29How would we describe it with servant? As mentioned earlier, an endpoint
31How would we describe it with servant? As mentioned earlier, an endpoint
30description is a good old Haskell **type**:
32description is a good old Haskell **type**:
31
33
34+``` haskell
32−> type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
35+type UserAPI = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
33−>
36+
34−> data SortBy = Age | Name
37+data SortBy = Age | Name
35−>
38+
36−> data User = User {
39+data User = User {
37−> name :: String,
40+ name :: String,
38−> age :: Int
41+ age :: Int
39−> }
42+}
43+```
40
44
41Let's break that down:
45Let's break that down:
42
46
@@ -61,8 +65,10 @@ equivalent to `/`, but sometimes it just lets you chain another combinator.
61We can also describe APIs with multiple endpoints by using the `:<|>`
65We can also describe APIs with multiple endpoints by using the `:<|>`
62combinators. Here's an example:
66combinators. Here's an example:
63
67
68+``` haskell
64−> type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
69+type UserAPI2 = "users" :> "list-all" :> Get '[JSON] [User]
65−> :<|> "list-all" :> "users" :> Get '[JSON] [User]
70+ :<|> "list-all" :> "users" :> Get '[JSON] [User]
71+```
66
72
67*servant* provides a fair amount of combinators out-of-the-box, but you can
73*servant* provides a fair amount of combinators out-of-the-box, but you can
68always write your own when you need it. Here's a quick overview of all the
74always 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
78`DataKinds` language extension) for static path fragments. Chaining
84`DataKinds` language extension) for static path fragments. Chaining
79them amounts to `/`-separating them in a URL.
85them amounts to `/`-separating them in a URL.
80
86
87+``` haskell
81−> type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
88+type UserAPI3 = "users" :> "list-all" :> "now" :> Get '[JSON] [User]
82−> -- describes an endpoint reachable at:
89+ -- describes an endpoint reachable at:
83−> -- /users/list-all/now
90+ -- /users/list-all/now
91+```
84
92
85`Delete`, `Get`, `Patch`, `Post` and `Put`
93`Delete`, `Get`, `Patch`, `Post` and `Put`
86------------------------------------------
94------------------------------------------
@@ -99,8 +107,10 @@ data Put (contentTypes :: [*]) a
99An endpoint ends with one of the 5 combinators above (unless you write your
107An endpoint ends with one of the 5 combinators above (unless you write your
100own). Examples:
108own). Examples:
101
109
110+``` haskell
102−> type UserAPI4 = "users" :> Get '[JSON] [User]
111+type UserAPI4 = "users" :> Get '[JSON] [User]
103−> :<|> "admins" :> Get '[JSON] [User]
112+ :<|> "admins" :> Get '[JSON] [User]
113+```
104
114
105`Capture`
115`Capture`
106---------
116---------
@@ -127,13 +137,15 @@ class, which the captured value must be an instance of.
127
137
128Examples:
138Examples:
129
139
140+``` haskell
130−> type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
141+type UserAPI5 = "user" :> Capture "userid" Integer :> Get '[JSON] User
131−> -- equivalent to 'GET /user/:userid'
142+ -- equivalent to 'GET /user/:userid'
132−> -- except that we explicitly say that "userid"
143+ -- except that we explicitly say that "userid"
133−> -- must be an integer
144+ -- must be an integer
134−>
145+
135−> :<|> "user" :> Capture "userid" Integer :> Delete '[] ()
146+ :<|> "user" :> Capture "userid" Integer :> Delete '[] ()
136−> -- equivalent to 'DELETE /user/:userid'
147+ -- equivalent to 'DELETE /user/:userid'
148+```
137
149
138`QueryParam`, `QueryParams`, `QueryFlag`, `MatrixParam`, `MatrixParams` and `MatrixFlag`
150`QueryParam`, `QueryParams`, `QueryFlag`, `MatrixParam`, `MatrixParams` and `MatrixFlag`
139----------------------------------------------------------------------------------------
151----------------------------------------------------------------------------------------
@@ -179,11 +191,13 @@ data MatrixFlag (sym :: Symbol)
179
191
180Examples:
192Examples:
181
193
194+``` haskell
182−> type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
195+type UserAPI6 = "users" :> QueryParam "sortby" SortBy :> Get '[JSON] [User]
183−> -- equivalent to 'GET /users?sortby={age, name}'
196+ -- equivalent to 'GET /users?sortby={age, name}'
184−>
197+
185−> :<|> "users" :> MatrixParam "sortby" SortBy :> Get '[JSON] [User]
198+ :<|> "users" :> MatrixParam "sortby" SortBy :> Get '[JSON] [User]
186−> -- equivalent to 'GET /users;sortby={age, name}'
199+ -- equivalent to 'GET /users;sortby={age, name}'
200+```
187
201
188Again, your handlers don't have to deserialize these things (into, for example,
202Again, your handlers don't have to deserialize these things (into, for example,
189a `SortBy`). *servant* takes care of it.
203a `SortBy`). *servant* takes care of it.
@@ -212,17 +226,19 @@ data ReqBody (contentTypes :: [*]) a
212
226
213Examples:
227Examples:
214
228
229+``` haskell
215−> type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
230+type UserAPI7 = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
216−> -- - equivalent to 'POST /users' with a JSON object
231+ -- - equivalent to 'POST /users' with a JSON object
217−> -- describing a User in the request body
232+ -- describing a User in the request body
218−> -- - returns a User encoded in JSON
233+ -- - returns a User encoded in JSON
219−>
234+
220−> :<|> "users" :> Capture "userid" Integer
235+ :<|> "users" :> Capture "userid" Integer
221−> :> ReqBody '[JSON] User
236+ :> ReqBody '[JSON] User
222−> :> Put '[JSON] User
237+ :> Put '[JSON] User
223−> -- - equivalent to 'PUT /users/:userid' with a JSON
238+ -- - equivalent to 'PUT /users/:userid' with a JSON
224−> -- object describing a User in the request body
239+ -- object describing a User in the request body
225−> -- - returns a User encoded in JSON
240+ -- - returns a User encoded in JSON
241+```
226
242
227Request `Header`s
243Request `Header`s
228-----------------
244-----------------
@@ -243,7 +259,9 @@ Here's an example where we declare that an endpoint makes use of the
243`User-Agent` header which specifies the name of the software/library used by
259`User-Agent` header which specifies the name of the software/library used by
244the client to send the request.
260the client to send the request.
245
261
262+``` haskell
246−> type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
263+type UserAPI8 = "users" :> Header "User-Agent" Text :> Get '[JSON] [User]
264+```
247
265
248Content types
266Content types
249-------------
267-------------
@@ -257,7 +275,9 @@ Four content-types are provided out-of-the-box by the core *servant* package:
257reason you wanted one of your endpoints to make your user data available under
275reason you wanted one of your endpoints to make your user data available under
258those 4 formats, you would write the API type as below:
276those 4 formats, you would write the API type as below:
259
277
278+``` haskell
260−> type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
279+type UserAPI9 = "users" :> Get '[JSON, PlainText, FormUrlEncoded, OctetStream] [User]
280+```
261
281
262We also provide an HTML content-type, but since there's no single library
282We also provide an HTML content-type, but since there's no single library
263that everyone uses, we decided to release 2 packages, *servant-lucid* and
283that everyone uses, we decided to release 2 packages, *servant-lucid* and
@@ -281,7 +301,9 @@ data Headers (ls :: [*]) a
281If you want to describe an endpoint that returns a "User-Count" header in each
301If you want to describe an endpoint that returns a "User-Count" header in each
282response, you could write it as below:
302response, you could write it as below:
283
303
304+``` haskell
284−> type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
305+type UserAPI10 = "users" :> Get '[JSON] (Headers '[Header "User-Count" Integer] [User])
306+```
285
307
286Interoperability with other WAI `Application`s: `Raw`
308Interoperability with other WAI `Application`s: `Raw`
287-----------------------------------------------------
309-----------------------------------------------------
@@ -290,14 +312,16 @@ Finally, we also include a combinator named `Raw` that can be used for two reaso
290
312
291- You want to serve static files from a given directory. In that case you can just say:
313- You want to serve static files from a given directory. In that case you can just say:
292
314
315+``` haskell
293−> type UserAPI11 = "users" :> Get '[JSON] [User]
316+type UserAPI11 = "users" :> Get '[JSON] [User]
294−> -- a /users endpoint
317+ -- a /users endpoint
295−>
318+
296−> :<|> Raw
319+ :<|> Raw
297−> -- requests to anything else than /users
320+ -- requests to anything else than /users
298−> -- go here, where the server will try to
321+ -- go here, where the server will try to
299−> -- find a file with the right name
322+ -- find a file with the right name
300−> -- at the right path
323+ -- at the right path
324+```
301
325
302- You more generally want to plug a [WAI `Application`](http://hackage.haskell.org/package/wai)
326- You more generally want to plug a [WAI `Application`](http://hackage.haskell.org/package/wai)
303into your webservice. Static file serving is a specific example of that. The API type would look the
327into 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
11The source for this tutorial section is a literate haskell file, so first we
11The source for this tutorial section is a literate haskell file, so first we
12need to have some language extensions and imports:
12need to have some language extensions and imports:
13
13
14+``` haskell
14−> {-# LANGUAGE DataKinds #-}
15+{-# LANGUAGE DataKinds #-}
15−> {-# LANGUAGE DeriveGeneric #-}
16+{-# LANGUAGE DeriveGeneric #-}
16−> {-# LANGUAGE TypeOperators #-}
17+{-# LANGUAGE TypeOperators #-}
17−>
18+
18−> module Client where
19+module Client where
19−>
20+
20−> import Control.Monad.Trans.Either
21+import Control.Monad.Trans.Either
21−> import Data.Aeson
22+import Data.Aeson
22−> import Data.Proxy
23+import Data.Proxy
23−> import GHC.Generics
24+import GHC.Generics
24−> import Servant.API
25+import Servant.API
25−> import Servant.Client
26+import Servant.Client
27+```
26
28
27Also, we need examples for some domain specific data types:
29Also, we need examples for some domain specific data types:
28
30
31+``` haskell
29−> data Position = Position
32+data Position = Position
30−> { x :: Int
33+ { x :: Int
31−> , y :: Int
34+ , y :: Int
32−> } deriving (Show, Generic)
35+ } deriving (Show, Generic)
33−>
36+
34−> instance FromJSON Position
37+instance FromJSON Position
35−>
38+
36−> newtype HelloMessage = HelloMessage { msg :: String }
39+newtype HelloMessage = HelloMessage { msg :: String }
37−> deriving (Show, Generic)
40+ deriving (Show, Generic)
38−>
41+
39−> instance FromJSON HelloMessage
42+instance FromJSON HelloMessage
40−>
43+
41−> data ClientInfo = ClientInfo
44+data ClientInfo = ClientInfo
42−> { clientName :: String
45+ { clientName :: String
43−> , clientEmail :: String
46+ , clientEmail :: String
44−> , clientAge :: Int
47+ , clientAge :: Int
45−> , clientInterestedIn :: [String]
48+ , clientInterestedIn :: [String]
46−> } deriving Generic
49+ } deriving Generic
47−>
50+
48−> instance ToJSON ClientInfo
51+instance ToJSON ClientInfo
49−>
52+
50−> data Email = Email
53+data Email = Email
51−> { from :: String
54+ { from :: String
52−> , to :: String
55+ , to :: String
53−> , subject :: String
56+ , subject :: String
54−> , body :: String
57+ , body :: String
55−> } deriving (Show, Generic)
58+ } deriving (Show, Generic)
56−>
59+
57−> instance FromJSON Email
60+instance FromJSON Email
61+```
58
62
59Enough chitchat, let's see an example. Consider the following API type from the previous section:
63Enough chitchat, let's see an example. Consider the following API type from the previous section:
60
64
65+``` haskell
61−> type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
66+type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
62−> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
67+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
63−> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
68+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
69+```
64
70
65What we are going to get with *servant-client* here is 3 functions, one to query each endpoint:
71What we are going to get with *servant-client* here is 3 functions, one to query each endpoint:
66
72
73+``` haskell
67−> position :: Int -- ^ value for "x"
74+position :: Int -- ^ value for "x"
68−> -> Int -- ^ value for "y"
75+ -> Int -- ^ value for "y"
69−> -> EitherT ServantError IO Position
76+ -> EitherT ServantError IO Position
70−>
77+
71−> hello :: Maybe String -- ^ an optional value for "name"
78+hello :: Maybe String -- ^ an optional value for "name"
72−> -> EitherT ServantError IO HelloMessage
79+ -> EitherT ServantError IO HelloMessage
73−>
80+
74−> marketing :: ClientInfo -- ^ value for the request body
81+marketing :: ClientInfo -- ^ value for the request body
75−> -> EitherT ServantError IO Email
82+ -> EitherT ServantError IO Email
83+```
76
84
77Each 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:
85Each 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:
78
86
87+``` haskell
79−> api :: Proxy API
88+api :: Proxy API
80−> api = Proxy
89+api = Proxy
81−>
90+
82−> position :<|> hello :<|> marketing = client api (BaseUrl Http "localhost" 8081)
91+position :<|> hello :<|> marketing = client api (BaseUrl Http "localhost" 8081)
92+```
83
93
84As 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:
94As 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:
85
95
@@ -101,22 +111,24 @@ data BaseUrl = BaseUrl
101
111
102That's it. Let's now write some code that uses our client functions.
112That's it. Let's now write some code that uses our client functions.
103
113
114+``` haskell
104−> queries :: EitherT ServantError IO (Position, HelloMessage, Email)
115+queries :: EitherT ServantError IO (Position, HelloMessage, Email)
105−> queries = do
116+queries = do
106−> pos <- position 10 10
117+ pos <- position 10 10
107−> msg <- hello (Just "servant")
118+ msg <- hello (Just "servant")
108−> em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
119+ em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
109−> return (pos, msg, em)
120+ return (pos, msg, em)
110−>
121+
111−> run :: IO ()
122+run :: IO ()
112−> run = do
123+run = do
113−> res <- runEitherT queries
124+ res <- runEitherT queries
114−> case res of
125+ case res of
115−> Left err -> putStrLn $ "Error: " ++ show err
126+ Left err -> putStrLn $ "Error: " ++ show err
116−> Right (pos, msg, em) -> do
127+ Right (pos, msg, em) -> do
117−> print pos
128+ print pos
118−> print msg
129+ print msg
119−> print em
130+ print em
131+```
120
132
121You can now run `dist/build/tutorial/tutorial 8` (the server) and
133You can now run `dist/build/tutorial/tutorial 8` (the server) and
122`dist/build/t8-main/t8-main` (the client) to see them both in action.
134`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
6The source for this tutorial section is a literate haskell file, so first we
6The source for this tutorial section is a literate haskell file, so first we
7need to have some language extensions and imports:
7need to have some language extensions and imports:
8
8
9+``` haskell
9−> {-# LANGUAGE DataKinds #-}
10+{-# LANGUAGE DataKinds #-}
10−> {-# LANGUAGE DeriveGeneric #-}
11+{-# LANGUAGE DeriveGeneric #-}
11−> {-# LANGUAGE FlexibleInstances #-}
12+{-# LANGUAGE FlexibleInstances #-}
12−> {-# LANGUAGE MultiParamTypeClasses #-}
13+{-# LANGUAGE MultiParamTypeClasses #-}
13−> {-# LANGUAGE OverloadedStrings #-}
14+{-# LANGUAGE OverloadedStrings #-}
14−> {-# LANGUAGE TypeOperators #-}
15+{-# LANGUAGE TypeOperators #-}
15−> {-# OPTIONS_GHC -fno-warn-orphans #-}
16+{-# OPTIONS_GHC -fno-warn-orphans #-}
16−>
17+
17−> module Docs where
18+module Docs where
18−>
19+
19−> import Data.ByteString.Lazy (ByteString)
20+import Data.ByteString.Lazy (ByteString)
20−> import Data.Proxy
21+import Data.Proxy
21−> import Data.Text.Lazy.Encoding (encodeUtf8)
22+import Data.Text.Lazy.Encoding (encodeUtf8)
22−> import Data.Text.Lazy (pack)
23+import Data.Text.Lazy (pack)
23−> import Network.HTTP.Types
24+import Network.HTTP.Types
24−> import Network.Wai
25+import Network.Wai
25−> import Servant.API
26+import Servant.API
26−> import Servant.Docs
27+import Servant.Docs
27−> import Servant.Server
28+import Servant.Server
29+```
28
30
29And we'll import some things from one of our earlier modules
31And we'll import some things from one of our earlier modules
30([Serving an API](/tutorial/server.html)):
32([Serving an API](/tutorial/server.html)):
31
33
34+``` haskell
32−> import Server (Email(..), ClientInfo(..), Position(..), HelloMessage(..),
35+import Server (Email(..), ClientInfo(..), Position(..), HelloMessage(..),
33−> server3, emailForClient)
36+ server3, emailForClient)
37+```
34
38
35Like 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.
39Like 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.
36
40
37This 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:
41This 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:
38
42
43+``` haskell
39−> type ExampleAPI = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
44+type ExampleAPI = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
40−> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
45+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
41−> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
46+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
42−>
47+
43−> exampleAPI :: Proxy ExampleAPI
48+exampleAPI :: Proxy ExampleAPI
44−> exampleAPI = Proxy
49+exampleAPI = Proxy
50+```
45
51
46While *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.
52While *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.
47
53
48For 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.
54For 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.
49
55
56+``` haskell
50−> instance ToCapture (Capture "x" Int) where
57+instance ToCapture (Capture "x" Int) where
51−> toCapture _ =
58+ toCapture _ =
52−> DocCapture "x" -- name
59+ DocCapture "x" -- name
53−> "(integer) position on the x axis" -- description
60+ "(integer) position on the x axis" -- description
54−>
61+
55−> instance ToCapture (Capture "y" Int) where
62+instance ToCapture (Capture "y" Int) where
56−> toCapture _ =
63+ toCapture _ =
57−> DocCapture "y" -- name
64+ DocCapture "y" -- name
58−> "(integer) position on the y axis" -- description
65+ "(integer) position on the y axis" -- description
59−>
66+
60−> instance ToSample Position Position where
67+instance ToSample Position Position where
61−> toSample _ = Just (Position 3 14) -- example of output
68+ toSample _ = Just (Position 3 14) -- example of output
62−>
69+
63−> instance ToParam (QueryParam "name" String) where
70+instance ToParam (QueryParam "name" String) where
64−> toParam _ =
71+ toParam _ =
65−> DocQueryParam "name" -- name
72+ DocQueryParam "name" -- name
66−> ["Alp", "John Doe", "..."] -- example of values (not necessarily exhaustive)
73+ ["Alp", "John Doe", "..."] -- example of values (not necessarily exhaustive)
67−> "Name of the person to say hello to." -- description
74+ "Name of the person to say hello to." -- description
68−> Normal -- Normal, List or Flag
75+ Normal -- Normal, List or Flag
69−>
76+
70−> instance ToSample HelloMessage HelloMessage where
77+instance ToSample HelloMessage HelloMessage where
71−> toSamples _ =
78+ toSamples _ =
72−> [ ("When a value is provided for 'name'", HelloMessage "Hello, Alp")
79+ [ ("When a value is provided for 'name'", HelloMessage "Hello, Alp")
73−> , ("When 'name' is not specified", HelloMessage "Hello, anonymous coward")
80+ , ("When 'name' is not specified", HelloMessage "Hello, anonymous coward")
74−> ]
81+ ]
75−> -- mutliple examples to display this time
82+ -- mutliple examples to display this time
76−>
83+
77−> ci :: ClientInfo
84+ci :: ClientInfo
78−> ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
85+ci = ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]
79−>
86+
80−> instance ToSample ClientInfo ClientInfo where
87+instance ToSample ClientInfo ClientInfo where
81−> toSample _ = Just ci
88+ toSample _ = Just ci
82−>
89+
83−> instance ToSample Email Email where
90+instance ToSample Email Email where
84−> toSample _ = Just (emailForClient ci)
91+ toSample _ = Just (emailForClient ci)
92+```
85
93
86Types 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.
94Types 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.
87
95
88With all of this, we can derive docs for our API.
96With all of this, we can derive docs for our API.
89
97
98+``` haskell
90−> apiDocs :: API
99+apiDocs :: API
91−> apiDocs = docs exampleAPI
100+apiDocs = docs exampleAPI
101+```
92
102
93`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.
103`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.
94
104
@@ -192,33 +202,37 @@ That lets us see what our API docs look down in markdown, by looking at `markdow
192
202
193However, 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.
203However, 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.
194
204
205+``` haskell
195−> docsBS :: ByteString
206+docsBS :: ByteString
196−> docsBS = encodeUtf8
207+docsBS = encodeUtf8
197−> . pack
208+ . pack
198−> . markdown
209+ . markdown
199−> $ docsWithIntros [intro] exampleAPI
210+ $ docsWithIntros [intro] exampleAPI
200−>
211+
201−> where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
212+ where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]
213+```
202
214
203`docsWithIntros` just takes an additional parameter, a list of `DocIntro`s that must be displayed before any endpoint docs.
215`docsWithIntros` just takes an additional parameter, a list of `DocIntro`s that must be displayed before any endpoint docs.
204
216
205We can now serve the API *and* the API docs with a simple server.
217We can now serve the API *and* the API docs with a simple server.
206
218
219+``` haskell
207−> type DocsAPI = ExampleAPI :<|> Raw
220+type DocsAPI = ExampleAPI :<|> Raw
208−>
221+
209−> api :: Proxy DocsAPI
222+api :: Proxy DocsAPI
210−> api = Proxy
223+api = Proxy
211−>
224+
212−> server :: Server DocsAPI
225+server :: Server DocsAPI
213−> server = Server.server3 :<|> serveDocs
226+server = Server.server3 :<|> serveDocs
214−>
227+
215−> where serveDocs _ respond =
228+ where serveDocs _ respond =
216−> respond $ responseLBS ok200 [plain] docsBS
229+ respond $ responseLBS ok200 [plain] docsBS
217−>
230+
218−> plain = ("Content-Type", "text/plain")
231+ plain = ("Content-Type", "text/plain")
219−>
232+
220−> app :: Application
233+app :: Application
221−> app = serve api server
234+app = serve api server
235+```
222
236
223And 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.
237And 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.
224
238
doc/tutorial/javascript.lhs+112−96
@@ -22,117 +22,131 @@ query your API.
22The source for this tutorial section is a literate haskell file, so first we
22The source for this tutorial section is a literate haskell file, so first we
23need to have some language extensions and imports:
23need to have some language extensions and imports:
24
24
25+``` haskell
25−> {-# LANGUAGE DataKinds #-}
26+{-# LANGUAGE DataKinds #-}
26−> {-# LANGUAGE DeriveGeneric #-}
27+{-# LANGUAGE DeriveGeneric #-}
27−> {-# LANGUAGE OverloadedStrings #-}
28+{-# LANGUAGE OverloadedStrings #-}
28−> {-# LANGUAGE TypeOperators #-}
29+{-# LANGUAGE TypeOperators #-}
29−>
30+
30−> module Javascript where
31+module Javascript where
31−>
32+
32−> import Control.Monad.IO.Class
33+import Control.Monad.IO.Class
33−> import Data.Aeson
34+import Data.Aeson
34−> import Data.Proxy
35+import Data.Proxy
35−> import Data.Text (Text)
36+import Data.Text (Text)
36−> import qualified Data.Text as T
37+import qualified Data.Text as T
37−> import GHC.Generics
38+import GHC.Generics
38−> import Language.Javascript.JQuery
39+import Language.Javascript.JQuery
39−> import Network.Wai
40+import Network.Wai
40−> import Servant
41+import Servant
41−> import Servant.JQuery
42+import Servant.JQuery
42−> import System.Random
43+import System.Random
44+```
43
45
44Now let's have the API type(s) and the accompanying datatypes.
46Now let's have the API type(s) and the accompanying datatypes.
45
47
48+``` haskell
46−> type API = "point" :> Get '[JSON] Point
49+type API = "point" :> Get '[JSON] Point
47−> :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
50+ :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)
48−>
51+
49−> type API' = API :<|> Raw
52+type API' = API :<|> Raw
50−>
53+
51−> data Point = Point
54+data Point = Point
52−> { x :: Double
55+ { x :: Double
53−> , y :: Double
56+ , y :: Double
54−> } deriving Generic
57+ } deriving Generic
55−>
58+
56−> instance ToJSON Point
59+instance ToJSON Point
57−>
60+
58−> data Search a = Search
61+data Search a = Search
59−> { query :: Text
62+ { query :: Text
60−> , results :: [a]
63+ , results :: [a]
61−> } deriving Generic
64+ } deriving Generic
62−>
65+
63−> mkSearch :: Text -> [a] -> Search a
66+mkSearch :: Text -> [a] -> Search a
64−> mkSearch = Search
67+mkSearch = Search
65−>
68+
66−> instance ToJSON a => ToJSON (Search a)
69+instance ToJSON a => ToJSON (Search a)
67−>
70+
68−> data Book = Book
71+data Book = Book
69−> { author :: Text
72+ { author :: Text
70−> , title :: Text
73+ , title :: Text
71−> , year :: Int
74+ , year :: Int
72−> } deriving Generic
75+ } deriving Generic
73−>
76+
74−> instance ToJSON Book
77+instance ToJSON Book
75−>
78+
76−> book :: Text -> Text -> Int -> Book
79+book :: Text -> Text -> Int -> Book
77−> book = Book
80+book = Book
81+```
78
82
79We need a "book database". For the purpose of this guide, let's restrict ourselves to the following books.
83We need a "book database". For the purpose of this guide, let's restrict ourselves to the following books.
80
84
85+``` haskell
81−> books :: [Book]
86+books :: [Book]
82−> books =
87+books =
83−> [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000
88+ [ 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
89+ , 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
90+ , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011
86−> , book "Graham Hutton" "Programming in Haskell" 2007
91+ , book "Graham Hutton" "Programming in Haskell" 2007
87−> , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
92+ , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013
88−> , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
93+ , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998
89−> ]
94+ ]
95+```
90
96
91Now, 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.
97Now, 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.
92
98
99+``` haskell
93−> searchBook :: Monad m => Maybe Text -> m (Search Book)
100+searchBook :: Monad m => Maybe Text -> m (Search Book)
94−> searchBook Nothing = return (mkSearch "" books)
101+searchBook Nothing = return (mkSearch "" books)
95−> searchBook (Just q) = return (mkSearch q books')
102+searchBook (Just q) = return (mkSearch q books')
96−>
103+
97−> where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
104+ where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)
98−> || q' `T.isInfixOf` T.toLower (title b)
105+ || q' `T.isInfixOf` T.toLower (title b)
99−> )
106+ )
100−> books
107+ books
101−> q' = T.toLower q
108+ q' = T.toLower q
109+```
102
110
103We 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`.
111We 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`.
104
112
113+``` haskell
105−> randomPoint :: MonadIO m => m Point
114+randomPoint :: MonadIO m => m Point
106−> randomPoint = liftIO . getStdRandom $ \g ->
115+randomPoint = liftIO . getStdRandom $ \g ->
107−> let (rx, g') = randomR (-1, 1) g
116+ let (rx, g') = randomR (-1, 1) g
108−> (ry, g'') = randomR (-1, 1) g'
117+ (ry, g'') = randomR (-1, 1) g'
109−> in (Point rx ry, g'')
118+ in (Point rx ry, g'')
119+```
110
120
111If we add static file serving, our server is now complete.
121If we add static file serving, our server is now complete.
112
122
123+``` haskell
113−> api :: Proxy API
124+api :: Proxy API
114−> api = Proxy
125+api = Proxy
115−>
126+
116−> api' :: Proxy API'
127+api' :: Proxy API'
117−> api' = Proxy
128+api' = Proxy
118−>
129+
119−> server :: Server API
130+server :: Server API
120−> server = randomPoint
131+server = randomPoint
121−> :<|> searchBook
132+ :<|> searchBook
122−>
133+
123−> server' :: Server API'
134+server' :: Server API'
124−> server' = server
135+server' = server
125−> :<|> serveDirectory "tutorial/t9"
136+ :<|> serveDirectory "tutorial/t9"
126−>
137+
127−> app :: Application
138+app :: Application
128−> app = serve api' server'
139+app = serve api' server'
140+```
129
141
130Why 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.
142Why 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.
131
143
132Very similarly to how one can derive haskell functions, we can derive the javascript with just a simple function call to `jsForAPI` from `Servant.JQuery`.
144Very similarly to how one can derive haskell functions, we can derive the javascript with just a simple function call to `jsForAPI` from `Servant.JQuery`.
133
145
146+``` haskell
134−> apiJS :: String
147+apiJS :: String
135−> apiJS = jsForAPI api
148+apiJS = jsForAPI api
149+```
136
150
137This `String` contains 2 Javascript functions:
151This `String` contains 2 Javascript functions:
138
152
@@ -161,11 +175,13 @@ function getbooks(q, onSuccess, onError)
161
175
162Right 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.
176Right 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.
163
177
178+``` haskell
164−> writeJSFiles :: IO ()
179+writeJSFiles :: IO ()
165−> writeJSFiles = do
180+writeJSFiles = do
166−> writeFile "getting-started/gs9/api.js" apiJS
181+ writeFile "getting-started/gs9/api.js" apiJS
167−> jq <- readFile =<< Language.Javascript.JQuery.file
182+ jq <- readFile =<< Language.Javascript.JQuery.file
168−> writeFile "getting-started/gs9/jq.js" jq
183+ writeFile "getting-started/gs9/jq.js" jq
184+```
169
185
170And 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.
186And 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.
171
187
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
34The source for this tutorial section is a literate haskell file, so first we
34The source for this tutorial section is a literate haskell file, so first we
35need to have some language extensions and imports:
35need to have some language extensions and imports:
36
36
37+``` haskell
37−> {-# LANGUAGE DataKinds #-}
38+{-# LANGUAGE DataKinds #-}
38−> {-# LANGUAGE DeriveGeneric #-}
39+{-# LANGUAGE DeriveGeneric #-}
39−> {-# LANGUAGE FlexibleInstances #-}
40+{-# LANGUAGE FlexibleInstances #-}
40−> {-# LANGUAGE GeneralizedNewtypeDeriving #-}
41+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
41−> {-# LANGUAGE MultiParamTypeClasses #-}
42+{-# LANGUAGE MultiParamTypeClasses #-}
42−> {-# LANGUAGE OverloadedStrings #-}
43+{-# LANGUAGE OverloadedStrings #-}
43−> {-# LANGUAGE ScopedTypeVariables #-}
44+{-# LANGUAGE ScopedTypeVariables #-}
44−> {-# LANGUAGE TypeOperators #-}
45+{-# LANGUAGE TypeOperators #-}
45−>
46+
46−> module Server where
47+module Server where
47−>
48+
48−> import Control.Monad.IO.Class
49+import Control.Monad.IO.Class
49−> import Control.Monad.Reader
50+import Control.Monad.Reader
50−> import Control.Monad.Trans.Either
51+import Control.Monad.Trans.Either
51−> import Data.Aeson
52+import Data.Aeson
52−> import Data.Aeson.Types
53+import Data.Aeson.Types
53−> import Data.Attoparsec.ByteString
54+import Data.Attoparsec.ByteString
54−> import Data.ByteString (ByteString)
55+import Data.ByteString (ByteString)
55−> import Data.Int
56+import Data.Int
56−> import Data.List
57+import Data.List
57−> import Data.String.Conversions
58+import Data.String.Conversions
58−> import Data.Time.Calendar
59+import Data.Time.Calendar
59−> import GHC.Generics
60+import GHC.Generics
60−> import Lucid
61+import Lucid
61−> import Network.HTTP.Media ((//), (/:))
62+import Network.HTTP.Media ((//), (/:))
62−> import Network.Wai
63+import Network.Wai
63−> import Network.Wai.Handler.Warp
64+import Network.Wai.Handler.Warp
64−> import Servant
65+import Servant
65−> import System.Directory
66+import System.Directory
66−> import Text.Blaze
67+import Text.Blaze
67−> import Text.Blaze.Html.Renderer.Utf8
68+import Text.Blaze.Html.Renderer.Utf8
68−> import qualified Data.Aeson.Parser
69+import qualified Data.Aeson.Parser
69−> import qualified Text.Blaze.Html
70+import qualified Text.Blaze.Html
71+```
70
72
71``` haskell ignore
73``` haskell ignore
72{-# LANGUAGE TypeFamilies #-}
74{-# LANGUAGE TypeFamilies #-}
@@ -76,7 +78,9 @@ need to have some language extensions and imports:
76
78
77We will write a server that will serve the following API.
79We will write a server that will serve the following API.
78
80
81+``` haskell
79−> type UserAPI1 = "users" :> Get '[JSON] [User]
82+type UserAPI1 = "users" :> Get '[JSON] [User]
83+```
80
84
81Here's what we would like to see when making a GET request to `/users`.
85Here's what we would like to see when making a GET request to `/users`.
82
86
@@ -88,22 +92,26 @@ Here's what we would like to see when making a GET request to `/users`.
88
92
89Now let's define our `User` data type and write some instances for it.
93Now let's define our `User` data type and write some instances for it.
90
94
95+``` haskell
91−> data User = User
96+data User = User
92−> { name :: String
97+ { name :: String
93−> , age :: Int
98+ , age :: Int
94−> , email :: String
99+ , email :: String
95−> , registration_date :: Day
100+ , registration_date :: Day
96−> } deriving (Eq, Show, Generic)
101+ } deriving (Eq, Show, Generic)
97−>
102+
98−> instance ToJSON User
103+instance ToJSON User
104+```
99
105
100Nothing funny going on here. But we now can define our list of two users.
106Nothing funny going on here. But we now can define our list of two users.
101
107
108+``` haskell
102−> users1 :: [User]
109+users1 :: [User]
103−> users1 =
110+users1 =
104−> [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
111+ [ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
105−> , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
112+ , User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
106−> ]
113+ ]
114+```
107
115
108Let's also write our API type.
116Let's also write our API type.
109
117
@@ -129,27 +137,33 @@ HTTP method combinator used for the corresponding endpoint. In our case, it
129means we must provide a handler of type `EitherT ServantErr IO [User]`. Well,
137means we must provide a handler of type `EitherT ServantErr IO [User]`. Well,
130we have a monad, let's just `return` our list:
138we have a monad, let's just `return` our list:
131
139
140+``` haskell
132−> server1 :: Server UserAPI1
141+server1 :: Server UserAPI1
133−> server1 = return users1
142+server1 = return users1
143+```
134
144
135That'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):
145That'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):
136
146
147+``` haskell
137−> userAPI :: Proxy UserAPI1
148+userAPI :: Proxy UserAPI1
138−> userAPI = Proxy
149+userAPI = Proxy
139−>
150+
140−> -- 'serve' comes from servant and hands you a WAI Application,
151+-- 'serve' comes from servant and hands you a WAI Application,
141−> -- which you can think of as an "abstract" web application,
152+-- which you can think of as an "abstract" web application,
142−> -- not yet a webserver.
153+-- not yet a webserver.
143−> app1 :: Application
154+app1 :: Application
144−> app1 = serve userAPI server1
155+app1 = serve userAPI server1
156+```
145
157
146The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
158The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
147But that's about as much boilerplate as you get.
159But that's about as much boilerplate as you get.
148
160
149And we're done! Let's run our webservice on the port 8081.
161And we're done! Let's run our webservice on the port 8081.
150
162
163+``` haskell
151−> main :: IO ()
164+main :: IO ()
152−> main = run 8081 app1
165+main = run 8081 app1
166+```
153
167
154You can put this all into a file or just grab [servant's
168You can put this all into a file or just grab [servant's
155repo](http://github.com/haskell-servant/servant) and look at the
169repo](http://github.com/haskell-servant/servant) and look at the
@@ -170,29 +184,35 @@ More endpoints
170
184
171What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
185What if we want more than one endpoint? Let's add `/albert` and `/isaac` to view the corresponding users encoded in JSON.
172
186
187+``` haskell
173−> type UserAPI2 = "users" :> Get '[JSON] [User]
188+type UserAPI2 = "users" :> Get '[JSON] [User]
174−> :<|> "albert" :> Get '[JSON] User
189+ :<|> "albert" :> Get '[JSON] User
175−> :<|> "isaac" :> Get '[JSON] User
190+ :<|> "isaac" :> Get '[JSON] User
191+```
176
192
177And let's adapt our code a bit.
193And let's adapt our code a bit.
178
194
195+``` haskell
179−> isaac :: User
196+isaac :: User
180−> isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
197+isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
181−>
198+
182−> albert :: User
199+albert :: User
183−> albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
200+albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
184−>
201+
185−> users2 :: [User]
202+users2 :: [User]
186−> users2 = [isaac, albert]
203+users2 = [isaac, albert]
204+```
187
205
188Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
206Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
189are going to separate the handlers with `:<|>` too! They must be provided in
207are going to separate the handlers with `:<|>` too! They must be provided in
190the same order as the one they appear in in the API type.
208the same order as the one they appear in in the API type.
191
209
210+``` haskell
192−> server2 :: Server UserAPI2
211+server2 :: Server UserAPI2
193−> server2 = return users2
212+server2 = return users2
194−> :<|> return albert
213+ :<|> return albert
195−> :<|> return isaac
214+ :<|> return isaac
215+```
196
216
197And that's it! You can run this example with
217And that's it! You can run this example with
198`dist/build/tutorial/tutorial 2` and check out the data available
218`dist/build/tutorial/tutorial 2` and check out the data available
@@ -211,70 +231,74 @@ decoding/encoding data from/to JSON. Never.
211
231
212We are going to use the following data types and functions to implement a server for `API`.
232We are going to use the following data types and functions to implement a server for `API`.
213
233
234+``` haskell
214−> type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
235+type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
215−> :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
236+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
216−> :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
237+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
217−>
238+
218−> data Position = Position
239+data Position = Position
219−> { x :: Int
240+ { x :: Int
220−> , y :: Int
241+ , y :: Int
221−> } deriving Generic
242+ } deriving Generic
222−>
243+
223−> instance ToJSON Position
244+instance ToJSON Position
224−>
245+
225−> newtype HelloMessage = HelloMessage { msg :: String }
246+newtype HelloMessage = HelloMessage { msg :: String }
226−> deriving Generic
247+ deriving Generic
227−>
248+
228−> instance ToJSON HelloMessage
249+instance ToJSON HelloMessage
229−>
250+
230−> data ClientInfo = ClientInfo
251+data ClientInfo = ClientInfo
231−> { clientName :: String
252+ { clientName :: String
232−> , clientEmail :: String
253+ , clientEmail :: String
233−> , clientAge :: Int
254+ , clientAge :: Int
234−> , clientInterestedIn :: [String]
255+ , clientInterestedIn :: [String]
235−> } deriving Generic
256+ } deriving Generic
236−>
257+
237−> instance FromJSON ClientInfo
258+instance FromJSON ClientInfo
238−> instance ToJSON ClientInfo
259+instance ToJSON ClientInfo
239−>
260+
240−> data Email = Email
261+data Email = Email
241−> { from :: String
262+ { from :: String
242−> , to :: String
263+ , to :: String
243−> , subject :: String
264+ , subject :: String
244−> , body :: String
265+ , body :: String
245−> } deriving Generic
266+ } deriving Generic
246−>
267+
247−> instance ToJSON Email
268+instance ToJSON Email
248−>
269+
249−> emailForClient :: ClientInfo -> Email
270+emailForClient :: ClientInfo -> Email
250−> emailForClient c = Email from' to' subject' body'
271+emailForClient c = Email from' to' subject' body'
251−>
272+
252−> where from' = "great@company.com"
273+ where from' = "great@company.com"
253−> to' = clientEmail c
274+ to' = clientEmail c
254−> subject' = "Hey " ++ clientName c ++ ", we miss you!"
275+ subject' = "Hey " ++ clientName c ++ ", we miss you!"
255−> body' = "Hi " ++ clientName c ++ ",\n\n"
276+ body' = "Hi " ++ clientName c ++ ",\n\n"
256−> ++ "Since you've recently turned " ++ show (clientAge c)
277+ ++ "Since you've recently turned " ++ show (clientAge c)
257−> ++ ", have you checked out our latest "
278+ ++ ", have you checked out our latest "
258−> ++ intercalate ", " (clientInterestedIn c)
279+ ++ intercalate ", " (clientInterestedIn c)
259−> ++ " products? Give us a visit!"
280+ ++ " products? Give us a visit!"
281+```
260
282
261We can implement handlers for the three endpoints:
283We can implement handlers for the three endpoints:
262
284
285+``` haskell
263−> server3 :: Server API
286+server3 :: Server API
264−> server3 = position
287+server3 = position
265−> :<|> hello
288+ :<|> hello
266−> :<|> marketing
289+ :<|> marketing
267−>
290+
268−> where position :: Int -> Int -> EitherT ServantErr IO Position
291+ where position :: Int -> Int -> EitherT ServantErr IO Position
269−> position x y = return (Position x y)
292+ position x y = return (Position x y)
270−>
293+
271−> hello :: Maybe String -> EitherT ServantErr IO HelloMessage
294+ hello :: Maybe String -> EitherT ServantErr IO HelloMessage
272−> hello mname = return . HelloMessage $ case mname of
295+ hello mname = return . HelloMessage $ case mname of
273−> Nothing -> "Hello, anonymous coward"
296+ Nothing -> "Hello, anonymous coward"
274−> Just n -> "Hello, " ++ n
297+ Just n -> "Hello, " ++ n
275−>
298+
276−> marketing :: ClientInfo -> EitherT ServantErr IO Email
299+ marketing :: ClientInfo -> EitherT ServantErr IO Email
277−> marketing clientinfo = return (emailForClient clientinfo)
300+ marketing clientinfo = return (emailForClient clientinfo)
301+```
278
302
279Did you see that? The types for your handlers changed to be just what we
303Did you see that? The types for your handlers changed to be just what we
280needed! In particular:
304needed! In particular:
@@ -337,29 +361,31 @@ decoded to provides a `FromText` instance, it will Just Work. *servant*
337provides a decent number of instances, but here are some examples of defining
361provides a decent number of instances, but here are some examples of defining
338your own.
362your own.
339
363
364+``` haskell
340−> -- A typical enumeration
365+-- A typical enumeration
341−> data Direction
366+data Direction
342−> = Up
367+ = Up
343−> | Down
368+ | Down
344−> | Left
369+ | Left
345−> | Right
370+ | Right
346−>
371+
347−> instance FromText Direction where
372+instance FromText Direction where
348−> -- requires {-# LANGUAGE OverloadedStrings #-}
373+ -- requires {-# LANGUAGE OverloadedStrings #-}
349−> fromText "up" = Just Up
374+ fromText "up" = Just Up
350−> fromText "down" = Just Down
375+ fromText "down" = Just Down
351−> fromText "left" = Just Server.Left
376+ fromText "left" = Just Server.Left
352−> fromText "right" = Just Server.Right
377+ fromText "right" = Just Server.Right
353−> fromText _ = Nothing
378+ fromText _ = Nothing
354−>
379+
355−> instance ToText Direction where
380+instance ToText Direction where
356−> toText Up = "up"
381+ toText Up = "up"
357−> toText Down = "down"
382+ toText Down = "down"
358−> toText Server.Left = "left"
383+ toText Server.Left = "left"
359−> toText Server.Right = "right"
384+ toText Server.Right = "right"
360−>
385+
361−> newtype UserId = UserId Int64
386+newtype UserId = UserId Int64
362−> deriving (FromText, ToText)
387+ deriving (FromText, ToText)
388+```
363
389
364or writing the instances by hand:
390or writing the instances by hand:
365
391
@@ -464,10 +490,12 @@ our own little function around *aeson* and *attoparsec* that allows any type of
464JSON value at the toplevel of a "JSON document". Here's the definition in case
490JSON value at the toplevel of a "JSON document". Here's the definition in case
465you are curious.
491you are curious.
466
492
493+``` haskell
467−> eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
494+eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
468−> eitherDecodeLenient input = do
495+eitherDecodeLenient input = do
469−> v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
496+ v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
470−> parseEither parseJSON v
497+ parseEither parseJSON v
498+```
471
499
472This function is exactly what we need for our `MimeUnrender` instance.
500This function is exactly what we need for our `MimeUnrender` instance.
473
501
@@ -492,7 +520,9 @@ or [lucid](http://hackage.haskell.org/package/lucid). The best option for
492*servant* is obviously to support both (and hopefully other templating
520*servant* is obviously to support both (and hopefully other templating
493solutions!).
521solutions!).
494
522
523+``` haskell
495−> data HTMLLucid
524+data HTMLLucid
525+```
496
526
497Once again, the data type is just there as a symbol for the encoding/decoding
527Once again, the data type is just there as a symbol for the encoding/decoding
498functions, except that this time we will only worry about encoding since
528functions, 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
500
530
501Both packages also have the same `Accept` instance for their `HTMLLucid` type.
531Both packages also have the same `Accept` instance for their `HTMLLucid` type.
502
532
533+``` haskell
503−> instance Accept HTMLLucid where
534+instance Accept HTMLLucid where
504−> contentType _ = "text" // "html" /: ("charset", "utf-8")
535+ contentType _ = "text" // "html" /: ("charset", "utf-8")
536+```
505
537
506Note that this instance uses the `(/:)` operator from *http-media* which lets
538Note that this instance uses the `(/:)` operator from *http-media* which lets
507us specify additional information about a content-type, like the charset here.
539us specify additional information about a content-type, like the charset here.
@@ -512,31 +544,35 @@ then write that to a `ByteString`.
512
544
513For *lucid*:
545For *lucid*:
514
546
547+``` haskell
515−> instance ToHtml a => MimeRender HTMLLucid a where
548+instance ToHtml a => MimeRender HTMLLucid a where
516−> mimeRender _ = renderBS . toHtml
549+ mimeRender _ = renderBS . toHtml
517−>
550+
518−> -- let's also provide an instance for lucid's
551+-- let's also provide an instance for lucid's
519−> -- 'Html' wrapper.
552+-- 'Html' wrapper.
520−> instance MimeRender HTMLLucid (Html a) where
553+instance MimeRender HTMLLucid (Html a) where
521−> mimeRender _ = renderBS
554+ mimeRender _ = renderBS
555+```
522
556
523For *blaze-html*:
557For *blaze-html*:
524
558
559+``` haskell
525−> -- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
560+-- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
526−> -- distinct. Usually you would stick to one html rendering library and then
561+-- distinct. Usually you would stick to one html rendering library and then
527−> -- you can go with one 'HTML' type.
562+-- you can go with one 'HTML' type.
528−> data HTMLBlaze
563+data HTMLBlaze
529−>
564+
530−> instance Accept HTMLBlaze where
565+instance Accept HTMLBlaze where
531−> contentType _ = "text" // "html" /: ("charset", "utf-8")
566+ contentType _ = "text" // "html" /: ("charset", "utf-8")
532−>
567+
533−> instance ToMarkup a => MimeRender HTMLBlaze a where
568+instance ToMarkup a => MimeRender HTMLBlaze a where
534−> mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
569+ mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
535−>
570+
536−> -- while we're at it, just like for lucid we can
571+-- while we're at it, just like for lucid we can
537−> -- provide an instance for rendering blaze's 'Html' type
572+-- provide an instance for rendering blaze's 'Html' type
538−> instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
573+instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
539−> mimeRender _ = renderHtml
574+ mimeRender _ = renderHtml
575+```
540
576
541Both [servant-blaze](http://hackage.haskell.org/package/servant-blaze) and
577Both [servant-blaze](http://hackage.haskell.org/package/servant-blaze) and
542[servant-lucid](http://hackage.haskell.org/package/servant-lucid) let you use
578[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.
548
584
549We will be serving the following API:
585We will be serving the following API:
550
586
587+``` haskell
551−> type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
588+type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
589+```
552
590
553where `Person` is defined as follows:
591where `Person` is defined as follows:
554
592
593+``` haskell
555−> data Person = Person
594+data Person = Person
556−> { firstName :: String
595+ { firstName :: String
557−> , lastName :: String
596+ , lastName :: String
558−> } deriving Generic -- for the JSON instance
597+ } deriving Generic -- for the JSON instance
559−>
598+
560−> instance ToJSON Person
599+instance ToJSON Person
600+```
561
601
562Now, let's teach *lucid* how to render a `Person` as a row in a table, and then
602Now, let's teach *lucid* how to render a `Person` as a row in a table, and then
563a list of `Person`s as a table with a row per person.
603a list of `Person`s as a table with a row per person.
564
604
605+``` haskell
565−> -- HTML serialization of a single person
606+-- HTML serialization of a single person
566−> instance ToHtml Person where
607+instance ToHtml Person where
567−> toHtml person =
608+ toHtml person =
568−> tr_ $ do
609+ tr_ $ do
569−> td_ (toHtml $ firstName person)
610+ td_ (toHtml $ firstName person)
570−> td_ (toHtml $ lastName person)
611+ td_ (toHtml $ lastName person)
571−>
612+
572−> -- do not worry too much about this
613+ -- do not worry too much about this
573−> toHtmlRaw = toHtml
614+ toHtmlRaw = toHtml
574−>
615+
575−> -- HTML serialization of a list of persons
616+-- HTML serialization of a list of persons
576−> instance ToHtml [Person] where
617+instance ToHtml [Person] where
577−> toHtml persons = table_ $ do
618+ toHtml persons = table_ $ do
578−> tr_ $ do
619+ tr_ $ do
579−> th_ "first name"
620+ th_ "first name"
580−> th_ "last name"
621+ th_ "last name"
581−>
622+
582−> -- this just calls toHtml on each person of the list
623+ -- this just calls toHtml on each person of the list
583−> -- and concatenates the resulting pieces of HTML together
624+ -- and concatenates the resulting pieces of HTML together
584−> foldMap toHtml persons
625+ foldMap toHtml persons
585−>
626+
586−> toHtmlRaw = toHtml
627+ toHtmlRaw = toHtml
628+```
587
629
588We create some `Person` values and serve them as a list:
630We create some `Person` values and serve them as a list:
589
631
632+``` haskell
590−> persons :: [Person]
633+persons :: [Person]
591−> persons =
634+persons =
592−> [ Person "Isaac" "Newton"
635+ [ Person "Isaac" "Newton"
593−> , Person "Albert" "Einstein"
636+ , Person "Albert" "Einstein"
594−> ]
637+ ]
595−>
638+
596−> personAPI :: Proxy PersonAPI
639+personAPI :: Proxy PersonAPI
597−> personAPI = Proxy
640+personAPI = Proxy
598−>
641+
599−> server4 :: Server PersonAPI
642+server4 :: Server PersonAPI
600−> server4 = return persons
643+server4 = return persons
601−>
644+
602−> app2 :: Application
645+app2 :: Application
603−> app2 = serve personAPI server4
646+app2 = serve personAPI server4
647+```
604
648
605And we're good to go. You can run this example with `dist/build/tutorial/tutorial 4`.
649And we're good to go. You can run this example with `dist/build/tutorial/tutorial 4`.
606
650
@@ -666,18 +710,20 @@ class Monad m => MonadIO m where
666
710
667Obviously, 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`:
711Obviously, 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`:
668
712
713+``` haskell
669−> type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
714+type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
670−>
715+
671−> newtype FileContent = FileContent
716+newtype FileContent = FileContent
672−> { content :: String }
717+ { content :: String }
673−> deriving Generic
718+ deriving Generic
674−>
719+
675−> instance ToJSON FileContent
720+instance ToJSON FileContent
676−>
721+
677−> server5 :: Server IOAPI1
722+server5 :: Server IOAPI1
678−> server5 = do
723+server5 = do
679−> filecontent <- liftIO (readFile "myfile.txt")
724+ filecontent <- liftIO (readFile "myfile.txt")
680−> return (FileContent filecontent)
725+ return (FileContent filecontent)
726+```
681
727
682Failing, through `ServantErr`
728Failing, through `ServantErr`
683-----------------------------
729-----------------------------
@@ -701,23 +747,27 @@ Many standard values are provided out of the box by the `Servant.Server`
701module. If you want to use these values but add a body or some headers, just
747module. If you want to use these values but add a body or some headers, just
702use record update syntax:
748use record update syntax:
703
749
750+``` haskell
704−> failingHandler :: EitherT ServantErr IO ()
751+failingHandler :: EitherT ServantErr IO ()
705−> failingHandler = left myerr
752+failingHandler = left myerr
706−>
753+
707−> where myerr :: ServantErr
754+ where myerr :: ServantErr
708−> myerr = err503 { errBody = "Sorry dear user." }
755+ myerr = err503 { errBody = "Sorry dear user." }
756+```
709
757
710Here's an example where we return a customised 404-Not-Found error message in
758Here's an example where we return a customised 404-Not-Found error message in
711the response body if "myfile.txt" isn't there:
759the response body if "myfile.txt" isn't there:
712
760
761+``` haskell
713−> server6 :: Server IOAPI1
762+server6 :: Server IOAPI1
714−> server6 = do
763+server6 = do
715−> exists <- liftIO (doesFileExist "myfile.txt")
764+ exists <- liftIO (doesFileExist "myfile.txt")
716−> if exists
765+ if exists
717−> then liftIO (readFile "myfile.txt") >>= return . FileContent
766+ then liftIO (readFile "myfile.txt") >>= return . FileContent
718−> else left custom404Err
767+ else left custom404Err
719−>
768+
720−> where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
769+ where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
770+```
721
771
722Let's run this server (`dist/build/tutorial/tutorial 5`) and
772Let's run this server (`dist/build/tutorial/tutorial 5`) and
723query it, first without the file and then with the file.
773query it, first without the file and then with the file.
@@ -758,10 +808,12 @@ Response headers
758To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
808To add headers to your response, use [addHeader](http://hackage.haskell.org/package/servant-0.4.4/docs/Servant-API-ResponseHeaders.html).
759Note that this changes the type of your API, as we can see in the following example:
809Note that this changes the type of your API, as we can see in the following example:
760
810
811+``` haskell
761−> type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
812+type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
762−>
813+
763−> myHandler :: Server MyHandler
814+myHandler :: Server MyHandler
764−> myHandler = return $ addHeader 1797 albert
815+myHandler = return $ addHeader 1797 albert
816+```
765
817
766
818
767Serving static files
819Serving static files
@@ -786,18 +838,24 @@ getting-started.
786
838
787The API type will be the following.
839The API type will be the following.
788
840
841+``` haskell
789−> type CodeAPI = "code" :> Raw
842+type CodeAPI = "code" :> Raw
843+```
790
844
791And the server:
845And the server:
792
846
847+``` haskell
793−> codeAPI :: Proxy CodeAPI
848+codeAPI :: Proxy CodeAPI
794−> codeAPI = Proxy
849+codeAPI = Proxy
850+```
851+
852+``` haskell
853+server7 :: Server CodeAPI
854+server7 = serveDirectory "tutorial"
795
855
796−> server7 :: Server CodeAPI
797−> server7 = serveDirectory "tutorial"
798−>
799−> app3 :: Application
856+app3 :: Application
800−> app3 = serve codeAPI server7
857+app3 = serve codeAPI server7
858+```
801
859
802This 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.
860This 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.
803
861
@@ -903,18 +961,22 @@ Nested APIs
903
961
904Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
962Let's see how you can define APIs in a modular way, while avoiding repetition. Consider this simple example:
905
963
964+``` haskell
906−> type UserAPI3 = -- view the user with given userid, in JSON
965+type UserAPI3 = -- view the user with given userid, in JSON
907−> Capture "userid" Int :> Get '[JSON] User
966+ Capture "userid" Int :> Get '[JSON] User
908−>
967+
909−> :<|> -- delete the user with given userid. empty response
968+ :<|> -- delete the user with given userid. empty response
910−> Capture "userid" Int :> Delete '[] ()
969+ Capture "userid" Int :> Delete '[] ()
970+```
911
971
912We can instead factor out the `userid`:
972We can instead factor out the `userid`:
913
973
974+``` haskell
914−> type UserAPI4 = Capture "userid" Int :>
975+type UserAPI4 = Capture "userid" Int :>
915−> ( Get '[JSON] User
976+ ( Get '[JSON] User
916−> :<|> Delete '[] ()
977+ :<|> Delete '[] ()
917−> )
978+ )
979+```
918
980
919However, you have to be aware that this has an effect on the type of the corresponding `Server`:
981However, you have to be aware that this has an effect on the type of the corresponding `Server`:
920
982
@@ -930,146 +992,158 @@ Server UserAPI4 = Int -> ( EitherT ServantErr IO User
930In the first case, each handler receives the *userid* argument. In the latter,
992In the first case, each handler receives the *userid* argument. In the latter,
931the whole `Server` takes the *userid* and has handlers that are just computations in `EitherT`, with no arguments. In other words:
993the whole `Server` takes the *userid* and has handlers that are just computations in `EitherT`, with no arguments. In other words:
932
994
995+``` haskell
933−> server8 :: Server UserAPI3
996+server8 :: Server UserAPI3
934−> server8 = getUser :<|> deleteUser
997+server8 = getUser :<|> deleteUser
935−>
998+
936−> where getUser :: Int -> EitherT ServantErr IO User
999+ where getUser :: Int -> EitherT ServantErr IO User
937−> getUser _userid = error "..."
1000+ getUser _userid = error "..."
938−>
1001+
939−> deleteUser :: Int -> EitherT ServantErr IO ()
1002+ deleteUser :: Int -> EitherT ServantErr IO ()
940−> deleteUser _userid = error "..."
1003+ deleteUser _userid = error "..."
941−>
1004+
942−> -- notice how getUser and deleteUser
1005+-- notice how getUser and deleteUser
943−> -- have a different type! no argument anymore,
1006+-- have a different type! no argument anymore,
944−> -- the argument directly goes to the whole Server
1007+-- the argument directly goes to the whole Server
945−> server9 :: Server UserAPI4
1008+server9 :: Server UserAPI4
946−> server9 userid = getUser userid :<|> deleteUser userid
1009+server9 userid = getUser userid :<|> deleteUser userid
947−>
1010+
948−> where getUser :: Int -> EitherT ServantErr IO User
1011+ where getUser :: Int -> EitherT ServantErr IO User
949−> getUser = error "..."
1012+ getUser = error "..."
950−>
1013+
951−> deleteUser :: Int -> EitherT ServantErr IO ()
1014+ deleteUser :: Int -> EitherT ServantErr IO ()
952−> deleteUser = error "..."
1015+ deleteUser = error "..."
1016+```
953
1017
954Note 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`.
1018Note 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`.
955
1019
1020+``` haskell
956−> -- we just factor out the "users" path fragment
1021+-- we just factor out the "users" path fragment
957−> type API1 = "users" :>
1022+type API1 = "users" :>
958−> ( Get '[JSON] [User] -- user listing
1023+ ( Get '[JSON] [User] -- user listing
959−> :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
1024+ :<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
960−> )
1025+ )
961−>
1026+
962−> -- we factor out the Request Body
1027+-- we factor out the Request Body
963−> type API2 = ReqBody '[JSON] User :>
1028+type API2 = ReqBody '[JSON] User :>
964−> ( Get '[JSON] User -- just display the same user back, don't register it
1029+ ( Get '[JSON] User -- just display the same user back, don't register it
965−> :<|> Post '[JSON] () -- register the user. empty response
1030+ :<|> Post '[JSON] () -- register the user. empty response
966−> )
1031+ )
967−>
1032+
968−> -- we factor out a Header
1033+-- we factor out a Header
969−> type API3 = Header "Authorization" Token :>
1034+type API3 = Header "Authorization" Token :>
970−> ( Get '[JSON] SecretData -- get some secret data, if authorized
1035+ ( Get '[JSON] SecretData -- get some secret data, if authorized
971−> :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
1036+ :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
972−> )
1037+ )
973−>
1038+
974−> newtype Token = Token ByteString
1039+newtype Token = Token ByteString
975−> newtype SecretData = SecretData ByteString
1040+newtype SecretData = SecretData ByteString
1041+```
976
1042
977This approach lets you define APIs modularly and assemble them all into one big API type only at the end.
1043This approach lets you define APIs modularly and assemble them all into one big API type only at the end.
978
1044
1045+``` haskell
979−> type UsersAPI =
1046+type UsersAPI =
980−> Get '[JSON] [User] -- list users
1047+ Get '[JSON] [User] -- list users
981−> :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
1048+ :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
982−> :<|> Capture "userid" Int :>
1049+ :<|> Capture "userid" Int :>
983−> ( Get '[JSON] User -- view a user
1050+ ( Get '[JSON] User -- view a user
984−> :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
1051+ :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
985−> :<|> Delete '[] () -- delete a user
1052+ :<|> Delete '[] () -- delete a user
986−> )
1053+ )
987−>
1054+
988−> usersServer :: Server UsersAPI
1055+usersServer :: Server UsersAPI
989−> usersServer = getUsers :<|> newUser :<|> userOperations
1056+usersServer = getUsers :<|> newUser :<|> userOperations
990−>
1057+
991−> where getUsers :: EitherT ServantErr IO [User]
1058+ where getUsers :: EitherT ServantErr IO [User]
992−> getUsers = error "..."
1059+ getUsers = error "..."
993−>
1060+
994−> newUser :: User -> EitherT ServantErr IO ()
1061+ newUser :: User -> EitherT ServantErr IO ()
995−> newUser = error "..."
1062+ newUser = error "..."
996−>
1063+
997−> userOperations userid =
1064+ userOperations userid =
998−> viewUser userid :<|> updateUser userid :<|> deleteUser userid
1065+ viewUser userid :<|> updateUser userid :<|> deleteUser userid
999−>
1066+
1000−> where
1067+ where
1001−> viewUser :: Int -> EitherT ServantErr IO User
1068+ viewUser :: Int -> EitherT ServantErr IO User
1002−> viewUser = error "..."
1069+ viewUser = error "..."
1003−>
1070+
1004−> updateUser :: Int -> User -> EitherT ServantErr IO ()
1071+ updateUser :: Int -> User -> EitherT ServantErr IO ()
1005−> updateUser = error "..."
1072+ updateUser = error "..."
1006−>
1073+
1007−> deleteUser :: Int -> EitherT ServantErr IO ()
1074+ deleteUser :: Int -> EitherT ServantErr IO ()
1008−> deleteUser = error "..."
1075+ deleteUser = error "..."
1009−
1076+```
1077+
1078+``` haskell
1010−> type ProductsAPI =
1079+type ProductsAPI =
1011−> Get '[JSON] [Product] -- list products
1080+ Get '[JSON] [Product] -- list products
1012−> :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
1081+ :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
1013−> :<|> Capture "productid" Int :>
1082+ :<|> Capture "productid" Int :>
1014−> ( Get '[JSON] Product -- view a product
1083+ ( Get '[JSON] Product -- view a product
1015−> :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
1084+ :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
1016−> :<|> Delete '[] () -- delete a product
1085+ :<|> Delete '[] () -- delete a product
1017−> )
1086+ )
1018−>
1087+
1019−> data Product = Product { productId :: Int }
1088+data Product = Product { productId :: Int }
1020−>
1089+
1021−> productsServer :: Server ProductsAPI
1090+productsServer :: Server ProductsAPI
1022−> productsServer = getProducts :<|> newProduct :<|> productOperations
1091+productsServer = getProducts :<|> newProduct :<|> productOperations
1023−>
1092+
1024−> where getProducts :: EitherT ServantErr IO [Product]
1093+ where getProducts :: EitherT ServantErr IO [Product]
1025−> getProducts = error "..."
1094+ getProducts = error "..."
1026−>
1095+
1027−> newProduct :: Product -> EitherT ServantErr IO ()
1096+ newProduct :: Product -> EitherT ServantErr IO ()
1028−> newProduct = error "..."
1097+ newProduct = error "..."
1029−>
1098+
1030−> productOperations productid =
1099+ productOperations productid =
1031−> viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1100+ viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1032−>
1101+
1033−> where
1102+ where
1034−> viewProduct :: Int -> EitherT ServantErr IO Product
1103+ viewProduct :: Int -> EitherT ServantErr IO Product
1035−> viewProduct = error "..."
1104+ viewProduct = error "..."
1036−>
1105+
1037−> updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1106+ updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1038−> updateProduct = error "..."
1107+ updateProduct = error "..."
1039−>
1108+
1040−> deleteProduct :: Int -> EitherT ServantErr IO ()
1109+ deleteProduct :: Int -> EitherT ServantErr IO ()
1041−> deleteProduct = error "..."
1110+ deleteProduct = error "..."
1042−
1111+```
1112+
1113+``` haskell
1043−> type CombinedAPI = "users" :> UsersAPI
1114+type CombinedAPI = "users" :> UsersAPI
1044−> :<|> "products" :> ProductsAPI
1115+ :<|> "products" :> ProductsAPI
1045−>
1116+
1046−> server10 :: Server CombinedAPI
1117+server10 :: Server CombinedAPI
1047−> server10 = usersServer :<|> productsServer
1118+server10 = usersServer :<|> productsServer
1119+```
1048
1120
1049Finally, we can realize the user and product APIs are quite similar and abstract that away:
1121Finally, we can realize the user and product APIs are quite similar and abstract that away:
1050
1122
1123+``` haskell
1051−> -- API for values of type 'a'
1124+-- API for values of type 'a'
1052−> -- indexed by values of type 'i'
1125+-- indexed by values of type 'i'
1053−> type APIFor a i =
1126+type APIFor a i =
1054−> Get '[JSON] [a] -- list 'a's
1127+ Get '[JSON] [a] -- list 'a's
1055−> :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
1128+ :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
1056−> :<|> Capture "id" i :>
1129+ :<|> Capture "id" i :>
1057−> ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1130+ ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1058−> :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
1131+ :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
1059−> :<|> Delete '[] () -- delete an 'a'
1132+ :<|> Delete '[] () -- delete an 'a'
1060−> )
1133+ )
1061−>
1134+
1062−> -- Build the appropriate 'Server'
1135+-- Build the appropriate 'Server'
1063−> -- given the handlers of the right type.
1136+-- given the handlers of the right type.
1064−> serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1137+serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1065−> -> (a -> EitherT ServantErr IO ()) -- handler for adding an 'a'
1138+ -> (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'
1139+ -> (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
1140+ -> (i -> a -> EitherT ServantErr IO ()) -- updating an 'a' with given id
1068−> -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1141+ -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1069−> -> Server (APIFor a i)
1142+ -> Server (APIFor a i)
1070−> serverFor = error "..."
1143+serverFor = error "..."
1071−> -- implementation left as an exercise. contact us on IRC
1144+-- implementation left as an exercise. contact us on IRC
1072−> -- or the mailing list if you get stuck!
1145+-- or the mailing list if you get stuck!
1146+```
1073
1147
1074Using another monad for your handlers
1148Using another monad for your handlers
1075=====================================
1149=====================================
@@ -1112,28 +1186,32 @@ computation by supplying it with a `String`, like `"hi"`. We get an `a` out
1112from that and can then just `return` it into `EitherT`. We can then just wrap
1186from that and can then just `return` it into `EitherT`. We can then just wrap
1113that function with the `Nat` constructor to make it have the fancier type.
1187that function with the `Nat` constructor to make it have the fancier type.
1114
1188
1189+``` haskell
1115−> readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1190+readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1116−> readerToEither' r = return (runReader r "hi")
1191+readerToEither' r = return (runReader r "hi")
1117−>
1192+
1118−> readerToEither :: Reader String :~> EitherT ServantErr IO
1193+readerToEither :: Reader String :~> EitherT ServantErr IO
1119−> readerToEither = Nat readerToEither'
1194+readerToEither = Nat readerToEither'
1195+```
1120
1196
1121We can write some simple webservice with the handlers running in `Reader String`.
1197We can write some simple webservice with the handlers running in `Reader String`.
1122
1198
1199+``` haskell
1123−> type ReaderAPI = "a" :> Get '[JSON] Int
1200+type ReaderAPI = "a" :> Get '[JSON] Int
1124−> :<|> "b" :> Get '[JSON] String
1201+ :<|> "b" :> Get '[JSON] String
1125−>
1202+
1126−> readerAPI :: Proxy ReaderAPI
1203+readerAPI :: Proxy ReaderAPI
1127−> readerAPI = Proxy
1204+readerAPI = Proxy
1128−>
1205+
1129−> readerServerT :: ServerT ReaderAPI (Reader String)
1206+readerServerT :: ServerT ReaderAPI (Reader String)
1130−> readerServerT = a :<|> b
1207+readerServerT = a :<|> b
1131−>
1208+
1132−> where a :: Reader String Int
1209+ where a :: Reader String Int
1133−> a = return 1797
1210+ a = return 1797
1134−>
1211+
1135−> b :: Reader String String
1212+ b :: Reader String String
1136−> b = ask
1213+ b = ask
1214+```
1137
1215
1138We unfortunately can't use `readerServerT` as an argument of `serve`, because
1216We unfortunately can't use `readerServerT` as an argument of `serve`, because
1139`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `EitherT
1217`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`.
1150
1228
1151In our case, we can wrap up our little webservice by using `enter readerToEither` on our handlers.
1229In our case, we can wrap up our little webservice by using `enter readerToEither` on our handlers.
1152
1230
1231+``` haskell
1153−> readerServer :: Server ReaderAPI
1232+readerServer :: Server ReaderAPI
1154−> readerServer = enter readerToEither readerServerT
1233+readerServer = enter readerToEither readerServerT
1155−>
1234+
1156−> app4 :: Application
1235+app4 :: Application
1157−> app4 = serve readerAPI readerServer
1236+app4 = serve readerAPI readerServer
1237+```
1158
1238
1159And we can indeed see this webservice in action by running `dist/build/tutorial/tutorial 7`.
1239And we can indeed see this webservice in action by running `dist/build/tutorial/tutorial 7`.
1160
1240