0

I would like for my default handler to be able to catch all of the exceptions that my App throws but in order for this to happen I need to manually call raise after manually adding some exception catching around my IO code.

below is an example minimal server:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Lib
    ( someFunc
    ) where

import Web.Scotty.Trans
import Control.Monad.Trans
import Control.Monad.Reader
import Control.Monad.Catch
import Control.Monad.Except
import Data.Text.Lazy as TL

data AppEnv = AppEnv
  { appStuff :: String
  } 

newtype App a = App 
  { unApp :: ReaderT AppEnv IO a 
  } deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv, MonadThrow)

someFunc :: IO ()
someFunc = do
  let run a  = runReaderT (unApp $ App a) (AppEnv "APPY STUFF")
  scottyT 8080 run $ do

    defaultHandler $ \(e :: TL.Text) -> do
      liftIO $ print "HERE"
      liftIO $ print $ showError e
      html $ "Something Went Seriously Wrong"

    get "/" $ do
      (r :: (Either TL.Text String)) <- liftIO $ runExceptT $ do
        (uId) <- lift $ readFile "./helloworld.txt"
        return $ ("hello") 
      liftIO $ print r
      case r of
        Left l -> raise l
        Right s -> (html "hello world")

    get "/catch-this" $ do
      error "Catch Me"
      (html "hello world")

    notFound $ do
     html "That is not a valid route"

I would like to be able to catch all of my uncaught exceptions in my default handler however this is not the default behavior of scotty that only happens if you call raise. I could wrap all of my ActionM code blocks in ExceptT however this seems like a messy/mechanical way of solving this problem. I mostly want to do this for logging purposes so I can report out to Sentry or Log to a file and this would make it much more convenient.

TylerH
  • 20,799
  • 66
  • 75
  • 101
emg184
  • 850
  • 8
  • 19

1 Answers1

0

I figured I'd throw this in there as I recently was looking for this same solution again. It's unfortunate but I was never able to get the behavior that I wanted out of scotty.

Fortunately since scotty is just a nice library to create WAI application's you can get a nice work around using the Settings type from warp and the Options type from scotty.

Below is an example of how you can approach this:

{-# LANGUAGE OverloadedStrings #-}

module Lib
    ( someFunc
    ) where

import Web.Scotty.Trans
import Data.Text
import qualified Data.Text.Lazy as TL
import Control.Monad.IO.Class
import Control.Exception
import Network.HTTP.Types
import System.IO.Error
import Network.Wai.Handler.Warp
import Network.Wai

myOpts :: Options 
myOpts = Options 1 mySettings 

mySettings :: Settings 
mySettings = setOnExceptionResponse myHandler $ setPort 3002 $  defaultSettings 

myHandler :: SomeException -> Response
myHandler se = responseLBS status500 [] "HERE WE ARE"

someFunc :: IO ()
someFunc = do
  scottyOptsT myOpts id routes

myExceptions :: (MonadIO m) => TL.Text -> ActionT TL.Text m ()
myExceptions t = do
  liftIO $ print t
  html "error"

routes :: (MonadIO m) => ScottyT TL.Text m ()
routes = do
  defaultHandler $ \str -> do
   liftAndCatchIO $ print str
   status status500
   json ("welp you thought"::Text)
  get "/:here" $ do
    liftIO $ ioError $ userError "Hahah"
    text "here"

You can then tap further into the Settings type provided by warp so that you could maybe log all the error messages or perform some custom action using the following methods setOnException setOnExceptionResponse.

TylerH
  • 20,799
  • 66
  • 75
  • 101
emg184
  • 850
  • 8
  • 19
  • There is an example provided in the [repository](https://github.com/scotty-web/scotty/blob/c36f35b89993f329b8ff08852c1535816375a926/examples/exceptions.hs) – cstml Jul 30 '21 at 15:16
  • I don’t believe the example catches all exceptions – emg184 Aug 01 '21 at 19:48