git.iko.soy/typeable/servant
on master help sign in
tutorial: make Server compile
a7424c4Sönke Hahn
doc/tutorial/Server.lhs+54−69
@@ -48,7 +48,7 @@ module Server where
48
48
49import Control.Monad.IO.Class
49import Control.Monad.IO.Class
50import Control.Monad.Reader
50import Control.Monad.Reader
51−import Control.Monad.Trans.Either
51+import Control.Monad.Trans.Except
52import Data.Aeson
52import Data.Aeson
53import Data.Aeson.Types
53import Data.Aeson.Types
54import Data.Attoparsec.ByteString
54import Data.Attoparsec.ByteString
@@ -130,11 +130,11 @@ corresponding API type.
130The first thing to know about the `Server` type family is that behind the
130The first thing to know about the `Server` type family is that behind the
131scenes it will drive the routing, letting you focus only on the business
131scenes it will drive the routing, letting you focus only on the business
132logic. The second thing to know is that for each endpoint, your handlers will
132logic. 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
134easily, as explained near the end of this guide. Third thing, the type of the
134easily, as explained near the end of this guide. Third thing, the type of the
135value returned in that monad must be the same as the second argument of the
135value returned in that monad must be the same as the second argument of the
136HTTP method combinator used for the corresponding endpoint. In our case, it
136HTTP 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,
138we have a monad, let's just `return` our list:
138we have a monad, let's just `return` our list:
139
139
140``` haskell
140``` haskell
@@ -152,7 +152,7 @@ userAPI = Proxy
152-- which you can think of as an "abstract" web application,
152-- which you can think of as an "abstract" web application,
153-- not yet a webserver.
153-- not yet a webserver.
154app1 :: Application
154app1 :: Application
155−app1 = serve userAPI server1
155+app1 = serve userAPI EmptyConfig server1
156```
156```
157
157
158The `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).
@@ -288,15 +288,15 @@ server3 = position
288 :<|> hello
288 :<|> hello
289 :<|> marketing
289 :<|> marketing
290
290
291− where position :: Int -> Int -> EitherT ServantErr IO Position
291+ where position :: Int -> Int -> ExceptT ServantErr IO Position
292 position x y = return (Position x y)
292 position x y = return (Position x y)
293
293
294− hello :: Maybe String -> EitherT ServantErr IO HelloMessage
294+ hello :: Maybe String -> ExceptT ServantErr IO HelloMessage
295 hello mname = return . HelloMessage $ case mname of
295 hello mname = return . HelloMessage $ case mname of
296 Nothing -> "Hello, anonymous coward"
296 Nothing -> "Hello, anonymous coward"
297 Just n -> "Hello, " ++ n
297 Just n -> "Hello, " ++ n
298
298
299− marketing :: ClientInfo -> EitherT ServantErr IO Email
299+ marketing :: ClientInfo -> ExceptT ServantErr IO Email
300 marketing clientinfo = return (emailForClient clientinfo)
300 marketing clientinfo = return (emailForClient clientinfo)
301```
301```
302
302
@@ -327,7 +327,7 @@ $ curl -X POST -d '{"name":"Alp Mestanogullari", "email" : "alp@foo.com", "age":
327For reference, here's a list of some combinators from *servant* and for those
327For reference, here's a list of some combinators from *servant* and for those
328that get turned into arguments to the handlers, the type of the argument.
328that get turned into arguments to the handlers, the type of the argument.
329
329
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>`.
331 > - `Capture "something" a` becomes an argument of type `a`.
331 > - `Capture "something" a` becomes an argument of type `a`.
332 > - `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.
332 > - `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.
333 > - `QueryFlag "something"` and `MatrixFlag "something"` get turned into arguments of type `Bool`.
333 > - `QueryFlag "something"` and `MatrixFlag "something"` get turned into arguments of type `Bool`.
@@ -369,22 +369,7 @@ data Direction
369 | Left
369 | Left
370 | Right
370 | Right
371
371
372−instance FromText Direction where
373− -- requires {-# LANGUAGE OverloadedStrings #-}
374− fromText "up" = Just Up
375− fromText "down" = Just Down
376− fromText "left" = Just Server.Left
377− fromText "right" = Just Server.Right
378− fromText _ = Nothing
379−
380−instance ToText Direction where
381− toText Up = "up"
382− toText Down = "down"
383− toText Server.Left = "left"
384− toText Server.Right = "right"
385−
386newtype UserId = UserId Int64
372newtype UserId = UserId Int64
387− deriving (FromText, ToText)
388```
373```
389
374
390or writing the instances by hand:
375or writing the instances by hand:
@@ -643,7 +628,7 @@ server4 :: Server PersonAPI
643server4 = return persons
628server4 = return persons
644
629
645app2 :: Application
630app2 :: Application
646−app2 = serve personAPI server4
631+app2 = serve personAPI EmptyConfig server4
647```
632```
648
633
649And we're good to go. You can run this example with `dist/build/tutorial/tutorial 4`.
634And 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
656 # or just point your browser to http://localhost:8081/persons
641 # or just point your browser to http://localhost:8081/persons
657```
642```
658
643
659−The `EitherT ServantErr IO` monad
644+The `ExceptT ServantErr IO` monad
660=================================
645=================================
661
646
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
663ServantErr IO`. One might wonder: why this monad? The answer is that it is the
648ServantErr IO`. One might wonder: why this monad? The answer is that it is the
664simplest monad with the following properties:
649simplest monad with the following properties:
665
650
@@ -677,11 +662,11 @@ data Either e a = Left e | Right a
677
662
678-- from the 'either' package at
663-- from the 'either' package at
679-- http://hackage.haskell.org/package/either-4.3.3.2/docs/Control-Monad-Trans-Either.html
664-- http://hackage.haskell.org/package/either-4.3.3.2/docs/Control-Monad-Trans-Either.html
680−newtype EitherT e m a
665+newtype ExceptT e m a
681− = EitherT { runEitherT :: m (Either e a) }
666+ = ExceptT { runEitherT :: m (Either e a) }
682```
667```
683
668
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
685equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
670equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
686action that either returns an error or a result.
671action that either returns an error or a result.
687
672
@@ -689,7 +674,7 @@ The aforementioned `either` package is worth taking a look at. Perhaps most
689importantly:
674importantly:
690
675
691``` haskell ignore
676``` haskell ignore
692−left :: Monad m => e -> EitherT e m a
677+left :: Monad m => e -> ExceptT e m a
693```
678```
694Allows you to return an error from your handler (whereas `return` is enough to
679Allows you to return an error from your handler (whereas `return` is enough to
695return a success).
680return a success).
@@ -701,14 +686,14 @@ kind and abort early. The next two sections cover how to do just that.
701Performing IO
686Performing IO
702-------------
687-------------
703
688
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:
705
690
706``` haskell ignore
691``` haskell ignore
707class Monad m => MonadIO m where
692class Monad m => MonadIO m where
708 liftIO :: IO a -> m a
693 liftIO :: IO a -> m a
709```
694```
710
695
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`:
712
697
713``` haskell
698``` haskell
714type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
699type 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
748use record update syntax:
733use record update syntax:
749
734
750``` haskell
735``` haskell
751−failingHandler :: EitherT ServantErr IO ()
736+failingHandler :: ExceptT ServantErr IO ()
752−failingHandler = left myerr
737+failingHandler = throwE myerr
753
738
754 where myerr :: ServantErr
739 where myerr :: ServantErr
755 myerr = err503 { errBody = "Sorry dear user." }
740 myerr = err503 { errBody = "Sorry dear user." }
@@ -764,7 +749,7 @@ server6 = do
764 exists <- liftIO (doesFileExist "myfile.txt")
749 exists <- liftIO (doesFileExist "myfile.txt")
765 if exists
750 if exists
766 then liftIO (readFile "myfile.txt") >>= return . FileContent
751 then liftIO (readFile "myfile.txt") >>= return . FileContent
767− else left custom404Err
752+ else throwE custom404Err
768
753
769 where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
754 where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
770```
755```
@@ -854,7 +839,7 @@ server7 :: Server CodeAPI
854server7 = serveDirectory "tutorial"
839server7 = serveDirectory "tutorial"
855
840
856app3 :: Application
841app3 :: Application
857−app3 = serve codeAPI server7
842+app3 = serve codeAPI EmptyConfig server7
858```
843```
859
844
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.
845This 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 :>
981However, you have to be aware that this has an effect on the type of the corresponding `Server`:
966However, you have to be aware that this has an effect on the type of the corresponding `Server`:
982
967
983``` haskell ignore
968``` haskell ignore
984−Server UserAPI3 = (Int -> EitherT ServantErr IO User)
969+Server UserAPI3 = (Int -> ExceptT ServantErr IO User)
985− :<|> (Int -> EitherT ServantErr IO ())
970+ :<|> (Int -> ExceptT ServantErr IO ())
986
971
987−Server UserAPI4 = Int -> ( EitherT ServantErr IO User
972+Server UserAPI4 = Int -> ( ExceptT ServantErr IO User
988− :<|> EitherT ServantErr IO ()
973+ :<|> ExceptT ServantErr IO ()
989 )
974 )
990```
975```
991
976
992In the first case, each handler receives the *userid* argument. In the latter,
977In 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:
994
979
995``` haskell
980``` haskell
996server8 :: Server UserAPI3
981server8 :: Server UserAPI3
997server8 = getUser :<|> deleteUser
982server8 = getUser :<|> deleteUser
998
983
999− where getUser :: Int -> EitherT ServantErr IO User
984+ where getUser :: Int -> ExceptT ServantErr IO User
1000 getUser _userid = error "..."
985 getUser _userid = error "..."
1001
986
1002− deleteUser :: Int -> EitherT ServantErr IO ()
987+ deleteUser :: Int -> ExceptT ServantErr IO ()
1003 deleteUser _userid = error "..."
988 deleteUser _userid = error "..."
1004
989
1005-- notice how getUser and deleteUser
990-- notice how getUser and deleteUser
@@ -1008,10 +993,10 @@ server8 = getUser :<|> deleteUser
1008server9 :: Server UserAPI4
993server9 :: Server UserAPI4
1009server9 userid = getUser userid :<|> deleteUser userid
994server9 userid = getUser userid :<|> deleteUser userid
1010
995
1011− where getUser :: Int -> EitherT ServantErr IO User
996+ where getUser :: Int -> ExceptT ServantErr IO User
1012 getUser = error "..."
997 getUser = error "..."
1013
998
1014− deleteUser :: Int -> EitherT ServantErr IO ()
999+ deleteUser :: Int -> ExceptT ServantErr IO ()
1015 deleteUser = error "..."
1000 deleteUser = error "..."
1016```
1001```
1017
1002
@@ -1055,23 +1040,23 @@ type UsersAPI =
1055usersServer :: Server UsersAPI
1040usersServer :: Server UsersAPI
1056usersServer = getUsers :<|> newUser :<|> userOperations
1041usersServer = getUsers :<|> newUser :<|> userOperations
1057
1042
1058− where getUsers :: EitherT ServantErr IO [User]
1043+ where getUsers :: ExceptT ServantErr IO [User]
1059 getUsers = error "..."
1044 getUsers = error "..."
1060
1045
1061− newUser :: User -> EitherT ServantErr IO ()
1046+ newUser :: User -> ExceptT ServantErr IO ()
1062 newUser = error "..."
1047 newUser = error "..."
1063
1048
1064 userOperations userid =
1049 userOperations userid =
1065 viewUser userid :<|> updateUser userid :<|> deleteUser userid
1050 viewUser userid :<|> updateUser userid :<|> deleteUser userid
1066
1051
1067 where
1052 where
1068− viewUser :: Int -> EitherT ServantErr IO User
1053+ viewUser :: Int -> ExceptT ServantErr IO User
1069 viewUser = error "..."
1054 viewUser = error "..."
1070
1055
1071− updateUser :: Int -> User -> EitherT ServantErr IO ()
1056+ updateUser :: Int -> User -> ExceptT ServantErr IO ()
1072 updateUser = error "..."
1057 updateUser = error "..."
1073
1058
1074− deleteUser :: Int -> EitherT ServantErr IO ()
1059+ deleteUser :: Int -> ExceptT ServantErr IO ()
1075 deleteUser = error "..."
1060 deleteUser = error "..."
1076```
1061```
1077
1062
@@ -1090,23 +1075,23 @@ data Product = Product { productId :: Int }
1090productsServer :: Server ProductsAPI
1075productsServer :: Server ProductsAPI
1091productsServer = getProducts :<|> newProduct :<|> productOperations
1076productsServer = getProducts :<|> newProduct :<|> productOperations
1092
1077
1093− where getProducts :: EitherT ServantErr IO [Product]
1078+ where getProducts :: ExceptT ServantErr IO [Product]
1094 getProducts = error "..."
1079 getProducts = error "..."
1095
1080
1096− newProduct :: Product -> EitherT ServantErr IO ()
1081+ newProduct :: Product -> ExceptT ServantErr IO ()
1097 newProduct = error "..."
1082 newProduct = error "..."
1098
1083
1099 productOperations productid =
1084 productOperations productid =
1100 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1085 viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
1101
1086
1102 where
1087 where
1103− viewProduct :: Int -> EitherT ServantErr IO Product
1088+ viewProduct :: Int -> ExceptT ServantErr IO Product
1104 viewProduct = error "..."
1089 viewProduct = error "..."
1105
1090
1106− updateProduct :: Int -> Product -> EitherT ServantErr IO ()
1091+ updateProduct :: Int -> Product -> ExceptT ServantErr IO ()
1107 updateProduct = error "..."
1092 updateProduct = error "..."
1108
1093
1109− deleteProduct :: Int -> EitherT ServantErr IO ()
1094+ deleteProduct :: Int -> ExceptT ServantErr IO ()
1110 deleteProduct = error "..."
1095 deleteProduct = error "..."
1111```
1096```
1112
1097
@@ -1134,11 +1119,11 @@ type APIFor a i =
1134
1119
1135-- Build the appropriate 'Server'
1120-- Build the appropriate 'Server'
1136-- given the handlers of the right type.
1121-- given the handlers of the right type.
1137−serverFor :: EitherT ServantErr IO [a] -- handler for listing of 'a's
1122+serverFor :: ExceptT ServantErr IO [a] -- handler for listing of 'a's
1138− -> (a -> EitherT ServantErr IO ()) -- handler for adding an 'a'
1123+ -> (a -> ExceptT ServantErr IO ()) -- handler for adding an 'a'
1139− -> (i -> EitherT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i'
1124+ -> (i -> ExceptT 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
1125+ -> (i -> a -> ExceptT ServantErr IO ()) -- updating an 'a' with given id
1141− -> (i -> EitherT ServantErr IO ()) -- deleting an 'a' given its id
1126+ -> (i -> ExceptT ServantErr IO ()) -- deleting an 'a' given its id
1142 -> Server (APIFor a i)
1127 -> Server (APIFor a i)
1143serverFor = error "..."
1128serverFor = error "..."
1144-- implementation left as an exercise. contact us on IRC
1129-- implementation left as an exercise. contact us on IRC
@@ -1148,10 +1133,10 @@ serverFor = error "..."
1148Using another monad for your handlers
1133Using another monad for your handlers
1149=====================================
1134=====================================
1150
1135
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.
1152
1137
1153``` haskell ignore
1138``` haskell ignore
1154−type Server api = ServerT api (EitherT ServantErr IO)
1139+type Server api = ServerT api (ExceptT ServantErr IO)
1155```
1140```
1156
1141
1157`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`.
1142`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}
1173```
1158```
1174(`Nat` comes from "natural transformation", in case you're wondering.)
1159(`Nat` comes from "natural transformation", in case you're wondering.)
1175
1160
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
1177ServantErr IO`, say the `Reader String` monad, the first thing you have to
1162ServantErr IO`, say the `Reader String` monad, the first thing you have to
1178prepare is a function:
1163prepare is a function:
1179
1164
1180``` haskell ignore
1165``` haskell ignore
1181−readerToEither :: Reader String :~> EitherT ServantErr IO
1166+readerToEither :: Reader String :~> ExceptT ServantErr IO
1182```
1167```
1183
1168
1184Let's start with `readerToEither'`. We obviously have to run the `Reader`
1169Let's start with `readerToEither'`. We obviously have to run the `Reader`
1185computation by supplying it with a `String`, like `"hi"`. We get an `a` out
1170computation 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
1187that function with the `Nat` constructor to make it have the fancier type.
1172that function with the `Nat` constructor to make it have the fancier type.
1188
1173
1189``` haskell
1174``` haskell
1190−readerToEither' :: forall a. Reader String a -> EitherT ServantErr IO a
1175+readerToEither' :: forall a. Reader String a -> ExceptT ServantErr IO a
1191readerToEither' r = return (runReader r "hi")
1176readerToEither' r = return (runReader r "hi")
1192
1177
1193−readerToEither :: Reader String :~> EitherT ServantErr IO
1178+readerToEither :: Reader String :~> ExceptT ServantErr IO
1194readerToEither = Nat readerToEither'
1179readerToEither = Nat readerToEither'
1195```
1180```
1196
1181
@@ -1214,7 +1199,7 @@ readerServerT = a :<|> b
1214```
1199```
1215
1200
1216We unfortunately can't use `readerServerT` as an argument of `serve`, because
1201We 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
1218ServantErr IO`. But there's a simple solution to this.
1203ServantErr IO`. But there's a simple solution to this.
1219
1204
1220Enter `enter`
1205Enter `enter`
@@ -1233,7 +1218,7 @@ readerServer :: Server ReaderAPI
1233readerServer = enter readerToEither readerServerT
1218readerServer = enter readerToEither readerServerT
1234
1219
1235app4 :: Application
1220app4 :: Application
1236−app4 = serve readerAPI readerServer
1221+app4 = serve readerAPI EmptyConfig readerServer
1237```
1222```
1238
1223
1239And we can indeed see this webservice in action by running `dist/build/tutorial/tutorial 7`.
1224And 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
18 , Client
18 , Client
19 -- , Docs
19 -- , Docs
20 -- , Javascript
20 -- , Javascript
21− -- , Server
21+ , Server
22 -- other-modules:
22 -- other-modules:
23 -- other-extensions:
23 -- other-extensions:
24 build-depends: base == 4.*
24 build-depends: base == 4.*
@@ -51,3 +51,4 @@ library
51 ghc-options: -Wall -Werror -pgmL markdown-unlit
51 ghc-options: -Wall -Werror -pgmL markdown-unlit
52 -- to silence aeson-0.10 warnings:
52 -- to silence aeson-0.10 warnings:
53 ghc-options: -fno-warn-missing-methods
53 ghc-options: -fno-warn-missing-methods
54+ ghc-options: -fno-warn-name-shadowing