Why pattern-matching failure is not catching by my exception handler excToStr
in this case?
I have a handler of an incoming POST request as under the control of the Scotty Web framework:
...
import qualified Web.Scotty as W
...
W.post "/some/endpoint" $ excToStr "Cannot handle it!" $ do
b <- W.body
-- throwString "aaa" <--- THIS IS HANDLED FINE!
Just x::Maybe SomeMyType <- pure (decode b) -- BUT NOT THIS PATTERN-MATCHING FAILURE!
liftIO $ print $ show x
W.text "OK"
where excToStr
is mine and it looks like:
...
import qualified Data.Text.Lazy as LT
...
excH :: (String -> String) -> ActionT LT.Text IO () -> ActionT LT.Text IO ()
excH mkErr m = catchAnyDeep m (W.text . cs . mkErr . show)
excToStr :: String -> ActionT LT.Text IO () -> ActionT LT.Text IO ()
excToStr errMsg = excH (\details -> errMsg <> " (" <> details <> ")")
catchAnyDeep
is from safe-exceptions library. I tried another functions as well (catchAny
, handle
, catch
, etc) - without success. The crux of the problem is that when incoming body cannot be decoded successfully (and decode
returns Nothing
instead of Just x
), then pattern-match fails, so I expect that my extToStr
(ie. excH
) will handle it because catchAnyDeep
(and catchAny
) handles ANY exception (including pattern-match failures, right?):
catchAny :: MonadCatch m => m a -> (SomeException -> m a) -> m a
and
catchAnyDeep :: (MonadCatch m, MonadIO m, NFData a) => m a -> (SomeException -> m a) -> m a
.
If I throw just an exception with throwString
then it works as expected (the exception gets caught). But the pattern matching failure leads to an HTTP internal error 500 with a message "Pattern match failure in do expression at....". How to handle pattern-matching exceptions as well?
There are two forms of exceptions in a scotty action (of type ActionT Text IO
). There are native exceptions in IO
, and another form added by the ActionT
transformer. These exceptions are handled separately. The interface is given by the instances of ActionT
:
(MonadCatch m, ScottyError e) => MonadCatch (ActionT e m)
(and similarly the MonadThrow
instance). This shows that when you use catch
from MonadCatch
(or throwString
from MonadThrow
, and other variants from the safe-exceptions library), you are using the error handling mechanism of the transformed monad m
, which in Scotty is usually IO
(it defines ActionM = ActionT Text IO
).
(Monad m, ScottyError e) => MonadFail (ActionT e m)
. MonadFail
is the constraint used for partial pattern-matches in do
blocks. It does not require a MonadFail
from the underlying monad m
, which indicates that, unlike MonadThrow
/MonadCatch
, it uses the exception mechanism provided by the ActionT
transformer itself. To catch this exception you must look for a combinator in Scotty rather than an auxiliary library, such as rescue
.