Introduce a Handler alias for ExceptT ServantErr IO

Fixes #434
This commit is contained in:
Luke Cycon 2016-04-07 14:34:23 -07:00
parent b8422e80b2
commit 21546991af
14 changed files with 115 additions and 117 deletions

View file

@ -44,7 +44,6 @@ You can use this combinator to protect an API as follows:
module Authentication where module Authentication where
import Control.Monad.Trans.Except (ExceptT)
import Data.Aeson (ToJSON) import Data.Aeson (ToJSON)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Map (Map, fromList) import Data.Map (Map, fromList)
@ -66,7 +65,7 @@ import Servant.Server (BasicAuthCheck (BasicAuthCheck),
), ),
Context ((:.), EmptyContext), Context ((:.), EmptyContext),
err401, err403, errBody, Server, err401, err403, errBody, Server,
ServantErr, serveWithContext) serveWithContext, Handler)
import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData, import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData,
mkAuthHandler) mkAuthHandler)
import Servant.Server.Experimental.Auth() import Servant.Server.Experimental.Auth()
@ -118,22 +117,22 @@ or dictated the structure of a response (e.g. a `Capture` param is pulled from
the request path). Now consider an API resource protected by basic the request path). Now consider an API resource protected by basic
authentication. Once the required `WWW-Authenticate` header is checked, we need authentication. Once the required `WWW-Authenticate` header is checked, we need
to verify the username and password. But how? One solution would be to force an to verify the username and password. But how? One solution would be to force an
API author to provide a function of type `BasicAuthData -> ExceptT ServantErr IO User` API author to provide a function of type `BasicAuthData -> Handler User`
and servant should use this function to authenticate a request. Unfortunately and servant should use this function to authenticate a request. Unfortunately
this didn't work prior to `0.5` because all of servant's machinery was this didn't work prior to `0.5` because all of servant's machinery was
engineered around the idea that each combinator can extract information from engineered around the idea that each combinator can extract information from
only the request. We cannot extract the function only the request. We cannot extract the function
`BasicAuthData -> ExceptT ServantErr IO User` from a request! Are we doomed? `BasicAuthData -> Handler User` from a request! Are we doomed?
Servant `0.5` introduced `Context` to handle this. The type machinery is beyond Servant `0.5` introduced `Context` to handle this. The type machinery is beyond
the scope of this tutorial, but the idea is simple: provide some data to the the scope of this tutorial, but the idea is simple: provide some data to the
`serve` function, and that data is propagated to the functions that handle each `serve` function, and that data is propagated to the functions that handle each
combinator. Using `Context`, we can supply a function of type combinator. Using `Context`, we can supply a function of type
`BasicAuthData -> ExceptT ServantErr IO User` to the `BasicAuth` combinator `BasicAuthData -> Handler User` to the `BasicAuth` combinator
handler. This will allow the handler to check authentication and return a `User` handler. This will allow the handler to check authentication and return a `User`
to downstream handlers if successful. to downstream handlers if successful.
In practice we wrap `BasicAuthData -> ExceptT ServantErr IO` into a slightly In practice we wrap `BasicAuthData -> Handler` into a slightly
different function to better capture the semantics of basic authentication: different function to better capture the semantics of basic authentication:
``` haskell ignore ``` haskell ignore
@ -247,7 +246,7 @@ your feedback!
### What is Generalized Authentication? ### What is Generalized Authentication?
**TL;DR**: you throw a tagged `AuthProtect` combinator in front of the endpoints **TL;DR**: you throw a tagged `AuthProtect` combinator in front of the endpoints
you want protected and then supply a function `Request -> ExceptT IO ServantErr user` you want protected and then supply a function `Request -> Handler user`
which we run anytime a request matches a protected endpoint. It precisely solves which we run anytime a request matches a protected endpoint. It precisely solves
the "I just need to protect these endpoints with a function that does some the "I just need to protect these endpoints with a function that does some
complicated business logic" and nothing more. Behind the scenes we use a type complicated business logic" and nothing more. Behind the scenes we use a type
@ -273,19 +272,19 @@ database = fromList [ ("key1", Account "Anne Briggs")
-- | A method that, when given a password, will return a Account. -- | A method that, when given a password, will return a Account.
-- This is our bespoke (and bad) authentication logic. -- This is our bespoke (and bad) authentication logic.
lookupAccount :: ByteString -> ExceptT ServantErr IO Account lookupAccount :: ByteString -> Handler Account
lookupAccount key = case Map.lookup key database of lookupAccount key = case Map.lookup key database of
Nothing -> throwError (err403 { errBody = "Invalid Cookie" }) Nothing -> throwError (err403 { errBody = "Invalid Cookie" })
Just usr -> return usr Just usr -> return usr
``` ```
For generalized authentication, servant exposes the `AuthHandler` type, For generalized authentication, servant exposes the `AuthHandler` type,
which is used to wrap the `Request -> ExceptT IO ServantErr user` logic. Let's which is used to wrap the `Request -> Handler user` logic. Let's
create a value of type `AuthHandler Request Account` using the above `lookupAccount` create a value of type `AuthHandler Request Account` using the above `lookupAccount`
method: method:
```haskell ```haskell
-- | The auth handler wraps a function from Request -> ExceptT ServantErr IO Account -- | The auth handler wraps a function from Request -> Handler Account
-- we look for a Cookie and pass the value of the cookie to `lookupAccount`. -- we look for a Cookie and pass the value of the cookie to `lookupAccount`.
authHandler :: AuthHandler Request Account authHandler :: AuthHandler Request Account
authHandler = authHandler =
@ -380,7 +379,7 @@ forward:
2. choose a application-specific data type used by your server when 2. choose a application-specific data type used by your server when
authentication is successful (in our case this was `User`). authentication is successful (in our case this was `User`).
3. Create a value of `AuthHandler Request User` which encapsulates the 3. Create a value of `AuthHandler Request User` which encapsulates the
authentication logic (`Request -> ExceptT IO ServantErr User`). This function authentication logic (`Request -> Handler User`). This function
will be executed everytime a request matches a protected route. will be executed everytime a request matches a protected route.
4. Provide an instance of the `AuthServerData` type family, specifying your 4. Provide an instance of the `AuthServerData` type family, specifying your
application-specific data type returned when authentication is successful (in application-specific data type returned when authentication is successful (in

View file

@ -111,11 +111,11 @@ corresponding API type.
The first thing to know about the `Server` type family is that behind the The first thing to know about the `Server` type family is that behind the
scenes it will drive the routing, letting you focus only on the business scenes it will drive the routing, letting you focus only on the business
logic. The second thing to know is that for each endpoint, your handlers will logic. The second thing to know is that for each endpoint, your handlers will
by default run in the `ExceptT ServantErr IO` monad. This is overridable very by default run in the `Handler` monad. This is overridable very
easily, as explained near the end of this guide. Third thing, the type of the easily, as explained near the end of this guide. Third thing, the type of the
value returned in that monad must be the same as the second argument of the value returned in that monad must be the same as the second argument of the
HTTP method combinator used for the corresponding endpoint. In our case, it HTTP method combinator used for the corresponding endpoint. In our case, it
means we must provide a handler of type `ExceptT ServantErr IO [User]`. Well, means we must provide a handler of type `Handler [User]`. Well,
we have a monad, let's just `return` our list: we have a monad, let's just `return` our list:
``` haskell ``` haskell
@ -269,15 +269,15 @@ server3 = position
:<|> hello :<|> hello
:<|> marketing :<|> marketing
where position :: Int -> Int -> ExceptT ServantErr IO Position where position :: Int -> Int -> Handler Position
position x y = return (Position x y) position x y = return (Position x y)
hello :: Maybe String -> ExceptT ServantErr IO HelloMessage hello :: Maybe String -> Handler HelloMessage
hello mname = return . HelloMessage $ case mname of hello mname = return . HelloMessage $ case mname of
Nothing -> "Hello, anonymous coward" Nothing -> "Hello, anonymous coward"
Just n -> "Hello, " ++ n Just n -> "Hello, " ++ n
marketing :: ClientInfo -> ExceptT ServantErr IO Email marketing :: ClientInfo -> Handler Email
marketing clientinfo = return (emailForClient clientinfo) marketing clientinfo = return (emailForClient clientinfo)
``` ```
@ -307,7 +307,7 @@ $ curl -X POST -d '{"clientName":"Alp Mestanogullari", "clientEmail" : "alp@foo.
For reference, here's a list of some combinators from **servant**: For reference, here's a list of some combinators from **servant**:
> - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `ExceptT ServantErr IO <something>`. > - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `Handler <something>`.
> - `Capture "something" a` becomes an argument of type `a`. > - `Capture "something" a` becomes an argument of type `a`.
> - `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. > - `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.
> - `QueryFlag "something"` gets turned into an argument of type `Bool`. > - `QueryFlag "something"` gets turned into an argument of type `Bool`.
@ -601,11 +601,10 @@ $ curl -H 'Accept: text/html' http://localhost:8081/persons
# or just point your browser to http://localhost:8081/persons # or just point your browser to http://localhost:8081/persons
``` ```
## The `ExceptT ServantErr IO` monad ## The `Handler` monad
At the heart of the handlers is the monad they run in, namely `ExceptT At the heart of the handlers is the monad they run in, namely `ExceptT ServantErr IO`
ServantErr IO` ([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)), which is aliased as `Handler`.
([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
One might wonder: why this monad? The answer is that it is the One might wonder: why this monad? The answer is that it is the
simplest monad with the following properties: simplest monad with the following properties:
@ -621,7 +620,7 @@ Let's recall some definitions.
newtype ExceptT e m a = ExceptT (m (Either e a)) newtype ExceptT e m a = ExceptT (m (Either e a))
``` ```
In short, this means that a handler of type `ExceptT ServantErr IO a` is simply In short, this means that a handler of type `Handler a` is simply
equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO equivalent to a computation of type `IO (Either ServantErr a)`, that is, an IO
action that either returns an error or a result. action that either returns an error or a result.
@ -688,7 +687,7 @@ module. If you want to use these values but add a body or some headers, just
use record update syntax: use record update syntax:
``` haskell ``` haskell
failingHandler :: ExceptT ServantErr IO () failingHandler :: Handler ()
failingHandler = throwError myerr failingHandler = throwError myerr
where myerr :: ServantErr where myerr :: ServantErr
@ -826,11 +825,11 @@ However, you have to be aware that this has an effect on the type of the
corresponding `Server`: corresponding `Server`:
``` haskell ignore ``` haskell ignore
Server UserAPI3 = (Int -> ExceptT ServantErr IO User) Server UserAPI3 = (Int -> Handler User)
:<|> (Int -> ExceptT ServantErr IO ()) :<|> (Int -> Handler ())
Server UserAPI4 = Int -> ( ExceptT ServantErr IO User Server UserAPI4 = Int -> ( Handler User
:<|> ExceptT ServantErr IO () :<|> Handler ()
) )
``` ```
@ -842,10 +841,10 @@ computations in `ExceptT`, with no arguments. In other words:
server8 :: Server UserAPI3 server8 :: Server UserAPI3
server8 = getUser :<|> deleteUser server8 = getUser :<|> deleteUser
where getUser :: Int -> ExceptT ServantErr IO User where getUser :: Int -> Handler User
getUser _userid = error "..." getUser _userid = error "..."
deleteUser :: Int -> ExceptT ServantErr IO () deleteUser :: Int -> Handler ()
deleteUser _userid = error "..." deleteUser _userid = error "..."
-- notice how getUser and deleteUser -- notice how getUser and deleteUser
@ -854,10 +853,10 @@ server8 = getUser :<|> deleteUser
server9 :: Server UserAPI4 server9 :: Server UserAPI4
server9 userid = getUser userid :<|> deleteUser userid server9 userid = getUser userid :<|> deleteUser userid
where getUser :: Int -> ExceptT ServantErr IO User where getUser :: Int -> Handler User
getUser = error "..." getUser = error "..."
deleteUser :: Int -> ExceptT ServantErr IO () deleteUser :: Int -> Handler ()
deleteUser = error "..." deleteUser = error "..."
``` ```
@ -905,23 +904,23 @@ type UsersAPI =
usersServer :: Server UsersAPI usersServer :: Server UsersAPI
usersServer = getUsers :<|> newUser :<|> userOperations usersServer = getUsers :<|> newUser :<|> userOperations
where getUsers :: ExceptT ServantErr IO [User] where getUsers :: Handler [User]
getUsers = error "..." getUsers = error "..."
newUser :: User -> ExceptT ServantErr IO () newUser :: User -> Handler ()
newUser = error "..." newUser = error "..."
userOperations userid = userOperations userid =
viewUser userid :<|> updateUser userid :<|> deleteUser userid viewUser userid :<|> updateUser userid :<|> deleteUser userid
where where
viewUser :: Int -> ExceptT ServantErr IO User viewUser :: Int -> Handler User
viewUser = error "..." viewUser = error "..."
updateUser :: Int -> User -> ExceptT ServantErr IO () updateUser :: Int -> User -> Handler ()
updateUser = error "..." updateUser = error "..."
deleteUser :: Int -> ExceptT ServantErr IO () deleteUser :: Int -> Handler ()
deleteUser = error "..." deleteUser = error "..."
``` ```
@ -940,23 +939,23 @@ data Product = Product { productId :: Int }
productsServer :: Server ProductsAPI productsServer :: Server ProductsAPI
productsServer = getProducts :<|> newProduct :<|> productOperations productsServer = getProducts :<|> newProduct :<|> productOperations
where getProducts :: ExceptT ServantErr IO [Product] where getProducts :: Handler [Product]
getProducts = error "..." getProducts = error "..."
newProduct :: Product -> ExceptT ServantErr IO () newProduct :: Product -> Handler ()
newProduct = error "..." newProduct = error "..."
productOperations productid = productOperations productid =
viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
where where
viewProduct :: Int -> ExceptT ServantErr IO Product viewProduct :: Int -> Handler Product
viewProduct = error "..." viewProduct = error "..."
updateProduct :: Int -> Product -> ExceptT ServantErr IO () updateProduct :: Int -> Product -> Handler ()
updateProduct = error "..." updateProduct = error "..."
deleteProduct :: Int -> ExceptT ServantErr IO () deleteProduct :: Int -> Handler ()
deleteProduct = error "..." deleteProduct = error "..."
``` ```
@ -985,11 +984,11 @@ type APIFor a i =
-- Build the appropriate 'Server' -- Build the appropriate 'Server'
-- given the handlers of the right type. -- given the handlers of the right type.
serverFor :: ExceptT ServantErr IO [a] -- handler for listing of 'a's serverFor :: Handler [a] -- handler for listing of 'a's
-> (a -> ExceptT ServantErr IO ()) -- handler for adding an 'a' -> (a -> Handler ()) -- handler for adding an 'a'
-> (i -> ExceptT ServantErr IO a) -- handler for viewing an 'a' given its identifier of type 'i' -> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
-> (i -> a -> ExceptT ServantErr IO ()) -- updating an 'a' with given id -> (i -> a -> Handler ()) -- updating an 'a' with given id
-> (i -> ExceptT ServantErr IO ()) -- deleting an 'a' given its id -> (i -> Handler ()) -- deleting an 'a' given its id
-> Server (APIFor a i) -> Server (APIFor a i)
serverFor = error "..." serverFor = error "..."
-- implementation left as an exercise. contact us on IRC -- implementation left as an exercise. contact us on IRC
@ -998,12 +997,11 @@ serverFor = error "..."
## Using another monad for your handlers ## Using another monad for your handlers
Remember how `Server` turns combinators for HTTP methods into `ExceptT Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
ServantErr IO`? Well, actually, there's more to that. `Server` is actually a
simple type synonym. simple type synonym.
``` haskell ignore ``` haskell ignore
type Server api = ServerT api (ExceptT ServantErr IO) type Server api = ServerT api Handler
``` ```
`ServerT` is the actual type family that computes the required types for the `ServerT` is the actual type family that computes the required types for the
@ -1036,12 +1034,11 @@ listToMaybeNat = Nat listToMaybe -- from Data.Maybe
(`Nat` comes from "natural transformation", in case you're wondering.) (`Nat` comes from "natural transformation", in case you're wondering.)
So if you want to write handlers using another monad/type than `ExceptT So if you want to write handlers using another monad/type than `Handler`, say the `Reader String` monad, the first thing you have to
ServantErr IO`, say the `Reader String` monad, the first thing you have to
prepare is a function: prepare is a function:
``` haskell ignore ``` haskell ignore
readerToHandler :: Reader String :~> ExceptT ServantErr IO readerToHandler :: Reader String :~> Handler
``` ```
Let's start with `readerToHandler'`. We obviously have to run the `Reader` Let's start with `readerToHandler'`. We obviously have to run the `Reader`
@ -1050,10 +1047,10 @@ from that and can then just `return` it into `ExceptT`. We can then just wrap
that function with the `Nat` constructor to make it have the fancier type. that function with the `Nat` constructor to make it have the fancier type.
``` haskell ``` haskell
readerToHandler' :: forall a. Reader String a -> ExceptT ServantErr IO a readerToHandler' :: forall a. Reader String a -> Handler a
readerToHandler' r = return (runReader r "hi") readerToHandler' r = return (runReader r "hi")
readerToHandler :: Reader String :~> ExceptT ServantErr IO readerToHandler :: Reader String :~> Handler
readerToHandler = Nat readerToHandler' readerToHandler = Nat readerToHandler'
``` ```
@ -1077,8 +1074,7 @@ readerServerT = a :<|> b
``` ```
We unfortunately can't use `readerServerT` as an argument of `serve`, because We unfortunately can't use `readerServerT` as an argument of `serve`, because
`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `ExceptT `serve` wants a `Server ReaderAPI`, i.e., with handlers running in `Handler`. But there's a simple solution to this.
ServantErr IO`. But there's a simple solution to this.
### Enter `enter` ### Enter `enter`

View file

@ -355,7 +355,7 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
_ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
data WrappedApi where data WrappedApi where
WrappedApi :: (HasServer (api :: *) '[], Server api ~ ExceptT ServantErr IO a, WrappedApi :: (HasServer (api :: *) '[], Server api ~ Handler a,
HasClient api, Client api ~ (C.Manager -> BaseUrl -> SCR.ClientM ())) => HasClient api, Client api ~ (C.Manager -> BaseUrl -> SCR.ClientM ())) =>
Proxy api -> WrappedApi Proxy api -> WrappedApi

View file

@ -97,8 +97,8 @@ class HasServer api context => HasMock api context where
-- actually "means" 2 request handlers, of the following types: -- actually "means" 2 request handlers, of the following types:
-- --
-- @ -- @
-- getUser :: ExceptT ServantErr IO User -- getUser :: Handler User
-- getBook :: ExceptT ServantErr IO Book -- getBook :: Handler Book
-- @ -- @
-- --
-- So under the hood, 'mock' uses the 'IO' bit to generate -- So under the hood, 'mock' uses the 'IO' bit to generate

View file

@ -44,7 +44,7 @@ testApi = Proxy
-- There's one handler per endpoint, which, just like in the type -- There's one handler per endpoint, which, just like in the type
-- that represents the API, are glued together using :<|>. -- that represents the API, are glued together using :<|>.
-- --
-- Each handler runs in the 'ExceptT ServantErr IO' monad. -- Each handler runs in the 'Handler' monad.
server :: Server TestApi server :: Server TestApi
server = helloH :<|> postGreetH :<|> deleteGreetH server = helloH :<|> postGreetH :<|> deleteGreetH

View file

@ -17,6 +17,7 @@ module Servant.Server
, -- * Handlers for all standard combinators , -- * Handlers for all standard combinators
HasServer(..) HasServer(..)
, Server , Server
, Handler
-- * Debugging the server layout -- * Debugging the server layout
, layout , layout

View file

@ -12,8 +12,7 @@
module Servant.Server.Experimental.Auth where module Servant.Server.Experimental.Auth where
import Control.Monad.Trans.Except (ExceptT, import Control.Monad.Trans.Except (runExceptT)
runExceptT)
import Data.Proxy (Proxy (Proxy)) import Data.Proxy (Proxy (Proxy))
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import GHC.Generics (Generic) import GHC.Generics (Generic)
@ -28,7 +27,7 @@ import Servant.Server.Internal (HasContextEntry,
import Servant.Server.Internal.Router (Router' (WithRequest)) import Servant.Server.Internal.Router (Router' (WithRequest))
import Servant.Server.Internal.RoutingApplication (RouteResult (FailFatal, Route), import Servant.Server.Internal.RoutingApplication (RouteResult (FailFatal, Route),
addAuthCheck) addAuthCheck)
import Servant.Server.Internal.ServantErr (ServantErr) import Servant.Server.Internal.ServantErr (ServantErr, Handler)
-- * General Auth -- * General Auth
@ -42,11 +41,11 @@ type family AuthServerData a :: *
-- --
-- NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE -- NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
newtype AuthHandler r usr = AuthHandler newtype AuthHandler r usr = AuthHandler
{ unAuthHandler :: r -> ExceptT ServantErr IO usr } { unAuthHandler :: r -> Handler usr }
deriving (Generic, Typeable) deriving (Generic, Typeable)
-- | NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE -- | NOTE: THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE
mkAuthHandler :: (r -> ExceptT ServantErr IO usr) -> AuthHandler r usr mkAuthHandler :: (r -> Handler usr) -> AuthHandler r usr
mkAuthHandler = AuthHandler mkAuthHandler = AuthHandler
-- | Known orphan instance. -- | Known orphan instance.

View file

@ -22,7 +22,6 @@ module Servant.Server.Internal
, module Servant.Server.Internal.ServantErr , module Servant.Server.Internal.ServantErr
) where ) where
import Control.Monad.Trans.Except (ExceptT)
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC8 import qualified Data.ByteString.Char8 as BC8
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
@ -73,7 +72,7 @@ class HasServer layout context where
route :: Proxy layout -> Context context -> Delayed (Server layout) -> Router route :: Proxy layout -> Context context -> Delayed (Server layout) -> Router
type Server layout = ServerT layout (ExceptT ServantErr IO) type Server layout = ServerT layout Handler
-- * Instances -- * Instances
@ -112,7 +111,7 @@ instance (HasServer a context, HasServer b context) => HasServer (a :<|> b) cont
-- > -- >
-- > server :: Server MyApi -- > server :: Server MyApi
-- > server = getBook -- > server = getBook
-- > where getBook :: Text -> ExceptT ServantErr IO Book -- > where getBook :: Text -> Handler Book
-- > getBook isbn = ... -- > getBook isbn = ...
instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout context) instance (KnownSymbol capture, FromHttpApiData a, HasServer sublayout context)
=> HasServer (Capture capture a :> sublayout) context where => HasServer (Capture capture a :> sublayout) context where
@ -157,7 +156,7 @@ acceptCheck proxy accH
methodRouter :: (AllCTRender ctypes a) methodRouter :: (AllCTRender ctypes a)
=> Method -> Proxy ctypes -> Status => Method -> Proxy ctypes -> Status
-> Delayed (ExceptT ServantErr IO a) -> Delayed (Handler a)
-> Router -> Router
methodRouter method proxy status action = leafRouter route' methodRouter method proxy status action = leafRouter route'
where where
@ -171,7 +170,7 @@ methodRouter method proxy status action = leafRouter route'
methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v) methodRouterHeaders :: (GetHeaders (Headers h v), AllCTRender ctypes v)
=> Method -> Proxy ctypes -> Status => Method -> Proxy ctypes -> Status
-> Delayed (ExceptT ServantErr IO (Headers h v)) -> Delayed (Handler (Headers h v))
-> Router -> Router
methodRouterHeaders method proxy status action = leafRouter route' methodRouterHeaders method proxy status action = leafRouter route'
where where
@ -223,7 +222,7 @@ instance OVERLAPPING_
-- > -- >
-- > server :: Server MyApi -- > server :: Server MyApi
-- > server = viewReferer -- > server = viewReferer
-- > where viewReferer :: Referer -> ExceptT ServantErr IO referer -- > where viewReferer :: Referer -> Handler referer
-- > viewReferer referer = return referer -- > viewReferer referer = return referer
instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context) instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
=> HasServer (Header sym a :> sublayout) context where => HasServer (Header sym a :> sublayout) context where
@ -254,7 +253,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
-- > -- >
-- > server :: Server MyApi -- > server :: Server MyApi
-- > server = getBooksBy -- > server = getBooksBy
-- > where getBooksBy :: Maybe Text -> ExceptT ServantErr IO [Book] -- > where getBooksBy :: Maybe Text -> Handler [Book]
-- > getBooksBy Nothing = ...return all books... -- > getBooksBy Nothing = ...return all books...
-- > getBooksBy (Just author) = ...return books by the given author... -- > getBooksBy (Just author) = ...return books by the given author...
instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context) instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
@ -291,7 +290,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
-- > -- >
-- > server :: Server MyApi -- > server :: Server MyApi
-- > server = getBooksBy -- > server = getBooksBy
-- > where getBooksBy :: [Text] -> ExceptT ServantErr IO [Book] -- > where getBooksBy :: [Text] -> Handler [Book]
-- > getBooksBy authors = ...return all books by these authors... -- > getBooksBy authors = ...return all books by these authors...
instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context) instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
=> HasServer (QueryParams sym a :> sublayout) context where => HasServer (QueryParams sym a :> sublayout) context where
@ -322,7 +321,7 @@ instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout context)
-- > -- >
-- > server :: Server MyApi -- > server :: Server MyApi
-- > server = getBooks -- > server = getBooks
-- > where getBooks :: Bool -> ExceptT ServantErr IO [Book] -- > where getBooks :: Bool -> Handler [Book]
-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument... -- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
instance (KnownSymbol sym, HasServer sublayout context) instance (KnownSymbol sym, HasServer sublayout context)
=> HasServer (QueryFlag sym :> sublayout) context where => HasServer (QueryFlag sym :> sublayout) context where
@ -379,7 +378,7 @@ instance HasServer Raw context where
-- > -- >
-- > server :: Server MyApi -- > server :: Server MyApi
-- > server = postBook -- > server = postBook
-- > where postBook :: Book -> ExceptT ServantErr IO Book -- > where postBook :: Book -> Handler Book
-- > postBook book = ...insert into your db... -- > postBook book = ...insert into your db...
instance ( AllCTUnrender list a, HasServer sublayout context instance ( AllCTUnrender list a, HasServer sublayout context
) => HasServer (ReqBody list a :> sublayout) context where ) => HasServer (ReqBody list a :> sublayout) context where

View file

@ -8,7 +8,7 @@
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
module Servant.Server.Internal.RoutingApplication where module Servant.Server.Internal.RoutingApplication where
import Control.Monad.Trans.Except (ExceptT, runExceptT) import Control.Monad.Trans.Except (runExceptT)
import Network.Wai (Application, Request, import Network.Wai (Application, Request,
Response, ResponseReceived) Response, ResponseReceived)
import Prelude () import Prelude ()
@ -222,7 +222,7 @@ runDelayed Delayed{..} =
-- Takes a continuation that lets us send a response. -- Takes a continuation that lets us send a response.
-- Also takes a continuation for how to turn the -- Also takes a continuation for how to turn the
-- result of the delayed server into a response. -- result of the delayed server into a response.
runAction :: Delayed (ExceptT ServantErr IO a) runAction :: Delayed (Handler a)
-> (RouteResult Response -> IO r) -> (RouteResult Response -> IO r)
-> (a -> RouteResult Response) -> (a -> RouteResult Response)
-> IO r -> IO r

View file

@ -4,6 +4,7 @@
module Servant.Server.Internal.ServantErr where module Servant.Server.Internal.ServantErr where
import Control.Exception (Exception) import Control.Exception (Exception)
import Control.Monad.Trans.Except (ExceptT)
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Lazy as LBS
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
@ -18,6 +19,8 @@ data ServantErr = ServantErr { errHTTPCode :: Int
instance Exception ServantErr instance Exception ServantErr
type Handler = ExceptT ServantErr IO
responseServantErr :: ServantErr -> Response responseServantErr :: ServantErr -> Response
responseServantErr ServantErr{..} = responseLBS status errHeaders errBody responseServantErr ServantErr{..} = responseLBS status errHeaders errBody
where where
@ -27,7 +30,7 @@ responseServantErr ServantErr{..} = responseLBS status errHeaders errBody
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err300 { errBody = "I can't choose." } -- > failingHandler = throwErr $ err300 { errBody = "I can't choose." }
-- --
err300 :: ServantErr err300 :: ServantErr
@ -41,7 +44,7 @@ err300 = ServantErr { errHTTPCode = 300
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err301 -- > failingHandler = throwErr err301
-- --
err301 :: ServantErr err301 :: ServantErr
@ -55,7 +58,7 @@ err301 = ServantErr { errHTTPCode = 301
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err302 -- > failingHandler = throwErr err302
-- --
err302 :: ServantErr err302 :: ServantErr
@ -69,7 +72,7 @@ err302 = ServantErr { errHTTPCode = 302
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err303 -- > failingHandler = throwErr err303
-- --
err303 :: ServantErr err303 :: ServantErr
@ -83,7 +86,7 @@ err303 = ServantErr { errHTTPCode = 303
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err304 -- > failingHandler = throwErr err304
-- --
err304 :: ServantErr err304 :: ServantErr
@ -97,7 +100,7 @@ err304 = ServantErr { errHTTPCode = 304
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err305 -- > failingHandler = throwErr err305
-- --
err305 :: ServantErr err305 :: ServantErr
@ -111,7 +114,7 @@ err305 = ServantErr { errHTTPCode = 305
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err307 -- > failingHandler = throwErr err307
-- --
err307 :: ServantErr err307 :: ServantErr
@ -125,7 +128,7 @@ err307 = ServantErr { errHTTPCode = 307
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err400 { errBody = "Your request makes no sense to me." } -- > failingHandler = throwErr $ err400 { errBody = "Your request makes no sense to me." }
-- --
err400 :: ServantErr err400 :: ServantErr
@ -139,7 +142,7 @@ err400 = ServantErr { errHTTPCode = 400
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err401 { errBody = "Your credentials are invalid." } -- > failingHandler = throwErr $ err401 { errBody = "Your credentials are invalid." }
-- --
err401 :: ServantErr err401 :: ServantErr
@ -153,7 +156,7 @@ err401 = ServantErr { errHTTPCode = 401
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err402 { errBody = "You have 0 credits. Please give me $$$." } -- > failingHandler = throwErr $ err402 { errBody = "You have 0 credits. Please give me $$$." }
-- --
err402 :: ServantErr err402 :: ServantErr
@ -167,7 +170,7 @@ err402 = ServantErr { errHTTPCode = 402
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err403 { errBody = "Please login first." } -- > failingHandler = throwErr $ err403 { errBody = "Please login first." }
-- --
err403 :: ServantErr err403 :: ServantErr
@ -181,7 +184,7 @@ err403 = ServantErr { errHTTPCode = 403
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err404 { errBody = "(╯°□°)╯︵ ┻━┻)." } -- > failingHandler = throwErr $ err404 { errBody = "(╯°□°)╯︵ ┻━┻)." }
-- --
err404 :: ServantErr err404 :: ServantErr
@ -195,7 +198,7 @@ err404 = ServantErr { errHTTPCode = 404
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err405 { errBody = "Your account privileges does not allow for this. Please pay $$$." } -- > failingHandler = throwErr $ err405 { errBody = "Your account privileges does not allow for this. Please pay $$$." }
-- --
err405 :: ServantErr err405 :: ServantErr
@ -209,7 +212,7 @@ err405 = ServantErr { errHTTPCode = 405
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err406 -- > failingHandler = throwErr err406
-- --
err406 :: ServantErr err406 :: ServantErr
@ -223,7 +226,7 @@ err406 = ServantErr { errHTTPCode = 406
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err407 -- > failingHandler = throwErr err407
-- --
err407 :: ServantErr err407 :: ServantErr
@ -237,7 +240,7 @@ err407 = ServantErr { errHTTPCode = 407
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err409 { errBody = "Transaction conflicts with 59879cb56c7c159231eeacdd503d755f7e835f74" } -- > failingHandler = throwErr $ err409 { errBody = "Transaction conflicts with 59879cb56c7c159231eeacdd503d755f7e835f74" }
-- --
err409 :: ServantErr err409 :: ServantErr
@ -251,7 +254,7 @@ err409 = ServantErr { errHTTPCode = 409
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err410 { errBody = "I know it was here at some point, but.. I blame bad luck." } -- > failingHandler = throwErr $ err410 { errBody = "I know it was here at some point, but.. I blame bad luck." }
-- --
err410 :: ServantErr err410 :: ServantErr
@ -265,7 +268,7 @@ err410 = ServantErr { errHTTPCode = 410
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr err411 -- > failingHandler = throwErr err411
-- --
err411 :: ServantErr err411 :: ServantErr
@ -279,7 +282,7 @@ err411 = ServantErr { errHTTPCode = 411
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err412 { errBody = "Precondition fail: x < 42 && y > 57" } -- > failingHandler = throwErr $ err412 { errBody = "Precondition fail: x < 42 && y > 57" }
-- --
err412 :: ServantErr err412 :: ServantErr
@ -293,7 +296,7 @@ err412 = ServantErr { errHTTPCode = 412
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err413 { errBody = "Request exceeded 64k." } -- > failingHandler = throwErr $ err413 { errBody = "Request exceeded 64k." }
-- --
err413 :: ServantErr err413 :: ServantErr
@ -307,7 +310,7 @@ err413 = ServantErr { errHTTPCode = 413
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err414 { errBody = "Maximum length is 64." } -- > failingHandler = throwErr $ err414 { errBody = "Maximum length is 64." }
-- --
err414 :: ServantErr err414 :: ServantErr
@ -321,7 +324,7 @@ err414 = ServantErr { errHTTPCode = 414
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err415 { errBody = "Supported media types: gif, png" } -- > failingHandler = throwErr $ err415 { errBody = "Supported media types: gif, png" }
-- --
err415 :: ServantErr err415 :: ServantErr
@ -335,7 +338,7 @@ err415 = ServantErr { errHTTPCode = 415
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err416 { errBody = "Valid range is [0, 424242]." } -- > failingHandler = throwErr $ err416 { errBody = "Valid range is [0, 424242]." }
-- --
err416 :: ServantErr err416 :: ServantErr
@ -349,7 +352,7 @@ err416 = ServantErr { errHTTPCode = 416
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err417 { errBody = "I found a quux in the request. This isn't going to work." } -- > failingHandler = throwErr $ err417 { errBody = "I found a quux in the request. This isn't going to work." }
-- --
err417 :: ServantErr err417 :: ServantErr
@ -363,7 +366,7 @@ err417 = ServantErr { errHTTPCode = 417
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err500 { errBody = "Exception in module A.B.C:55. Have a great day!" } -- > failingHandler = throwErr $ err500 { errBody = "Exception in module A.B.C:55. Have a great day!" }
-- --
err500 :: ServantErr err500 :: ServantErr
@ -377,7 +380,7 @@ err500 = ServantErr { errHTTPCode = 500
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err501 { errBody = "/v1/foo is not supported with quux in the request." } -- > failingHandler = throwErr $ err501 { errBody = "/v1/foo is not supported with quux in the request." }
-- --
err501 :: ServantErr err501 :: ServantErr
@ -391,7 +394,7 @@ err501 = ServantErr { errHTTPCode = 501
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err502 { errBody = "Tried gateway foo, bar, and baz. None responded." } -- > failingHandler = throwErr $ err502 { errBody = "Tried gateway foo, bar, and baz. None responded." }
-- --
err502 :: ServantErr err502 :: ServantErr
@ -405,7 +408,7 @@ err502 = ServantErr { errHTTPCode = 502
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err503 { errBody = "We're rewriting in PHP." } -- > failingHandler = throwErr $ err503 { errBody = "We're rewriting in PHP." }
-- --
err503 :: ServantErr err503 :: ServantErr
@ -419,7 +422,7 @@ err503 = ServantErr { errHTTPCode = 503
-- --
-- Example: -- Example:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err504 { errBody = "Backend foobar did not respond in 5 seconds." } -- > failingHandler = throwErr $ err504 { errBody = "Backend foobar did not respond in 5 seconds." }
-- --
err504 :: ServantErr err504 :: ServantErr
@ -433,7 +436,7 @@ err504 = ServantErr { errHTTPCode = 504
-- --
-- Example usage: -- Example usage:
-- --
-- > failingHandler :: ExceptT ServantErr IO () -- > failingHandler :: Handler ()
-- > failingHandler = throwErr $ err505 { errBody = "I support HTTP/4.0 only." } -- > failingHandler = throwErr $ err505 { errBody = "I support HTTP/4.0 only." }
-- --
err505 :: ServantErr err505 :: ServantErr

View file

@ -34,7 +34,7 @@ combinedAPI = Proxy
readerServer' :: ServerT ReaderAPI (Reader String) readerServer' :: ServerT ReaderAPI (Reader String)
readerServer' = return 1797 :<|> ask readerServer' = return 1797 :<|> ask
fReader :: Reader String :~> ExceptT ServantErr IO fReader :: Reader String :~> Handler
fReader = generalizeNat C.. (runReaderTNat "hi") fReader = generalizeNat C.. (runReaderTNat "hi")
readerServer :: Server ReaderAPI readerServer :: Server ReaderAPI

View file

@ -9,7 +9,7 @@
module Servant.Server.StreamingSpec where module Servant.Server.StreamingSpec where
import Control.Concurrent import Control.Concurrent
import Control.Exception import Control.Exception hiding (Handler)
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import qualified Data.ByteString as Strict import qualified Data.ByteString as Strict
@ -66,7 +66,7 @@ spec = do
-- - receives the first chunk -- - receives the first chunk
-- - notifies serverReceivedFirstChunk -- - notifies serverReceivedFirstChunk
-- - receives the rest of the request -- - receives the rest of the request
let handler :: Lazy.ByteString -> ExceptT ServantErr IO NoContent let handler :: Lazy.ByteString -> Handler NoContent
handler input = liftIO $ do handler input = liftIO $ do
let prefix = Lazy.take 3 input let prefix = Lazy.take 3 input
prefix `shouldBe` "foo" prefix `shouldBe` "foo"

View file

@ -25,7 +25,7 @@ spec = do
type OneEntryAPI = type OneEntryAPI =
ExtractFromContext :> Get '[JSON] String ExtractFromContext :> Get '[JSON] String
testServer :: String -> ExceptT ServantErr IO String testServer :: String -> Handler String
testServer s = return s testServer s = return s
oneEntryApp :: Application oneEntryApp :: Application

View file

@ -14,7 +14,7 @@
module Servant.ServerSpec where module Servant.ServerSpec where
import Control.Monad (forM_, when, unless) import Control.Monad (forM_, when, unless)
import Control.Monad.Trans.Except (ExceptT, throwE) import Control.Monad.Trans.Except (throwE)
import Data.Aeson (FromJSON, ToJSON, decode', encode) import Data.Aeson (FromJSON, ToJSON, decode', encode)
import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString.Base64 as Base64
import Data.ByteString.Conversion () import Data.ByteString.Conversion ()
@ -48,8 +48,9 @@ import Servant.API ((:<|>) (..), (:>), AuthProtect,
Raw, RemoteHost, ReqBody, Raw, RemoteHost, ReqBody,
StdMethod (..), Verb, addHeader) StdMethod (..), Verb, addHeader)
import Servant.API.Internal.Test.ComprehensiveAPI import Servant.API.Internal.Test.ComprehensiveAPI
import Servant.Server (ServantErr (..), Server, err401, err403, err404, import Servant.Server (ServantErr (..), Server, Handler, err401, err403,
serve, serveWithContext, Context((:.), EmptyContext)) err404, serve, serveWithContext,
Context((:.), EmptyContext))
import Test.Hspec (Spec, context, describe, it, import Test.Hspec (Spec, context, describe, it,
shouldBe, shouldContain) shouldBe, shouldContain)
import qualified Test.Hspec.Wai as THW import qualified Test.Hspec.Wai as THW
@ -180,7 +181,7 @@ verbSpec = describe "Servant.API.Verb" $ do
type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
captureApi :: Proxy CaptureApi captureApi :: Proxy CaptureApi
captureApi = Proxy captureApi = Proxy
captureServer :: Integer -> ExceptT ServantErr IO Animal captureServer :: Integer -> Handler Animal
captureServer legs = case legs of captureServer legs = case legs of
4 -> return jerry 4 -> return jerry
2 -> return tweety 2 -> return tweety
@ -336,11 +337,11 @@ headerApi = Proxy
headerSpec :: Spec headerSpec :: Spec
headerSpec = describe "Servant.API.Header" $ do headerSpec = describe "Servant.API.Header" $ do
let expectsInt :: Maybe Int -> ExceptT ServantErr IO () let expectsInt :: Maybe Int -> Handler ()
expectsInt (Just x) = when (x /= 5) $ error "Expected 5" expectsInt (Just x) = when (x /= 5) $ error "Expected 5"
expectsInt Nothing = error "Expected an int" expectsInt Nothing = error "Expected an int"
let expectsString :: Maybe String -> ExceptT ServantErr IO () let expectsString :: Maybe String -> Handler ()
expectsString (Just x) = when (x /= "more from you") $ error "Expected more from you" expectsString (Just x) = when (x /= "more from you") $ error "Expected more from you"
expectsString Nothing = error "Expected a string" expectsString Nothing = error "Expected a string"