haskell-servant / haskell-servant/servant

How to handle impure exceptions in servant-server?

Open
#1,022 6 comments 3 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.