git.iko.soy/typeable/servant
on master help sign in
tutorial: make Server compile
commita7424c47530f8fbc053b5dc2829330242372f62f authorSönke Hahn <soenkehahn@gmail.com> dateThu 28 Jan 2016, 14:12 (11 years ago) parentad48c0e
2 files changed+56−70
doc/tutorial/Server.lhs+54−69 doc/tutorial/tutorial.cabal+2−1
doc/tutorial/Server.lhs+54−69
@@ -48,7 +48,7 @@ module Server where
4848
4949import Control.Monad.IO.Class
5050import Control.Monad.Reader
51−import Control.Monad.Trans.Either
51+import Control.Monad.Trans.Except
5252import Data.Aeson
5353import Data.Aeson.Types
5454import Data.Attoparsec.ByteString
@@ -130,11 +130,11 @@ corresponding API type.
130130The first thing to know about the `Server` type family is that behind the
131131scenes it will drive the routing, letting you focus only on the business
132132logic. The second thing to know is that for each endpoint, your handlers will
133−by default run in the `EitherT ServantErr IO` monad. This is overridable very
133+by default run in the `ExceptT ServantErr IO` monad. This is overridable very
134134easily, as explained near the end of this guide. Third thing, the type of the
135135value returned in that monad must be the same as the second argument of the
136136HTTP method combinator used for the corresponding endpoint. In our case, it
137−means we must provide a handler of type `EitherT ServantErr IO [User]`. Well,
137+means we must provide a handler of type `ExceptT ServantErr IO [User]`. Well,
138138we have a monad, let's just `return` our list:
139139
140140``` haskell
@@ -152,7 +152,7 @@ userAPI = Proxy
152152-- which you can think of as an "abstract" web application,
153153-- not yet a webserver.
154154app1 :: Application
155−app1 = serve userAPI server1
155+app1 = serve userAPI EmptyConfig server1
156156```
157157
158158The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
@@ -288,15 +288,15 @@ server3 = position
288288 :<|> hello
289289 :<|> marketing
290290
291− where position :: Int -> Int -> EitherT ServantErr IO Position
291+ where position :: Int -> Int -> ExceptT ServantErr IO Position
292292 position x y = return (Position x y)
293293
294− hello :: Maybe String -> EitherT ServantErr IO HelloMessage
294+ hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
295295 hello mname = return . HelloMessage $ case mname of
296296 Nothing -> "Hello, anonymous coward"
297297 Just n -> "Hello, " ++ n
298298
299− marketing :: ClientInfo -> EitherT ServantErr IO Email
299+ marketing :: ClientInfo -> ExceptT ServantErr IO Email
300300 marketing clientinfo = return (emailForClient clientinfo)
301301```
302302
@@ -327,7 +327,7 @@ $ curl -X POST -d '{"name":"Alp Mestanogullari", "email" : "alp@foo.com", "age":
327327For reference, here's a list of some combinators from *servant* and for those
328328that get turned into arguments to the handlers, the type of the argument.
329329
330− > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `EitherT ServantErr IO <something>`.
330+ > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `ExceptT ServantErr IO <something>`.
331331 > - `Capture "something" a` becomes an argument of type `a`.
332332 > - `QueryParam "something" a`, `MatrixParam "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.
333333 > - `QueryFlag "something"` and `MatrixFlag "something"` get turned into arguments of type `Bool`.
@@ -369,22 +369,7 @@ data Direction
369369 | Left
370370 | Right
371371
372−instance FromText Direction where
373− -- requires {-# LANGUAGE OverloadedStrings #-}
374− fromText "up" = Just Up
375− fromText "down" = Just Down
376− fromText "left" = Just Server.Left
377− fromText "right" = Just Server.Right
378− fromText _ = Nothing
379−
380−instance ToText Direction where
381− toText Up = "up"
382− toText Down = "down"
383− toText Server.Left = "left"
384− toText Server.Right = "right"
385−
386372newtype UserId = UserId Int64
387− deriving (FromText, ToText)
388373```
389374
390375or writing the instances by hand:
@@ -643,7 +628,7 @@ server4 :: Server PersonAPI
643628server4 = return persons
644629
645630app2 :: Application
646−app2 = serve personAPI server4
631+app2 = serve personAPI EmptyConfig server4
647632```
648633
649634And we're good to go. You can run this example with `dist/build/tutorial/tutorial 4`.
@@ -656,10 +641,10 @@ And we're good to go. You can run this example with `dist/build/tutorial/tutoria
656641 # or just point your browser to http://localhost:8081/persons
657642```
658643
659−The `EitherT ServantErr IO` monad
644+The `ExceptT ServantErr IO` monad
660645=================================
661646
662−At the heart of the handlers is the monad they run in, namely `EitherT
647+At the heart of the handlers is the monad they run in, namely `ExceptT
663648ServantErr IO`. One might wonder: why this monad? The answer is that it is the
664649simplest monad with the following properties:
665650
@@ -677,11 +662,11 @@ data Either e a = Left e | Right a
677662
678663-- from the 'either' package at
679664-- http://hackage.haskell.org/package/either-4.3.3.2/docs/Control-Monad-Trans-Either.html
680−newtype EitherT e m a
681− = EitherT { runEitherT :: m (Either e a) }
665+newtype ExceptT e m a
666+ = ExceptT { runEitherT :: m (Either e a) }
682667```
683668
684−In short, this means that a handler of type `EitherT ServantErr IO a` is simply
669+In short, this means that a handler of type `ExceptT ServantErr IO a` is simply
685670equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
686671action that either returns an error or a result.
687672
@@ -689,7 +674,7 @@ The aforementioned `either` package is worth taking a look at. Perhaps most
689674importantly:
690675
691676``` haskell ignore
692−left :: Monad m => e -> EitherT e m a
677+left :: Monad m => e -> ExceptT e m a
693678```
694679Allows you to return an error from your handler (whereas `return` is enough to
695680return a success).
@@ -701,14 +686,14 @@ kind and abort early. The next two sections cover how to do just that.
701686Performing IO
702687-------------
703688
704−Another important instance from the list above is `MonadIO m => MonadIO (EitherT e m)`. [`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html) is a class from the *transformers* package defined as:
689+Another important instance from the list above is `MonadIO m => MonadIO (ExceptT e m)`. [`MonadIO`](http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-IO-Class.html) is a class from the *transformers* package defined as:
705690
706691``` haskell ignore
707692class Monad m => MonadIO m where
708693 liftIO :: IO a -> m a
709694```
710695
711−Obviously, 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`:
696+Obviously, the `IO` monad provides a `MonadIO` instance. Hence for any type `e`, `ExceptT e IO` has a `MonadIO` instance. So if you want to run any kind of IO computation in your handlers, just use `liftIO`:
712697
713698``` haskell
714699type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
@@ -748,8 +733,8 @@ module. If you want to use these values but add a body or some headers, just
748733use record update syntax:
749734
750735``` haskell
751−failingHandler :: EitherT ServantErr IO ()
752−failingHandler = left myerr
736+failingHandler :: ExceptT ServantErr IO ()
737+failingHandler = throwE myerr
753738
754739 where myerr :: ServantErr
755740 myerr = err503 { errBody = "Sorry dear user." }
@@ -764,7 +749,7 @@ server6 = do
764749 exists <- liftIO (doesFileExist "myfile.txt")
765750 if exists
766751 then liftIO (readFile "myfile.txt") >>= return . FileContent
767− else left custom404Err
752+ else throwE custom404Err
768753
769754 where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
770755```
@@ -854,7 +839,7 @@ server7 :: Server CodeAPI
854839server7 = serveDirectory "tutorial"
855840
856841app3 :: Application
857−app3 = serve codeAPI server7
842+app3 = serve codeAPI EmptyConfig server7
858843```
859844
860845This 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.
@@ -981,25 +966,25 @@ type UserAPI4 = Capture "userid" Int :>
981966However, you have to be aware that this has an effect on the type of the corresponding `Server`:
982967
983968``` haskell ignore
984−Server UserAPI3 = (Int -> EitherT ServantErr IO User)
985− :<|> (Int -> EitherT ServantErr IO ())
969+Server UserAPI3 = (Int -> ExceptT ServantErr IO User)
970+ :<|> (Int -> ExceptT ServantErr IO ())
986971
987−Server UserAPI4 = Int -> ( EitherT ServantErr IO User
988− :<|> EitherT ServantErr IO ()
972+Server UserAPI4 = Int -> ( ExceptT ServantErr IO User
973+ :<|> ExceptT ServantErr IO ()
989974 )
990975```
991976
992977In the first case, each handler receives the *userid* argument. In the latter,
993−the whole `Server` takes the *userid* and has handlers that are just computations in `EitherT`, with no arguments. In other words:
978+the whole `Server` takes the *userid* and has handlers that are just computations in `ExceptT`, with no arguments. In other words:
994979
995980``` haskell
996981server8 :: Server UserAPI3
997982server8 = getUser :<|> deleteUser
998983
999− where getUser :: Int -> EitherT ServantErr IO User
984+ where getUser :: Int -> ExceptT ServantErr IO User
1000985 getUser _userid = error "..."
1001986
1002− deleteUser :: Int -> EitherT ServantErr IO ()
987+ deleteUser :: Int -> ExceptT ServantErr IO ()
1003988 deleteUser _userid = error "..."
1004989
1005990-- notice how getUser and deleteUser
@@ -1008,10 +993,10 @@ server8 = getUser :<|> deleteUser
1008993server9 :: Server UserAPI4
1009994server9 userid = getUser userid :<|> deleteUser userid
1010995
1011− where getUser :: Int -> EitherT ServantErr IO User
996+ where getUser :: Int -> ExceptT ServantErr IO User
1012997 getUser = error "..."
1013998
1014− deleteUser :: Int -> EitherT ServantErr IO ()
999+ deleteUser :: Int -> ExceptT ServantErr IO ()
10151000 deleteUser = error "..."
10161001```
10171002
@@ -1055,23 +1040,23 @@ type UsersAPI =
10551040usersServer :: Server UsersAPI
10561041usersServer = getUsers :<|> newUser :<|> userOperations
10571042
1058− where getUsers :: EitherT ServantErr IO [User]
1043+ where getUsers :: ExceptT ServantErr IO [User]
10591044 getUsers = error "..."
10601045
1061− newUser :: User -> EitherT ServantErr IO ()
1046+ newUser :: User -> ExceptT ServantErr IO ()
10621047 newUser = error "..."
10631048
10641049 userOperations userid =
10651050 viewUser userid :<|> updateUser userid :<|> deleteUser userid
10661051
10671052 where
1068− viewUser :: Int -> EitherT ServantErr IO User
1053+ viewUser :: Int -> ExceptT ServantErr IO User
10691054 viewUser = error "..."
10701055
1071− updateUser :: Int -> User -> EitherT ServantErr IO ()
1056+ updateUser :: Int -> User -> ExceptT ServantErr IO ()
10721057 updateUser = error "..."
10731058
1074− deleteUser :: Int -> EitherT ServantErr IO ()
1059+ deleteUser :: Int -> ExceptT ServantErr IO ()
10751060 deleteUser = error "..."
10761061```
10771062
@@ -1090,23 +1075,23 @@ data Product = Product { productId :: Int }
10901075productsServer :: Server ProductsAPI
10911076productsServer = getProducts :<|> newProduct :<|> productOperations
10921077
1093− where getProducts :: EitherT ServantErr IO [Product]
1078+ where getProducts :: ExceptT ServantErr IO [Product]
10941079 getProducts = error "..."
10951080
1096− newProduct :: Product -> EitherT ServantErr IO ()
1081+ newProduct :: Product -> ExceptT ServantErr IO ()
10971082 newProduct = error "..."
10981083
10991084 productOperations productid =
11001085 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
11011086
11021087 where
1103− viewProduct :: Int -> EitherT ServantErr IO Product
1088+ viewProduct :: Int -> ExceptT ServantErr IO Product
11041089 viewProduct = error "..."
11051090
1106− updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1091+ updateProduct :: Int -> Product -> ExceptT ServantErr IO ()
11071092 updateProduct = error "..."
11081093
1109− deleteProduct :: Int -> EitherT ServantErr IO ()
1094+ deleteProduct :: Int -> ExceptT ServantErr IO ()
11101095 deleteProduct = error "..."
11111096```
11121097
@@ -1134,11 +1119,11 @@ type APIFor a i =
11341119
11351120-- Build the appropriate 'Server'
11361121-- given the handlers of the right type.
1137−serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1138− -> (a -> EitherT ServantErr IO ()) -- handler for adding an 'a'
1139− -> (i -> EitherT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1140− -> (i -> a -> EitherT ServantErr IO ()) -- updating an 'a' with given id
1141− -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1122+serverFor :: ExceptT ServantErr IO [a] -- handler for listing of 'a's
1123+ -> (a -> ExceptT ServantErr IO ()) -- handler for adding an 'a'
1124+ -> (i -> ExceptT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1125+ -> (i -> a -> ExceptT ServantErr IO ()) -- updating an 'a' with given id
1126+ -> (i -> ExceptT ServantErr IO ()) -- deleting an 'a' given its id
11421127 -> Server (APIFor a i)
11431128serverFor = error "..."
11441129-- implementation left as an exercise. contact us on IRC
@@ -1148,10 +1133,10 @@ serverFor = error "..."
11481133Using another monad for your handlers
11491134=====================================
11501135
1151−Remember how `Server` turns combinators for HTTP methods into `EitherT ServantErr IO`? Well, actually, there's more to that. `Server` is actually a simple type synonym.
1136+Remember how `Server` turns combinators for HTTP methods into `ExceptT ServantErr IO`? Well, actually, there's more to that. `Server` is actually a simple type synonym.
11521137
11531138``` haskell ignore
1154−type Server api = ServerT api (EitherT ServantErr IO)
1139+type Server api = ServerT api (ExceptT ServantErr IO)
11551140```
11561141
11571142`ServerT` is the actual type family that computes the required types for the handlers that's part of the `HasServer` class. It's like `Server` except that it takes a third parameter which is the monad you want your handlers to run in, or more generally the return types of your handlers. This third parameter is used for specifying the return type of the handler for an endpoint, e.g when computing `ServerT (Get '[JSON] Person) SomeMonad`. The result would be `SomeMonad Person`.
@@ -1173,24 +1158,24 @@ newtype m :~> n = Nat { unNat :: forall a. m a -> n a}
11731158```
11741159(`Nat` comes from "natural transformation", in case you're wondering.)
11751160
1176−So if you want to write handlers using another monad/type than `EitherT
1161+So if you want to write handlers using another monad/type than `ExceptT
11771162ServantErr IO`, say the `Reader String` monad, the first thing you have to
11781163prepare is a function:
11791164
11801165``` haskell ignore
1181−readerToEither :: Reader String :~> EitherT ServantErr IO
1166+readerToEither :: Reader String :~> ExceptT ServantErr IO
11821167```
11831168
11841169Let's start with `readerToEither'`. We obviously have to run the `Reader`
11851170computation by supplying it with a `String`, like `"hi"`. We get an `a` out
1186−from that and can then just `return` it into `EitherT`. We can then just wrap
1171+from that and can then just `return` it into `ExceptT`. We can then just wrap
11871172that function with the `Nat` constructor to make it have the fancier type.
11881173
11891174``` haskell
1190−readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1175+readerToEither' :: forall a. Reader String a -> ExceptT ServantErr IO a
11911176readerToEither' r = return (runReader r "hi")
11921177
1193−readerToEither :: Reader String :~> EitherT ServantErr IO
1178+readerToEither :: Reader String :~> ExceptT ServantErr IO
11941179readerToEither = Nat readerToEither'
11951180```
11961181
@@ -1214,7 +1199,7 @@ readerServerT = a :<|> b
12141199```
12151200
12161201We unfortunately can't use `readerServerT` as an argument of `serve`, because
1217−`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `EitherT
1202+`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT
12181203ServantErr IO`. But there's a simple solution to this.
12191204
12201205Enter `enter`
@@ -1233,7 +1218,7 @@ readerServer :: Server ReaderAPI
12331218readerServer = enter readerToEither readerServerT
12341219
12351220app4 :: Application
1236−app4 = serve readerAPI readerServer
1221+app4 = serve readerAPI EmptyConfig readerServer
12371222```
12381223
12391224And we can indeed see this webservice in action by running `dist/build/tutorial/tutorial 7`.
doc/tutorial/tutorial.cabal+2−1
@@ -18,7 +18,7 @@ library
1818 , Client
1919 -- , Docs
2020 -- , Javascript
21− -- , Server
21+ , Server
2222 -- other-modules:
2323 -- other-extensions:
2424 build-depends: base == 4.*
@@ -51,3 +51,4 @@ library
5151 ghc-options: -Wall -Werror -pgmL markdown-unlit
5252 -- to silence aeson-0.10 warnings:
5353 ghc-options: -fno-warn-missing-methods
54+ ghc-options: -fno-warn-name-shadowing