haskell-servant / haskell-servant/servant
How to handle impure exceptions in servant-server?
- Dominant language
- Haskell
- Stars
- 2k
- Forks
- 427
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 5
Description
Hi all. First, I would like to say thanks for the new `hoistServer`. I've recently converted a servant app to use a custom monad, and `hoistServer` is much more straightforward to use than the old `enter` IMO. However, I have run into one problem. I am wanting to use `safe-exceptions` to catch all exceptions, log them, and produce a 500 error with a formatted response. Here's a small example:
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
module Lib
( startApp
, app
) where
import Control.DeepSeq (NFData)
import Control.Exception.Safe (handleAny)
import Data.Aeson
import Data.Aeson.TH
import Data.String.Conversions (cs)
import GHC.Generics (Generic)
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
data User = User
{ userId :: Int
, userFirstName :: String
, userLastName :: String
} deriving (Eq, Show, Generic, NFData)
$(deriveJSON defaultOptions ''User)
type API = "users" :> Get '[JSON] [User]
startApp :: IO ()
startApp = run 8080 app
app :: Application
app = serve api $ hoistServer api nt server
nt :: Handler a -> Handler a
nt = handleAny throwEx
where
throwEx e = throwError $ err500 {errBody = "The following server exception occured: " <> (cs $ show e)}
api :: Proxy API
api = Proxy
server :: Server API
server = return users
users :: [User]
users = [ User 1 "Isaac" "Newton"
, User 2 "Albert" "Einstein"
]
```
This works just fine. However, if I try to use `handleAnyDeep` to try to catch impure exceptions, like this:
```haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
module Lib
( startApp
, app
) where
import Control.DeepSeq (NFData)
import Control.Exception.Safe (handleAnyDeep)
import Data.Aeson
import Data.Aeson.TH
import Data.String.Conversions (cs)
import GHC.Generics (Generic)
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
data User = User
{ userId :: Int
, userFirstName :: String
, userLastName :: String
} deriving (Eq, Show, Generic, NFData)
$(deriveJSON defaultOptions ''User)
type API = "users" :> Get '[JSON] [User]
startApp :: IO ()
startApp = run 8080 app
app :: Application
app = serve api $ hoistServer api nt server
nt :: NFData a => Handler a -> Handler a
nt = handleAnyDeep throwEx
where
throwEx e = throwError $ err500 {errBody = "The following server exception occured: " <> (cs $ show e)}
api :: Proxy API
api = Proxy
server :: Server API
server = return users
users :: [User]
users = [ User 1 "Isaac" "Newton"
, User 2 "Albert" "Einstein"
]
```
I get this:
```
src\Lib.hs:37:35: error:
* No instance for (NFData x) arising from a use of `nt'
Possible fix:
add (NFData x) to the context of
a type expected by the context:
forall x. Handler x -> Handler x
* In the second argument of `hoistServer', namely `nt'
In the second argument of `($)', namely `hoistServer api nt server'
In the expression: serve api $ hoistServer api nt server
|
37 | app = serve api $ hoistServer api nt server
| ^^
```
Is it possible (or even advisable) to do what I want?
Contributor guide
Assessment
This issue has not been assessed yet.