@@ -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:
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 #-}
46−> module Server where
47+module Server where
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``` haskell ignore
73``` haskell ignore
72{-# LANGUAGE TypeFamilies #-}
74{-# LANGUAGE TypeFamilies #-}
@@ -76,7 +78,9 @@ need to have some language extensions and imports:
77We will write a server that will serve the following API.
79We will write a server that will serve the following API.
79−> type UserAPI1 = "users" :> Get '[JSON] [User]
82+type UserAPI1 = "users" :> Get '[JSON] [User]
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`.
@@ -88,22 +92,26 @@ Here's what we would like to see when making a GET request to `/users`.
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.
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)
98−> instance ToJSON User
103+instance ToJSON User
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.
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)
108Let's also write our API type.
116Let's also write our API type.
@@ -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:
132−> server1 :: Server UserAPI1
141+server1 :: Server UserAPI1
133−> server1 = return users1
142+server1 = return users1
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):
137−> userAPI :: Proxy UserAPI1
148+userAPI :: Proxy UserAPI1
138−> userAPI = Proxy
149+userAPI = Proxy
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
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.
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.
151−> main :: IO ()
164+main :: IO ()
152−> main = run 8081 app1
165+main = run 8081 app1
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
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.
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
177And let's adapt our code a bit.
193And let's adapt our code a bit.
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)
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)
185−> users2 :: [User]
202+users2 :: [User]
186−> users2 = [isaac, albert]
203+users2 = [isaac, albert]
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.
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
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.
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`.
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
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
223−> instance ToJSON Position
244+instance ToJSON Position
225−> newtype HelloMessage = HelloMessage { msg :: String }
246+newtype HelloMessage = HelloMessage { msg :: String }
226−> deriving Generic
247+ deriving Generic
228−> instance ToJSON HelloMessage
249+instance ToJSON HelloMessage
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
237−> instance FromJSON ClientInfo
258+instance FromJSON ClientInfo
238−> instance ToJSON ClientInfo
259+instance ToJSON ClientInfo
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
247−> instance ToJSON Email
268+instance ToJSON Email
249−> emailForClient :: ClientInfo -> Email
270+emailForClient :: ClientInfo -> Email
250−> emailForClient c = Email from' to' subject' body'
271+emailForClient c = Email from' to' subject' body'
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!"
261We can implement handlers for the three endpoints:
283We can implement handlers for the three endpoints:
263−> server3 :: Server API
286+server3 :: Server API
264−> server3 = position
287+server3 = position
265−> :<|> hello
288+ :<|> hello
266−> :<|> marketing
289+ :<|> marketing
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)
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
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)
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
340−> -- A typical enumeration
365+-- A typical enumeration
341−> data Direction
366+data Direction
345−> | Right
370+ | Right
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
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"
361−> newtype UserId = UserId Int64
386+newtype UserId = UserId Int64
362−> deriving (FromText, ToText)
387+ deriving (FromText, ToText)
364or writing the instances by hand:
390or writing the instances by hand:
@@ -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.
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
472This function is exactly what we need for our `MimeUnrender` instance.
500This function is exactly what we need for our `MimeUnrender` instance.
@@ -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!).
495−> data HTMLLucid
524+data HTMLLucid
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
501Both packages also have the same `Accept` instance for their `HTMLLucid` type.
531Both packages also have the same `Accept` instance for their `HTMLLucid` type.
503−> instance Accept HTMLLucid where
534+instance Accept HTMLLucid where
504−> contentType _ = "text" // "html" /: ("charset", "utf-8")
535+ contentType _ = "text" // "html" /: ("charset", "utf-8")
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`.
513For *lucid*:
545For *lucid*:
515−> instance ToHtml a => MimeRender HTMLLucid a where
548+instance ToHtml a => MimeRender HTMLLucid a where
516−> mimeRender _ = renderBS . toHtml
549+ mimeRender _ = renderBS . toHtml
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
523For *blaze-html*:
557For *blaze-html*:
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
530−> instance Accept HTMLBlaze where
565+instance Accept HTMLBlaze where
531−> contentType _ = "text" // "html" /: ("charset", "utf-8")
566+ contentType _ = "text" // "html" /: ("charset", "utf-8")
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
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
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.
549We will be serving the following API:
585We will be serving the following API:
551−> type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
588+type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
553where `Person` is defined as follows:
591where `Person` is defined as follows:
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
560−> instance ToJSON Person
599+instance ToJSON Person
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.
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)
572−> -- do not worry too much about this
613+ -- do not worry too much about this
573−> toHtmlRaw = toHtml
614+ toHtmlRaw = toHtml
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"
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
586−> toHtmlRaw = toHtml
627+ toHtmlRaw = toHtml
588We create some `Person` values and serve them as a list:
630We create some `Person` values and serve them as a list:
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"
596−> personAPI :: Proxy PersonAPI
639+personAPI :: Proxy PersonAPI
597−> personAPI = Proxy
640+personAPI = Proxy
599−> server4 :: Server PersonAPI
642+server4 :: Server PersonAPI
600−> server4 = return persons
643+server4 = return persons
602−> app2 :: Application
645+app2 :: Application
603−> app2 = serve personAPI server4
646+app2 = serve personAPI server4
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`.
@@ -666,18 +710,20 @@ class Monad m => MonadIO m where
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`:
669−> type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
714+type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
671−> newtype FileContent = FileContent
716+newtype FileContent = FileContent
672−> { content :: String }
717+ { content :: String }
673−> deriving Generic
718+ deriving Generic
675−> instance ToJSON FileContent
720+instance ToJSON FileContent
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)
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:
704−> failingHandler :: EitherT ServantErr IO ()
751+failingHandler :: EitherT ServantErr IO ()
705−> failingHandler = left myerr
752+failingHandler = left myerr
707−> where myerr :: ServantErr
754+ where myerr :: ServantErr
708−> myerr = err503 { errBody = "Sorry dear user." }
755+ myerr = err503 { errBody = "Sorry dear user." }
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:
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
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." }
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:
761−> type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
812+type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
763−> myHandler :: Server MyHandler
814+myHandler :: Server MyHandler
764−> myHandler = return $ addHeader 1797 albert
815+myHandler = return $ addHeader 1797 albert
767Serving static files
819Serving static files
@@ -786,18 +838,24 @@ getting-started.
787The API type will be the following.
839The API type will be the following.
789−> type CodeAPI = "code" :> Raw
842+type CodeAPI = "code" :> Raw
791And the server:
845And the server:
793−> codeAPI :: Proxy CodeAPI
848+codeAPI :: Proxy CodeAPI
794−> codeAPI = Proxy
849+codeAPI = Proxy
853+server7 :: Server CodeAPI
854+server7 = serveDirectory "tutorial"
796−> server7 :: Server CodeAPI
797−> server7 = serveDirectory "tutorial"
799−> app3 :: Application
856+app3 :: Application
800−> app3 = serve codeAPI server7
857+app3 = serve codeAPI server7
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.
@@ -903,18 +961,22 @@ Nested APIs
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:
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
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 '[] ()
912We can instead factor out the `userid`:
972We can instead factor out the `userid`:
914−> type UserAPI4 = Capture "userid" Int :>
975+type UserAPI4 = Capture "userid" Int :>
915−> ( Get '[JSON] User
976+ ( Get '[JSON] User
916−> :<|> Delete '[] ()
977+ :<|> Delete '[] ()
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`:
@@ -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:
933−> server8 :: Server UserAPI3
996+server8 :: Server UserAPI3
934−> server8 = getUser :<|> deleteUser
997+server8 = getUser :<|> deleteUser
936−> where getUser :: Int -> EitherT ServantErr IO User
999+ where getUser :: Int -> EitherT ServantErr IO User
937−> getUser _userid = error "..."
1000+ getUser _userid = error "..."
939−> deleteUser :: Int -> EitherT ServantErr IO ()
1002+ deleteUser :: Int -> EitherT ServantErr IO ()
940−> deleteUser _userid = error "..."
1003+ deleteUser _userid = error "..."
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
948−> where getUser :: Int -> EitherT ServantErr IO User
1011+ where getUser :: Int -> EitherT ServantErr IO User
949−> getUser = error "..."
1012+ getUser = error "..."
951−> deleteUser :: Int -> EitherT ServantErr IO ()
1014+ deleteUser :: Int -> EitherT ServantErr IO ()
952−> deleteUser = error "..."
1015+ deleteUser = error "..."
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`.
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
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
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
974−> newtype Token = Token ByteString
1039+newtype Token = Token ByteString
975−> newtype SecretData = SecretData ByteString
1040+newtype SecretData = SecretData ByteString
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.
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
988−> usersServer :: Server UsersAPI
1055+usersServer :: Server UsersAPI
989−> usersServer = getUsers :<|> newUser :<|> userOperations
1056+usersServer = getUsers :<|> newUser :<|> userOperations
991−> where getUsers :: EitherT ServantErr IO [User]
1058+ where getUsers :: EitherT ServantErr IO [User]
992−> getUsers = error "..."
1059+ getUsers = error "..."
994−> newUser :: User -> EitherT ServantErr IO ()
1061+ newUser :: User -> EitherT ServantErr IO ()
995−> newUser = error "..."
1062+ newUser = error "..."
997−> userOperations userid =
1064+ userOperations userid =
998−> viewUser userid :<|> updateUser userid :<|> deleteUser userid
1065+ viewUser userid :<|> updateUser userid :<|> deleteUser userid
1001−> viewUser :: Int -> EitherT ServantErr IO User
1068+ viewUser :: Int -> EitherT ServantErr IO User
1002−> viewUser = error "..."
1069+ viewUser = error "..."
1004−> updateUser :: Int -> User -> EitherT ServantErr IO ()
1071+ updateUser :: Int -> User -> EitherT ServantErr IO ()
1005−> updateUser = error "..."
1072+ updateUser = error "..."
1007−> deleteUser :: Int -> EitherT ServantErr IO ()
1074+ deleteUser :: Int -> EitherT ServantErr IO ()
1008−> deleteUser = error "..."
1075+ deleteUser = error "..."
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
1019−> data Product = Product { productId :: Int }
1088+data Product = Product { productId :: Int }
1021−> productsServer :: Server ProductsAPI
1090+productsServer :: Server ProductsAPI
1022−> productsServer = getProducts :<|> newProduct :<|> productOperations
1091+productsServer = getProducts :<|> newProduct :<|> productOperations
1024−> where getProducts :: EitherT ServantErr IO [Product]
1093+ where getProducts :: EitherT ServantErr IO [Product]
1025−> getProducts = error "..."
1094+ getProducts = error "..."
1027−> newProduct :: Product -> EitherT ServantErr IO ()
1096+ newProduct :: Product -> EitherT ServantErr IO ()
1028−> newProduct = error "..."
1097+ newProduct = error "..."
1030−> productOperations productid =
1099+ productOperations productid =
1031−> viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1100+ viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1034−> viewProduct :: Int -> EitherT ServantErr IO Product
1103+ viewProduct :: Int -> EitherT ServantErr IO Product
1035−> viewProduct = error "..."
1104+ viewProduct = error "..."
1037−> updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1106+ updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1038−> updateProduct = error "..."
1107+ updateProduct = error "..."
1040−> deleteProduct :: Int -> EitherT ServantErr IO ()
1109+ deleteProduct :: Int -> EitherT ServantErr IO ()
1041−> deleteProduct = error "..."
1110+ deleteProduct = error "..."
1043−> type CombinedAPI = "users" :> UsersAPI
1114+type CombinedAPI = "users" :> UsersAPI
1044−> :<|> "products" :> ProductsAPI
1115+ :<|> "products" :> ProductsAPI
1046−> server10 :: Server CombinedAPI
1117+server10 :: Server CombinedAPI
1047−> server10 = usersServer :<|> productsServer
1118+server10 = usersServer :<|> productsServer
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:
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'
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!
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.
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")
1118−> readerToEither :: Reader String :~> EitherT ServantErr IO
1193+readerToEither :: Reader String :~> EitherT ServantErr IO
1119−> readerToEither = Nat readerToEither'
1194+readerToEither = Nat readerToEither'
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`.
1123−> type ReaderAPI = "a" :> Get '[JSON] Int
1200+type ReaderAPI = "a" :> Get '[JSON] Int
1124−> :<|> "b" :> Get '[JSON] String
1201+ :<|> "b" :> Get '[JSON] String
1126−> readerAPI :: Proxy ReaderAPI
1203+readerAPI :: Proxy ReaderAPI
1127−> readerAPI = Proxy
1204+readerAPI = Proxy
1129−> readerServerT :: ServerT ReaderAPI (Reader String)
1206+readerServerT :: ServerT ReaderAPI (Reader String)
1130−> readerServerT = a :<|> b
1207+readerServerT = a :<|> b
1132−> where a :: Reader String Int
1209+ where a :: Reader String Int
1133−> a = return 1797
1210+ a = return 1797
1135−> b :: Reader String String
1212+ b :: Reader String String
1136−> b = ask
1213+ b = ask
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`.
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.
1153−> readerServer :: Server ReaderAPI
1232+readerServer :: Server ReaderAPI
1154−> readerServer = enter readerToEither readerServerT
1233+readerServer = enter readerToEither readerServerT
1156−> app4 :: Application
1235+app4 :: Application
1157−> app4 = serve readerAPI readerServer
1236+app4 = serve readerAPI readerServer
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`.