diff --git a/Web/Scotty/Internal/Types.hs b/Web/Scotty/Internal/Types.hs index 3d2c7d22..2e7721d1 100644 --- a/Web/Scotty/Internal/Types.hs +++ b/Web/Scotty/Internal/Types.hs @@ -1,7 +1,6 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DerivingStrategies #-} -{-# language DerivingVia #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -20,6 +19,7 @@ import qualified Control.Exception as E import Control.Monad (MonadPlus(..)) import Control.Monad.Base (MonadBase) import Control.Monad.Catch (MonadCatch, MonadThrow) +import Control.Monad.Error.Class (MonadError(..)) import Control.Monad.IO.Class (MonadIO(..)) import UnliftIO (MonadUnliftIO(..)) import Control.Monad.Reader (MonadReader(..), ReaderT, asks) @@ -207,6 +207,15 @@ defaultScottyResponse = SR status200 [] (ContentBuilder mempty) newtype ActionT m a = ActionT { runAM :: ReaderT ActionEnv m a } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader ActionEnv, MonadTrans, MonadThrow, MonadCatch, MonadBase b, MonadBaseControl b, MonadTransControl, MonadUnliftIO) + +-- | Models the invariant that only 'StatusError's can be thrown and caught. +instance (MonadUnliftIO m) => MonadError StatusError (ActionT m) where + throwError = E.throw + catchError = catch +-- | Modeled after the behaviour in scotty < 0.20, 'fail' throws a 'StatusError' with code 500 ("Server Error"), which can be caught with 'E.catch' or 'rescue'. +instance (MonadIO m) => MonadFail (ActionT m) where + fail = E.throw . StatusError status500 . pack +-- | 'empty' throws 'ActionError' 'AENext', whereas '(<|>)' catches any 'ActionError's or 'StatusError's in the first action and proceeds to the second one. instance (MonadUnliftIO m) => Alternative (ActionT m) where empty = E.throw AENext a <|> b = do diff --git a/changelog.md b/changelog.md index 2163b527..96b8d7bc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,11 @@ ## next [????.??.??] +## 0.20.1 [2023.10.03] + * remove dependencies on 'base-compat' and 'base-compat-batteries' (#318) +* re-add MonadFail (ActionT m) instance (#325) +* re-add MonadError (ActionT m) instance, but the error type is now specialized to 'StatusError' (#325) +* raise lower bound on base ( > 4.14 ) to reflect support for GHC >= 8.10 (#325). ## 0.20 [2023.10.02] * Drop support for GHC < 8.10 and modernise the CI pipeline (#300). @@ -14,8 +19,10 @@ Breaking: * (#310) Introduce `unliftio` as a dependency, and base exception handling on `catch`. -** Clarify the exception handling mechanism of ActionT, ScottyT. `rescue` changes signature to use proper `Exception` types rather than strings. -** All ActionT methods (`text`, `html` etc.) have now a MonadIO constraint on the base monad rather than Monad because the response is constructed in a TVar inside ActionEnv. `rescue` has a MonadUnliftIO constraint. The Alternative instance of ActionT also is based on MonadUnliftIO because `<|>` is implemented in terms of `catch`. `ScottyT` and `ActionT` do not have an exception type parameter anymore. +* (#310) Clarify the exception handling mechanism of ActionT, ScottyT. `rescue` changes signature to use proper `Exception` types rather than strings. Remove `ScottyError` typeclass. +* (#310) All ActionT methods (`text`, `html` etc.) have now a MonadIO constraint on the base monad rather than Monad because the response is constructed in a TVar inside ActionEnv. `rescue` has a MonadUnliftIO constraint. The Alternative instance of ActionT also is based on MonadUnliftIO because `<|>` is implemented in terms of `catch`. `ScottyT` and `ActionT` do not have an exception type parameter anymore. +* (#310) MonadError e (ActionT m) instance removed +* (#310) MonadFail (ActionT m) instance is missing by mistake. ## 0.12.1 [2022.11.17] * Fix CPP bug that prevented tests from building on Windows. diff --git a/scotty.cabal b/scotty.cabal index 07dfb7ba..e06ffb96 100644 --- a/scotty.cabal +++ b/scotty.cabal @@ -1,5 +1,5 @@ Name: scotty -Version: 0.20 +Version: 0.20.1 Synopsis: Haskell web framework inspired by Ruby's Sinatra, using WAI and Warp Homepage: https://github.com/scotty-web/scotty Bug-reports: https://github.com/scotty-web/scotty/issues @@ -69,7 +69,7 @@ Library Web.Scotty.Util default-language: Haskell2010 build-depends: aeson >= 0.6.2.1 && < 2.3, - base >= 4.6 && < 5, + base >= 4.14 && < 5, blaze-builder >= 0.3.3.0 && < 0.5, bytestring >= 0.10.0.2 && < 0.12, case-insensitive >= 1.0.0.1 && < 1.3, diff --git a/test/Web/ScottySpec.hs b/test/Web/ScottySpec.hs index e2aa3646..91d629e8 100644 --- a/test/Web/ScottySpec.hs +++ b/test/Web/ScottySpec.hs @@ -141,6 +141,14 @@ spec = do get "/dictionary?word2=y" `shouldRespondWith` "y" get "/dictionary?word1=a&word2=b" `shouldRespondWith` "a" + context "MonadFail instance" $ do + withApp (Scotty.get "/" $ fail "boom!") $ do + it "returns 500 if not caught" $ + get "/" `shouldRespondWith` 500 + withApp (Scotty.get "/" $ (fail "boom!") `rescue` (\(_ :: StatusError) -> text "ok")) $ + it "can catch the StatusError thrown by fail" $ do + get "/" `shouldRespondWith` 200 { matchBody = "ok"} + describe "redirect" $ do withApp ( do