@@ -18,7 +18,6 @@ need to have some language extensions and imports:
18{-# LANGUAGE GeneralizedNewtypeDeriving #-}
18{-# LANGUAGE GeneralizedNewtypeDeriving #-}
19{-# LANGUAGE MultiParamTypeClasses #-}
19{-# LANGUAGE MultiParamTypeClasses #-}
20{-# LANGUAGE OverloadedStrings #-}
20{-# LANGUAGE OverloadedStrings #-}
21−{-# LANGUAGE RankNTypes #-}
22{-# LANGUAGE ScopedTypeVariables #-}
21{-# LANGUAGE ScopedTypeVariables #-}
23{-# LANGUAGE TypeOperators #-}
22{-# LANGUAGE TypeOperators #-}
@@ -29,7 +28,8 @@ import Prelude.Compat
30import Control.Monad.Except
29import Control.Monad.Except
31import Control.Monad.Reader
30import Control.Monad.Reader
31+import Control.Monad.Trans.Except
32−import Data.Aeson
32+import Data.Aeson.Compat
33import Data.Aeson.Types
33import Data.Aeson.Types
34import Data.Attoparsec.ByteString
34import Data.Attoparsec.ByteString
35import Data.ByteString (ByteString)
35import Data.ByteString (ByteString)
@@ -46,7 +46,6 @@ import Servant
46import System.Directory
46import System.Directory
47import Text.Blaze
47import Text.Blaze
48import Text.Blaze.Html.Renderer.Utf8
48import Text.Blaze.Html.Renderer.Utf8
49−import Servant.Types.SourceT (source)
50import qualified Data.Aeson.Parser
49import qualified Data.Aeson.Parser
51import qualified Text.Blaze.Html
50import qualified Text.Blaze.Html
@@ -96,6 +95,12 @@ users1 =
98+Let's also write our API type.
101+type UserAPI1 = "users" :> Get '[JSON] [User]
99We can now take care of writing the actual webservice that will handle requests
104We can now take care of writing the actual webservice that will handle requests
100to such an API. This one will be very simple, being reduced to just a single
105to such an API. This one will be very simple, being reduced to just a single
101endpoint. The type of the web application is determined by the API type,
106endpoint. The type of the web application is determined by the API type,
@@ -107,11 +112,11 @@ corresponding API type.
107The first thing to know about the `Server` type family is that behind the
112The first thing to know about the `Server` type family is that behind the
108scenes it will drive the routing, letting you focus only on the business
113scenes it will drive the routing, letting you focus only on the business
109logic. The second thing to know is that for each endpoint, your handlers will
114logic. The second thing to know is that for each endpoint, your handlers will
110−by default run in the `Handler` monad. This is overridable very
115+by default run in the `ExceptT ServantErr IO` monad. This is overridable very
111easily, as explained near the end of this guide. Third thing, the type of the
116easily, as explained near the end of this guide. Third thing, the type of the
112value returned in that monad must be the same as the second argument of the
117value returned in that monad must be the same as the second argument of the
113HTTP method combinator used for the corresponding endpoint. In our case, it
118HTTP method combinator used for the corresponding endpoint. In our case, it
114−means we must provide a handler of type `Handler [User]`. Well,
119+means we must provide a handler of type `ExceptT ServantErr IO [User]`. Well,
115we have a monad, let's just `return` our list:
120we have a monad, let's just `return` our list:
117``` haskell
122``` haskell
@@ -183,7 +188,7 @@ users2 = [isaac, albert]
184Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
189Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
185are going to separate the handlers with `:<|>` too! They must be provided in
190are going to separate the handlers with `:<|>` too! They must be provided in
186−the same order as in the API type.
191+the same order as in in the API type.
188``` haskell
193``` haskell
189server2 :: Server UserAPI2
194server2 :: Server UserAPI2
@@ -199,7 +204,7 @@ And that's it! You can run this example in the same way that we showed for
200Fine, we can write trivial webservices easily, but none of the two above use
205Fine, we can write trivial webservices easily, but none of the two above use
201any "fancy" combinator from servant. Let's address this and use `QueryParam`,
206any "fancy" combinator from servant. Let's address this and use `QueryParam`,
202−`Capture` and `ReqBody` right away. You'll see how each occurrence of these
207+`Capture` and `ReqBody` right away. You'll see how each occurence of these
203combinators in an endpoint makes the corresponding handler receive an
208combinators in an endpoint makes the corresponding handler receive an
204argument of the appropriate type automatically. You don't have to worry about
209argument of the appropriate type automatically. You don't have to worry about
205manually looking up URL captures or query string parameters, or
210manually looking up URL captures or query string parameters, or
@@ -265,15 +270,15 @@ server3 = position
265 :<|> hello
270 :<|> hello
266 :<|> marketing
271 :<|> marketing
268− where position :: Int -> Int -> Handler Position
273+ where position :: Int -> Int -> ExceptT ServantErr IO Position
269 position x y = return (Position x y)
274 position x y = return (Position x y)
271− hello :: Maybe String -> Handler HelloMessage
276+ hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
272 hello mname = return . HelloMessage $ case mname of
277 hello mname = return . HelloMessage $ case mname of
273 Nothing -> "Hello, anonymous coward"
278 Nothing -> "Hello, anonymous coward"
274 Just n -> "Hello, " ++ n
279 Just n -> "Hello, " ++ n
276− marketing :: ClientInfo -> Handler Email
281+ marketing :: ClientInfo -> ExceptT ServantErr IO Email
277 marketing clientinfo = return (emailForClient clientinfo)
282 marketing clientinfo = return (emailForClient clientinfo)
@@ -303,7 +308,7 @@ $ curl -X POST -d '{"clientName":"Alp Mestanogullari", "clientEmail" : "alp@foo.
304For reference, here's a list of some combinators from **servant**:
309For reference, here's a list of some combinators from **servant**:
306− > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `Handler <something>`.
311+ > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `ExceptT ServantErr IO <something>`.
307 > - `Capture "something" a` becomes an argument of type `a`.
312 > - `Capture "something" a` becomes an argument of type `a`.
308 > - `QueryParam "something" a`, `Header "something" a` all become arguments of type `Maybe a`, because there might be no value at all specified by the client for these.
313 > - `QueryParam "something" a`, `Header "something" a` all become arguments of type `Maybe a`, because there might be no value at all specified by the client for these.
309 > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
314 > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
@@ -313,8 +318,8 @@ For reference, here's a list of some combinators from **servant**:
313## The `FromHttpApiData`/`ToHttpApiData` classes
318## The `FromHttpApiData`/`ToHttpApiData` classes
315Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
320Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
316−to decode a `ClientInfo` value from the request body? The following three sections will
321+to decode a `ClientInfo` value from the request body? This is what this and the
317−help us answer these questions.
322+following two sections address.
319`Capture`s and `QueryParam`s are represented by some textual value in URLs.
324`Capture`s and `QueryParam`s are represented by some textual value in URLs.
320`Header`s are similarly represented by a pair of a header name and a
325`Header`s are similarly represented by a pair of a header name and a
@@ -597,9 +602,10 @@ $ curl -H 'Accept: text/html' http://localhost:8081/persons
597# or just point your browser to http://localhost:8081/persons
602# or just point your browser to http://localhost:8081/persons
600−## The `Handler` monad
605+## The `ExceptT ServantErr IO` monad
602−At the heart of the handlers is the monad they run in, namely a newtype `Handler` around `ExceptT ServerError IO`
607+At the heart of the handlers is the monad they run in, namely `ExceptT
603([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
609([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
604One might wonder: why this monad? The answer is that it is the
610One might wonder: why this monad? The answer is that it is the
605simplest monad with the following properties:
611simplest monad with the following properties:
@@ -616,13 +622,13 @@ Let's recall some definitions.
616newtype ExceptT e m a = ExceptT (m (Either e a))
622newtype ExceptT e m a = ExceptT (m (Either e a))
619−In short, this means that a handler of type `Handler a` is simply
625+In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
620−equivalent to a computation of type `IO (Either ServerError a)`, that is, an IO
626+equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
621action that either returns an error or a result.
627action that either returns an error or a result.
623−The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl/docs/Control-Monad-Except.html#t:ExceptT)
629+The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)
624from which `ExceptT` comes is worth looking at.
630from which `ExceptT` comes is worth looking at.
625−Perhaps most importantly, `ExceptT` and `Handler` are instances of `MonadError`, so
631+Perhaps most importantly, `ExceptT` is an instance of `MonadError`, so
626`throwError` can be used to return an error from your handler (whereas `return`
632`throwError` can be used to return an error from your handler (whereas `return`
627 is enough to return a success).
633 is enough to return a success).
@@ -632,9 +638,9 @@ kind and abort early. The next two sections cover how to do just that.
633### Performing IO
639### Performing IO
635−Other important instances from the list above are `MonadIO m => MonadIO
641+Another important instance from the list above is `MonadIO m => MonadIO
636−(ExceptT e m)`, and therefore also `MonadIO Handler` as there is a `MonadIO IO` instance.
642+(ExceptT e m)`.
637−[`MonadIO`](http://hackage.haskell.org/package/base/docs/Control-Monad-IO-Class.html#t:MonadIO)
643+[`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html)
638is a class from the **transformers** package defined as:
644is a class from the **transformers** package defined as:
640``` haskell ignore
646``` haskell ignore
@@ -642,7 +648,8 @@ class Monad m => MonadIO m where
642 liftIO :: IO a -> m a
648 liftIO :: IO a -> m a
651+The `IO` monad provides a `MonadIO` instance. Hence for any type
645−So if you want to run any kind of
652+`e`, `ExceptT e IO` has a `MonadIO` instance. So if you want to run any kind of
646IO computation in your handlers, just use `liftIO`:
653IO computation in your handlers, just use `liftIO`:
648``` haskell
655``` haskell
@@ -660,16 +667,16 @@ server5 = do
660 return (FileContent filecontent)
667 return (FileContent filecontent)
663−### Failing, through `ServerError`
670+### Failing, through `ServantErr`
665If you want to explicitly fail at providing the result promised by an endpoint
672If you want to explicitly fail at providing the result promised by an endpoint
666using the appropriate HTTP status code (not found, unauthorized, etc) and some
673using the appropriate HTTP status code (not found, unauthorized, etc) and some
667error message, all you have to do is use the `throwError` function mentioned above
674error message, all you have to do is use the `throwError` function mentioned above
668−and provide it with the appropriate value of type `ServerError`, which is
675+and provide it with the appropriate value of type `ServantErr`, which is
669defined as:
676defined as:
671``` haskell ignore
678``` haskell ignore
672−data ServerError = ServerError
679+data ServantErr = ServantErr
673 { errHTTPCode :: Int
680 { errHTTPCode :: Int
674 , errReasonPhrase :: String
681 , errReasonPhrase :: String
675 , errBody :: ByteString -- lazy bytestring
682 , errBody :: ByteString -- lazy bytestring
@@ -682,10 +689,10 @@ module. If you want to use these values but add a body or some headers, just
682use record update syntax:
689use record update syntax:
684``` haskell
691``` haskell
685−failingHandler :: Handler ()
692+failingHandler :: ExceptT ServantErr IO ()
686failingHandler = throwError myerr
693failingHandler = throwError myerr
688− where myerr :: ServerError
695+ where myerr :: ServantErr
689 myerr = err503 { errBody = "Sorry dear user." }
696 myerr = err503 { errBody = "Sorry dear user." }
@@ -716,7 +723,7 @@ $ curl --verbose http://localhost:8081/myfile.txt
717< HTTP/1.1 404 Not Found
724< HTTP/1.1 404 Not Found
719−myfile.txt just isn't there, please leave this server alone.
726+myfile.txt just isnt there, please leave this server alone.
721$ echo Hello > myfile.txt
728$ echo Hello > myfile.txt
@@ -748,29 +755,7 @@ myHandler :: Server MyHandler
748myHandler = return $ addHeader 1797 albert
755myHandler = return $ addHeader 1797 albert
751−Note that the type of `addHeader header x` is different than the type of `x`!
758+Note that the type of `addHeader x` is different than the type of `x`!
752−And if you add more headers, more headers will appear in the header list:
755−type MyHeadfulHandler = Get '[JSON] (Headers '[Header "X-A-Bool" Bool, Header "X-An-Int" Int] User)
757−myHeadfulHandler :: Server MyHeadfulHandler
758−myHeadfulHandler = return $ addHeader True $ addHeader 1797 albert
761−But what if your handler only *sometimes* adds a header? If you declare that
762−your handler adds headers, and you don't add one, the return type of your
763−handler will be different than expected. To solve this, you have to explicitly
764−*not* add a header by using `noHeader`:
767−type MyMaybeHeaderHandler
768− = Capture "withHeader" Bool :> Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
770−myMaybeHeaderHandler :: Server MyMaybeHeaderHandler
771−myMaybeHeaderHandler x = return $ if x then addHeader 1797 albert
772− else noHeader albert
775## Serving static files
760## Serving static files
@@ -782,10 +767,10 @@ directory serving WAI application, namely:
783``` haskell ignore
768``` haskell ignore
784-- exported by Servant and Servant.Server
769-- exported by Servant and Servant.Server
785−serveDirectoryWebApp :: FilePath -> Server Raw
770+serveDirectory :: FilePath -> Server Raw
788−`serveDirectoryWebApp`'s argument must be a path to a valid directory.
773+`serveDirectory`'s argument must be a path to a valid directory.
790Here's an example API that will serve some static files:
775Here's an example API that will serve some static files:
@@ -802,7 +787,7 @@ staticAPI = Proxy
803``` haskell
788``` haskell
804server7 :: Server StaticAPI
789server7 :: Server StaticAPI
805−server7 = serveDirectoryWebApp "static-files"
790+server7 = serveDirectory "static-files"
807app3 :: Application
792app3 :: Application
808app3 = serve staticAPI server7
793app3 = serve staticAPI server7
@@ -816,10 +801,6 @@ In other words: If a client requests `/static/foo.txt`, the server will look for
816`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
801`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
817If it doesn't exist, the handler will fail with a `404` status code.
802If it doesn't exist, the handler will fail with a `404` status code.
819−`serveDirectoryWebApp` uses some standard settings that fit the use case of
820−serving static files for most web apps. You can find out about the other
821−options in the documentation of the `Servant.Server.StaticFiles` module.
823## Nested APIs
804## Nested APIs
825Let's see how you can define APIs in a modular way, while avoiding repetition.
806Let's see how you can define APIs in a modular way, while avoiding repetition.
@@ -830,7 +811,7 @@ type UserAPI3 = -- view the user with given userid, in JSON
830 Capture "userid" Int :> Get '[JSON] User
811 Capture "userid" Int :> Get '[JSON] User
832 :<|> -- delete the user with given userid. empty response
813 :<|> -- delete the user with given userid. empty response
833− Capture "userid" Int :> DeleteNoContent
814+ Capture "userid" Int :> Delete '[] ()
836We can instead factor out the `userid`:
817We can instead factor out the `userid`:
@@ -838,7 +819,7 @@ We can instead factor out the `userid`:
838``` haskell
819``` haskell
839type UserAPI4 = Capture "userid" Int :>
820type UserAPI4 = Capture "userid" Int :>
840 ( Get '[JSON] User
821 ( Get '[JSON] User
841− :<|> DeleteNoContent
822+ :<|> Delete '[] ()
@@ -846,26 +827,26 @@ However, you have to be aware that this has an effect on the type of the
846corresponding `Server`:
827corresponding `Server`:
848``` haskell ignore
829``` haskell ignore
849−Server UserAPI3 = (Int -> Handler User)
830+Server UserAPI3 = (Int -> ExceptT ServantErr IO User)
850− :<|> (Int -> Handler NoContent)
831+ :<|> (Int -> ExceptT ServantErr IO ())
852−Server UserAPI4 = Int -> ( Handler User
833+Server UserAPI4 = Int -> ( ExceptT ServantErr IO User
853− :<|> Handler NoContent
834+ :<|> ExceptT ServantErr IO ()
857In the first case, each handler receives the *userid* argument. In the latter,
838In the first case, each handler receives the *userid* argument. In the latter,
858the whole `Server` takes the *userid* and has handlers that are just
839the whole `Server` takes the *userid* and has handlers that are just
859−computations in `Handler`, with no arguments. In other words:
840+computations in `ExceptT`, with no arguments. In other words:
861``` haskell
842``` haskell
862server8 :: Server UserAPI3
843server8 :: Server UserAPI3
863server8 = getUser :<|> deleteUser
844server8 = getUser :<|> deleteUser
865− where getUser :: Int -> Handler User
846+ where getUser :: Int -> ExceptT ServantErr IO User
866 getUser _userid = error "..."
847 getUser _userid = error "..."
868− deleteUser :: Int -> Handler NoContent
849+ deleteUser :: Int -> ExceptT ServantErr IO ()
869 deleteUser _userid = error "..."
850 deleteUser _userid = error "..."
871-- notice how getUser and deleteUser
852-- notice how getUser and deleteUser
@@ -874,10 +855,10 @@ server8 = getUser :<|> deleteUser
874server9 :: Server UserAPI4
855server9 :: Server UserAPI4
875server9 userid = getUser userid :<|> deleteUser userid
856server9 userid = getUser userid :<|> deleteUser userid
877− where getUser :: Int -> Handler User
858+ where getUser :: Int -> ExceptT ServantErr IO User
878 getUser = error "..."
859 getUser = error "..."
880− deleteUser :: Int -> Handler NoContent
861+ deleteUser :: Int -> ExceptT ServantErr IO ()
881 deleteUser = error "..."
862 deleteUser = error "..."
@@ -896,13 +877,13 @@ type API1 = "users" :>
896-- we factor out the Request Body
877-- we factor out the Request Body
897type API2 = ReqBody '[JSON] User :>
878type API2 = ReqBody '[JSON] User :>
898 ( Get '[JSON] User -- just display the same user back, don't register it
879 ( Get '[JSON] User -- just display the same user back, don't register it
899− :<|> PostNoContent -- register the user. empty response
880+ :<|> Post '[JSON] () -- register the user. empty response
902-- we factor out a Header
883-- we factor out a Header
903type API3 = Header "Authorization" Token :>
884type API3 = Header "Authorization" Token :>
904 ( Get '[JSON] SecretData -- get some secret data, if authorized
885 ( Get '[JSON] SecretData -- get some secret data, if authorized
905− :<|> ReqBody '[JSON] SecretData :> PostNoContent -- add some secret data, if authorized
886+ :<|> ReqBody '[JSON] SecretData :> Post '[] () -- add some secret data, if authorized
908newtype Token = Token ByteString
889newtype Token = Token ByteString
@@ -915,44 +896,44 @@ API type only at the end.
915``` haskell
896``` haskell
916type UsersAPI =
897type UsersAPI =
917 Get '[JSON] [User] -- list users
898 Get '[JSON] [User] -- list users
918− :<|> ReqBody '[JSON] User :> PostNoContent -- add a user
899+ :<|> ReqBody '[JSON] User :> Post '[] () -- add a user
919 :<|> Capture "userid" Int :>
900 :<|> Capture "userid" Int :>
920 ( Get '[JSON] User -- view a user
901 ( Get '[JSON] User -- view a user
921− :<|> ReqBody '[JSON] User :> PutNoContent -- update a user
902+ :<|> ReqBody '[JSON] User :> Put '[] () -- update a user
922− :<|> DeleteNoContent -- delete a user
903+ :<|> Delete '[] () -- delete a user
925usersServer :: Server UsersAPI
906usersServer :: Server UsersAPI
926usersServer = getUsers :<|> newUser :<|> userOperations
907usersServer = getUsers :<|> newUser :<|> userOperations
928− where getUsers :: Handler [User]
909+ where getUsers :: ExceptT ServantErr IO [User]
929 getUsers = error "..."
910 getUsers = error "..."
931− newUser :: User -> Handler NoContent
912+ newUser :: User -> ExceptT ServantErr IO ()
932 newUser = error "..."
913 newUser = error "..."
934 userOperations userid =
915 userOperations userid =
935 viewUser userid :<|> updateUser userid :<|> deleteUser userid
916 viewUser userid :<|> updateUser userid :<|> deleteUser userid
938− viewUser :: Int -> Handler User
919+ viewUser :: Int -> ExceptT ServantErr IO User
939 viewUser = error "..."
920 viewUser = error "..."
941− updateUser :: Int -> User -> Handler NoContent
922+ updateUser :: Int -> User -> ExceptT ServantErr IO ()
942 updateUser = error "..."
923 updateUser = error "..."
944− deleteUser :: Int -> Handler NoContent
925+ deleteUser :: Int -> ExceptT ServantErr IO ()
945 deleteUser = error "..."
926 deleteUser = error "..."
948``` haskell
929``` haskell
949type ProductsAPI =
930type ProductsAPI =
950 Get '[JSON] [Product] -- list products
931 Get '[JSON] [Product] -- list products
951− :<|> ReqBody '[JSON] Product :> PostNoContent -- add a product
932+ :<|> ReqBody '[JSON] Product :> Post '[] () -- add a product
952 :<|> Capture "productid" Int :>
933 :<|> Capture "productid" Int :>
953 ( Get '[JSON] Product -- view a product
934 ( Get '[JSON] Product -- view a product
954− :<|> ReqBody '[JSON] Product :> PutNoContent -- update a product
935+ :<|> ReqBody '[JSON] Product :> Put '[] () -- update a product
955− :<|> DeleteNoContent -- delete a product
936+ :<|> Delete '[] () -- delete a product
958data Product = Product { productId :: Int }
939data Product = Product { productId :: Int }
@@ -960,23 +941,23 @@ data Product = Product { productId :: Int }
960productsServer :: Server ProductsAPI
941productsServer :: Server ProductsAPI
961productsServer = getProducts :<|> newProduct :<|> productOperations
942productsServer = getProducts :<|> newProduct :<|> productOperations
963− where getProducts :: Handler [Product]
944+ where getProducts :: ExceptT ServantErr IO [Product]
964 getProducts = error "..."
945 getProducts = error "..."
966− newProduct :: Product -> Handler NoContent
947+ newProduct :: Product -> ExceptT ServantErr IO ()
967 newProduct = error "..."
948 newProduct = error "..."
969 productOperations productid =
950 productOperations productid =
970 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
951 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
973− viewProduct :: Int -> Handler Product
954+ viewProduct :: Int -> ExceptT ServantErr IO Product
974 viewProduct = error "..."
955 viewProduct = error "..."
976− updateProduct :: Int -> Product -> Handler NoContent
957+ updateProduct :: Int -> Product -> ExceptT ServantErr IO ()
977 updateProduct = error "..."
958 updateProduct = error "..."
979− deleteProduct :: Int -> Handler NoContent
960+ deleteProduct :: Int -> ExceptT ServantErr IO ()
980 deleteProduct = error "..."
961 deleteProduct = error "..."
@@ -996,44 +977,34 @@ abstract that away:
996-- indexed by values of type 'i'
977-- indexed by values of type 'i'
997type APIFor a i =
978type APIFor a i =
998 Get '[JSON] [a] -- list 'a's
979 Get '[JSON] [a] -- list 'a's
999− :<|> ReqBody '[JSON] a :> PostNoContent -- add an 'a'
980+ :<|> ReqBody '[JSON] a :> Post '[] () -- add an 'a'
1000 :<|> Capture "id" i :>
981 :<|> Capture "id" i :>
1001 ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
982 ( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
1002− :<|> ReqBody '[JSON] a :> PutNoContent -- update an 'a'
983+ :<|> ReqBody '[JSON] a :> Put '[] () -- update an 'a'
1003− :<|> DeleteNoContent -- delete an 'a'
984+ :<|> Delete '[] () -- delete an 'a'
1006-- Build the appropriate 'Server'
987-- Build the appropriate 'Server'
1007-- given the handlers of the right type.
988-- given the handlers of the right type.
1008−serverFor :: Handler [a] -- handler for listing of 'a's
989+serverFor :: ExceptT ServantErr IO [a] -- handler for listing of 'a's
1009− -> (a -> Handler NoContent) -- handler for adding an 'a'
990+ -> (a -> ExceptT ServantErr IO ()) -- handler for adding an 'a'
1010− -> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
991+ -> (i -> ExceptT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1011− -> (i -> a -> Handler NoContent) -- updating an 'a' with given id
992+ -> (i -> a -> ExceptT ServantErr IO ()) -- updating an 'a' with given id
1012− -> (i -> Handler NoContent) -- deleting an 'a' given its id
993+ -> (i -> ExceptT ServantErr IO ()) -- deleting an 'a' given its id
1013 -> Server (APIFor a i)
994 -> Server (APIFor a i)
1014serverFor = error "..."
995serverFor = error "..."
1015-- implementation left as an exercise. contact us on IRC
996-- implementation left as an exercise. contact us on IRC
1016-- or the mailing list if you get stuck!
997-- or the mailing list if you get stuck!
1019−When your API contains the `EmptyAPI` combinator, you'll want to use
1020−`emptyServer` in the corresponding slot for your server, which will simply fail
1021−with 404 whenever a request reaches it:
1024−type CombinedAPI2 = API :<|> "empty" :> EmptyAPI
1026−server11 :: Server CombinedAPI2
1027−server11 = server3 :<|> emptyServer
1030## Using another monad for your handlers
1000## Using another monad for your handlers
1032−Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
1002+Remember how `Server` turns combinators for HTTP methods into `ExceptT
1003+ServantErr IO`? Well, actually, there's more to that. `Server` is actually a
1033simple type synonym.
1004simple type synonym.
1035``` haskell ignore
1006``` haskell ignore
1036−type Server api = ServerT api Handler
1007+type Server api = ServerT api (ExceptT ServantErr IO)
1039`ServerT` is the actual type family that computes the required types for the
1010`ServerT` is the actual type family that computes the required types for the
@@ -1053,71 +1024,77 @@ into something **servant** can understand?
1053If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1024If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
1054do we have?
1025do we have?
1056−``` haskell
1027+``` haskell ignore
1057−type (~>) m n = forall a. m a -> n a
1028+newtype m :~> n = Nat { unNat :: forall a. m a -> n a}
1060For example:
1031For example:
1062``` haskell
1033``` haskell
1063−listToMaybe' :: [] ~> Maybe
1034+listToMaybeNat :: [] :~> Maybe
1064−listToMaybe' = listToMaybe -- from Data.Maybe
1035+listToMaybeNat = Nat listToMaybe -- from Data.Maybe
1067−Note that `servant` doesn't declare the `~>` type-alias, as the unfolded
1038+(`Nat` comes from "natural transformation", in case you're wondering.)
1068−variant isn't much longer to write, as we'll see shortly.
1070−So if you want to write handlers using another monad/type than `Handler`, say the `Reader String` monad, the first thing you have to
1040+So if you want to write handlers using another monad/type than `ExceptT
1041+ServantErr IO`, say the `Reader String` monad, the first thing you have to
1071prepare is a function:
1042prepare is a function:
1073``` haskell ignore
1044``` haskell ignore
1074−readerToHandler :: Reader String a -> Handler a
1045+readerToHandler :: Reader String :~> ExceptT ServantErr IO
1048+Let's start with `readerToHandler'`. We obviously have to run the `Reader`
1077−We obviously have to run the `Reader` computation by supplying it with a
1049+computation by supplying it with a `String`, like `"hi"`. We get an `a` out
1078−`String`, like `"hi"`. We get an `a` out from that and can then just `return`
1050+from that and can then just `return` it into `ExceptT`. We can then just wrap
1079−it into `Handler`.
1051+that function with the `Nat` constructor to make it have the fancier type.
1081``` haskell
1053``` haskell
1082−readerToHandler :: Reader String a -> Handler a
1054+readerToHandler' :: forall a. Reader String a -> ExceptT ServantErr IO a
1083−readerToHandler r = return (runReader r "hi")
1055+readerToHandler' r = return (runReader r "hi")
1057+readerToHandler :: Reader String :~> ExceptT ServantErr IO
1058+readerToHandler = Nat readerToHandler'
1086We can write some simple webservice with the handlers running in `Reader String`.
1061We can write some simple webservice with the handlers running in `Reader String`.
1088``` haskell
1063``` haskell
1089type ReaderAPI = "a" :> Get '[JSON] Int
1064type ReaderAPI = "a" :> Get '[JSON] Int
1090− :<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
1065+ :<|> "b" :> Get '[JSON] String
1092readerAPI :: Proxy ReaderAPI
1067readerAPI :: Proxy ReaderAPI
1093readerAPI = Proxy
1068readerAPI = Proxy
1095readerServerT :: ServerT ReaderAPI (Reader String)
1070readerServerT :: ServerT ReaderAPI (Reader String)
1096−readerServerT = a :<|> b where
1071+readerServerT = a :<|> b
1097− a :: Reader String Int
1073+ where a :: Reader String Int
1098− a = return 1797
1074+ a = return 1797
1100− b :: Double -> Reader String Bool
1076+ b :: Reader String String
1101− b _ = asks (== "hi")
1077+ b = ask
1104We unfortunately can't use `readerServerT` as an argument of `serve`, because
1080We unfortunately can't use `readerServerT` as an argument of `serve`, because
1105−`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `Handler`. But there's a simple solution to this.
1081+`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
1082+ServantErr IO`. But there's a simple solution to this.
1107−### Welcome `hoistServer`
1084+### Enter `enter`
1109That's right. We have just written `readerToHandler`, which is exactly what we
1086That's right. We have just written `readerToHandler`, which is exactly what we
1110would need to apply to all handlers to make the handlers have the
1087would need to apply to all handlers to make the handlers have the
1111right type for `serve`. Being cumbersome to do by hand, we provide a function
1088right type for `serve`. Being cumbersome to do by hand, we provide a function
1112−`hoistServer` which takes a natural transformation between two parameterized types `m`
1089+`enter` which takes a natural transformation between two parametrized types `m`
1113and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
1090and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
1115−In our case, we can wrap up our little webservice by using
1092+In our case, we can wrap up our little webservice by using `enter
1116−`hoistServer readerAPI readerToHandler` on our handlers.
1093+readerToHandler` on our handlers.
1118``` haskell
1095``` haskell
1119readerServer :: Server ReaderAPI
1096readerServer :: Server ReaderAPI
1120−readerServer = hoistServer readerAPI readerToHandler readerServerT
1097+readerServer = enter readerToHandler readerServerT
1122app4 :: Application
1099app4 :: Application
1123app4 = serve readerAPI readerServer
1100app4 = serve readerAPI readerServer
@@ -1128,71 +1105,10 @@ This is the webservice in action:
1129$ curl http://localhost:8081/a
1106$ curl http://localhost:8081/a
1131−$ curl http://localhost:8081/b -X GET -d '42.0' -H 'Content-Type: application/json'
1108+$ curl http://localhost:8081/b
1135−### An arrow is a reader too.
1137−In previous versions of `servant` we had an `enter` to do what `hoistServer`
1138−does now. `enter` had an ambitious design goals, but was problematic in practice.
1140−One problematic situation was when the source monad was `(->) r`, yet it's
1141−handy in practice, because `(->) r` is isomorphic to `Reader r`.
1143−We can rewrite the previous example without `Reader`:
1146−funServerT :: ServerT ReaderAPI ((->) String)
1147−funServerT = a :<|> b where
1151− -- unfortunately, we cannot make `String` the first argument.
1152− b :: Double -> String -> Bool
1153− b _ s = s == "hi"
1109+"hi"
1155−funToHandler :: (String -> a) -> Handler a
1156−funToHandler f = return (f "hi")
1159−app5 = serve readerAPI (hoistServer readerAPI funToHandler funServerT)
1162−## Streaming endpoints
1164−We can create endpoints that don't just give back a single result, but give
1165−back a *stream* of results, served one at a time. Stream endpoints only provide
1166−a single content type, and also specify what framing strategy is used to
1167−delineate the results. To serve these results, we need to give back a stream
1168−producer. Adapters can be written to *Pipes*, *Conduit* and the like, or
1169−written directly as `SourceIO`s. SourceIO builds upon servant's own `SourceT`
1170−stream type (it's simpler than *Pipes* or *Conduit*).
1171−The API of a streaming endpoint needs to explicitly specify which sort of
1172−generator it produces. Note that the generator itself is returned by a
1173−`Handler` action, so that additional IO may be done in the creation of one.
1176−type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (SourceIO User)
1177−streamAPI :: Proxy StreamAPI
1180−streamUsers :: SourceIO User
1181−streamUsers = source [isaac, albert, albert]
1184−app6 = serve streamAPI (return streamUsers)
1187−This simple application returns a stream of `User` values encoded in JSON
1188−format, with each value separated by a newline. In this case, the stream will
1189−consist of the value of `isaac`, followed by the value of `albert`, then the
1190−value of `albert` a second time. Importantly, the stream is written back as
1191−results are produced, rather than all at once. This means first that results
1192−are delivered when they are available, and second, that if an exception
1193−interrupts production of the full stream, nonetheless partial results have
1194−already been written back.
1196## Conclusion
1112## Conclusion
1198You're now equipped to write webservices/web-applications using
1114You're now equipped to write webservices/web-applications using