packages feed

error-context 0.1.2.0 → 0.2.0.0

raw patch · 9 files changed

+695/−369 lines, 9 filesdep +aesondep +bytestringdep +katipPVP ok

version bump matches the API change (PVP)

Dependencies added: aeson, bytestring, katip, unordered-containers

API changes (from Hackage documentation)

- Control.Error.Context: instance (Control.Monad.Catch.MonadCatch m, Control.Monad.IO.Class.MonadIO m) => Control.Monad.IO.Class.MonadIO (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance (Control.Monad.Catch.MonadCatch m, Control.Monad.Trans.Resource.Internal.MonadResource m) => Control.Monad.Trans.Resource.Internal.MonadResource (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance Control.Monad.Catch.MonadThrow m => Control.Error.Context.MonadErrorContext (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance Control.Monad.Trans.Class.MonadTrans Control.Error.Context.ErrorContextT
- Control.Error.Context: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance GHC.Base.Functor Control.Error.Context.ErrorWithContext
- Control.Error.Context: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Error.Context.ErrorContextT m)
- Control.Error.Context: instance GHC.Classes.Eq Control.Error.Context.ErrorContext
- Control.Error.Context: instance GHC.Exception.Exception e => GHC.Exception.Exception (Control.Error.Context.ErrorWithContext e)
- Control.Error.Context: instance GHC.Show.Show Control.Error.Context.ErrorContext
- Control.Error.Context: instance GHC.Show.Show e => GHC.Show.Show (Control.Error.Context.ErrorWithContext e)
+ Control.Error.Context: ErrorContextKatipT :: m a -> ErrorContextKatipT m a
+ Control.Error.Context: [errorContextKVs] :: ErrorContext -> HashMap Text Value
+ Control.Error.Context: [errorContextNamespace] :: ErrorContext -> [Text]
+ Control.Error.Context: [runErrorContextKatipT] :: ErrorContextKatipT m a -> m a
+ Control.Error.Context: newtype ErrorContextKatipT m a
+ Control.Error.Context: withErrorNamespace :: MonadErrorContext m => Text -> m a -> m a
+ Control.Error.Context.Exception: catchAnyWithContext :: MonadCatch m => m a -> (ErrorWithContext SomeException -> m a) -> m a
+ Control.Error.Context.Exception: catchAnyWithoutContext :: MonadCatch m => m a -> (SomeException -> m a) -> m a
+ Control.Error.Context.Exception: catchWithContext :: (MonadCatch m, Exception e) => m a -> (ErrorWithContext e -> m a) -> m a
+ Control.Error.Context.Exception: catchWithoutContext :: forall a e m. (MonadCatch m, Exception e) => m a -> (e -> m a) -> m a
+ Control.Error.Context.Exception: errorContextForget :: ErrorWithContext e -> e
+ Control.Error.Context.Exception: tryAnyWithContext :: MonadCatch m => m a -> m (Either (ErrorWithContext SomeException) a)
+ Control.Error.Context.Exception: tryAnyWithoutContext :: MonadCatch m => m a -> m (Either SomeException a)
+ Control.Error.Context.Exception: tryWithContext :: (MonadCatch m, Exception e) => m a -> m (Either (ErrorWithContext e) a)
+ Control.Error.Context.Exception: tryWithoutContext :: (MonadCatch m, Exception e) => m a -> m (Either e a)
+ Control.Error.Context.Katip: ErrorContextKatipT :: m a -> ErrorContextKatipT m a
+ Control.Error.Context.Katip: [runErrorContextKatipT] :: ErrorContextKatipT m a -> m a
+ Control.Error.Context.Katip: instance (Control.Monad.Catch.MonadCatch m, Katip.Monadic.KatipContext m) => Control.Error.Context.Types.MonadErrorContext (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance (Control.Monad.Catch.MonadCatch m, Katip.Monadic.KatipContext m) => Katip.Monadic.KatipContext (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance (Control.Monad.Catch.MonadCatch m, Katip.Monadic.KatipContext m, Control.Monad.IO.Class.MonadIO m, Katip.Core.Katip m) => Katip.Core.Katip (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance (Katip.Monadic.KatipContext m, Control.Monad.Catch.MonadCatch m) => Control.Monad.Catch.MonadCatch (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance (Katip.Monadic.KatipContext m, Control.Monad.Catch.MonadCatch m) => Control.Monad.Catch.MonadThrow (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance (Katip.Monadic.KatipContext m, Control.Monad.Catch.MonadCatch m, Control.Monad.IO.Class.MonadIO m) => Control.Monad.IO.Class.MonadIO (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance (Katip.Monadic.KatipContext m, Control.Monad.Catch.MonadCatch m, Control.Monad.Trans.Resource.Internal.MonadResource m) => Control.Monad.Trans.Resource.Internal.MonadResource (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance Control.Monad.Trans.Class.MonadTrans Control.Error.Context.Katip.ErrorContextKatipT
+ Control.Error.Context.Katip: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Error.Context.Katip.ErrorContextKatipT m)
+ Control.Error.Context.Katip: newtype ErrorContextKatipT m a
+ Control.Error.Context.Simple: ErrorContextT :: ReaderT ErrorContext m a -> ErrorContextT m a
+ Control.Error.Context.Simple: [_runErrorContextT] :: ErrorContextT m a -> ReaderT ErrorContext m a
+ Control.Error.Context.Simple: instance (Control.Monad.Catch.MonadCatch m, Control.Monad.IO.Class.MonadIO m) => Control.Monad.IO.Class.MonadIO (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance (Control.Monad.Catch.MonadCatch m, Control.Monad.Trans.Resource.Internal.MonadResource m) => Control.Monad.Trans.Resource.Internal.MonadResource (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance Control.Monad.Catch.MonadCatch m => Control.Error.Context.Types.MonadErrorContext (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadThrow (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance Control.Monad.Reader.Class.MonadReader r m => Control.Monad.Reader.Class.MonadReader r (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance Control.Monad.Trans.Class.MonadTrans Control.Error.Context.Simple.ErrorContextT
+ Control.Error.Context.Simple: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance GHC.Base.Applicative m => GHC.Base.Applicative (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Error.Context.Simple.ErrorContextT m)
+ Control.Error.Context.Simple: newtype ErrorContextT m a
+ Control.Error.Context.Simple: runErrorContextT :: ErrorContextT m a -> m a
+ Control.Error.Context.Types: ErrorContext :: HashMap Text Value -> [Text] -> ErrorContext
+ Control.Error.Context.Types: ErrorWithContext :: ErrorContext -> e -> ErrorWithContext e
+ Control.Error.Context.Types: [errorContextKVs] :: ErrorContext -> HashMap Text Value
+ Control.Error.Context.Types: [errorContextNamespace] :: ErrorContext -> [Text]
+ Control.Error.Context.Types: class (Monad m, MonadThrow m) => MonadErrorContext m
+ Control.Error.Context.Types: data ErrorContext
+ Control.Error.Context.Types: data ErrorWithContext e
+ Control.Error.Context.Types: errorContextAsString :: ErrorContext -> String
+ Control.Error.Context.Types: errorContextCollect :: MonadErrorContext m => m ErrorContext
+ Control.Error.Context.Types: instance GHC.Base.Functor Control.Error.Context.Types.ErrorWithContext
+ Control.Error.Context.Types: instance GHC.Base.Monoid Control.Error.Context.Types.ErrorContext
+ Control.Error.Context.Types: instance GHC.Exception.Exception e => GHC.Exception.Exception (Control.Error.Context.Types.ErrorWithContext e)
+ Control.Error.Context.Types: instance GHC.Show.Show e => GHC.Show.Show (Control.Error.Context.Types.ErrorWithContext e)
+ Control.Error.Context.Types: withErrorContext :: (MonadErrorContext m, ToJSON v) => Text -> v -> m a -> m a
+ Control.Error.Context.Types: withErrorNamespace :: MonadErrorContext m => Text -> m a -> m a
- Control.Error.Context: ErrorContext :: [Text] -> ErrorContext
+ Control.Error.Context: ErrorContext :: HashMap Text Value -> [Text] -> ErrorContext
- Control.Error.Context: withErrorContext :: MonadErrorContext m => Text -> m a -> m a
+ Control.Error.Context: withErrorContext :: (MonadErrorContext m, ToJSON v) => Text -> v -> m a -> m a

Files

README.md view
@@ -1,7 +1,14 @@+# error-context [![Hackage version](https://img.shields.io/hackage/v/error-context.svg?label=Hackage)](https://hackage.haskell.org/package/error-context) [![Stackage version](https://www.stackage.org/package/error-context/badge/lts?label=Stackage)](https://www.stackage.org/package/error-context) [![Build Status](https://travis-ci.org/mtesseract/error-context.svg?branch=master)](https://travis-ci.org/mtesseract/error-context)+ # !! This is experimental work-in-progress !!  Welcome to *error-context*! This is a library providing context-aware-error and exception handling for Haskell.+error and exception handling for Haskell. It has built-in support for+the [Katip logging+package](https://hackage.haskell.org/package/katip). This means that+in combination with Katip, *error-context* can transparently use the+context (key-value pairs and namespace hierarchy) maintained by+`KatipContext` monads.  ## What problem does *error-context* attempt to solve? @@ -26,7 +33,30 @@  ## Examples -See https://github.com/mtesseract/error-context/blob/master/test/Control/Error/Context/Test.hs.+Consider this IO action:++```haskell+testExample :: IO ()+testExample = do+  Left errWithCtx <- tryAnyWithContext . runErrorContextT $ do+    withErrorNamespace "middle-earth" $+      withErrorNamespace "mordor" $+      withErrorContext "ring-carrier" ("Frodo" :: Text) $ +      throwM TestException+  putStrLn . displayException $ errWithCtx+```++When run, it produces the following output:++```+Exception: TestException+           ring-carrier: "Frodo"+  caused by: middle-earth+  caused by: mordor+```++For more examples, see+https://github.com/mtesseract/error-context/blob/master/test/Control/Error/Context/Test.hs.  ## What about "pure" exceptions? 
error-context.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: f55dae8d67d5460e15eb0ff2f407d2c336439b95b1e21c8573a1458bb40350fd+-- hash: 38188d7be27013ba6b735436e08a57a19e64d2e3cb1239df11b552e4c93bea50  name:           error-context-version:        0.1.2.0+version:        0.2.0.0 synopsis:       Provides API for enriching errors with contexts description:    Please see the README on Github at <https://github.com/mtesseract/error-context#readme> category:       Control, Error Handling@@ -32,16 +32,24 @@       src   ghc-options: -Wall   build-depends:-      base >=4.7 && <5+      aeson+    , base >=4.7 && <5+    , bytestring     , exceptions+    , katip     , monad-logger     , mtl     , resourcet     , safe-exceptions     , text     , unliftio-core+    , unordered-containers   exposed-modules:       Control.Error.Context+      Control.Error.Context.Exception+      Control.Error.Context.Katip+      Control.Error.Context.Simple+      Control.Error.Context.Types   other-modules:       Paths_error_context   default-language: Haskell2010@@ -53,9 +61,12 @@       test   ghc-options: -Wall -g -threaded -rtsopts -with-rtsopts=-N   build-depends:-      base >=4.7 && <5+      aeson+    , base >=4.7 && <5+    , bytestring     , error-context     , exceptions+    , katip     , monad-logger     , mtl     , resourcet@@ -64,6 +75,7 @@     , tasty-hunit     , text     , unliftio-core+    , unordered-containers   other-modules:       Control.Error.Context.Test       Paths_error_context
src/Control/Error/Context.hs view
@@ -21,11 +21,12 @@ {-# LANGUAGE UndecidableInstances       #-}  module Control.Error.Context-  ( ErrorContext(..)+  ( MonadErrorContext(..)+  , ErrorContext(..)   , ErrorContextT-  , MonadErrorContext(..)-  , ErrorWithContext(..)   , runErrorContextT+  , ErrorContextKatipT(..)+  , ErrorWithContext(..)   , errorContextualize   , errorContextForget   , errorWithContextDump@@ -40,209 +41,18 @@   , tryWithoutContext   ) where -import           Control.Exception.Safe       (SomeException (..), catchAny,-                                               catchJust)-import           Control.Monad.Catch          (Exception (..), MonadCatch (..),-                                               MonadThrow, throwM)-import           Control.Monad.IO.Unlift-import           Control.Monad.Reader-import           Control.Monad.State-import           Control.Monad.Trans.Resource-import           Control.Monad.Writer-import           Data.Text                    (Text)-import qualified Data.Text                    as Text-import           Data.Typeable---- | Monad type class providing contextualized errors.-class (Monad m, MonadThrow m) => MonadErrorContext m where-  errorContextCollect :: m ErrorContext     -- ^ Return the current error context.-  withErrorContext    :: Text -> m a -> m a -- ^ Execute a monadic action while having the-                                            -- provided 'Text' being pushed to the error context.---- | Data type implementing 'MonadErrorContext'.-newtype ErrorContextT m a =-  ErrorContextT { _runErrorContextT :: ReaderT ErrorContext m a-                } deriving ( Functor-                           , Applicative-                           , Monad-                           , MonadTrans-                           , MonadState s-                           , MonadWriter w )---- | Boundles an error with an 'ErrorContext'.-data ErrorWithContext e =-  ErrorWithContext ErrorContext e-  deriving (Show)--instance Functor ErrorWithContext where-  fmap f (ErrorWithContext ctx e) = ErrorWithContext ctx (f e)---- | An @ErrorWithContext e@ can be used as an exception.-instance Exception e => Exception (ErrorWithContext e) where-  toException exn = SomeException exn-  fromException (SomeException someExn) =-    case cast someExn :: Maybe (ErrorWithContext e) of-      Just (exnWithCtx @ (ErrorWithContext _ctx _exn)) ->-        Just exnWithCtx-      Nothing ->-        Nothing-  displayException (ErrorWithContext ctx exn) = do-    "Exception: " <> displayException exn <> "\n" <> errorContextAsString ctx---- | Unwrap an 'ErrorContextT'. Exceptions of type @e@ thrown in the--- provided action via 'throwM' will cause an @'ErrorWithContext' e@--- exception to be propagated upwards.-runErrorContextT-  :: ErrorContextT m a-  -> m a-runErrorContextT ctx =-  runReaderT (_runErrorContextT ctx) (ErrorContext [])--instance (MonadCatch m, MonadIO m) => MonadIO (ErrorContextT m) where-  liftIO m = do-    ctx <- errorContextCollect-    lift $ errorContextualizeIO ctx m--    where errorContextualizeIO ctx io = liftIO $-            catchAny io $ \ (SomeException exn) -> throwM (ErrorWithContext ctx exn)---- | Implement 'MonadErrorContext' for 'ErrorContextT'.-instance MonadThrow m => MonadErrorContext (ErrorContextT m) where-  errorContextCollect = ErrorContextT ask-  withErrorContext layer (ErrorContextT m) =-    ErrorContextT (local (errorContextPush layer) m)---- | Implement 'MonadThrow' for 'ErrorContextT'.-instance MonadThrow m => MonadThrow (ErrorContextT m) where-  throwM e = do-    case fromException (toException e) :: Maybe (ErrorWithContext SomeException) of-      Just exnWithCtx ->-        lift $ throwM exnWithCtx-      Nothing -> do-        ctx <- errorContextCollect-        lift $ throwM (ErrorWithContext ctx (SomeException e))---- | Implement 'MonadCatch' for 'ErrorContextT'.-instance MonadCatch m => MonadCatch (ErrorContextT m) where-  catch (ErrorContextT (ReaderT m)) c =-    ErrorContextT . ReaderT $-    \ r -> m r `catchWithoutContext` \ exn -> runReaderT (_runErrorContextT (c exn)) r---- | Like 'catch', but the handler is required to be context-aware. Is--- also able to catch exceptions of type 'e' (without context).-catchWithContext-  :: (MonadCatch m, Exception e) -- , MonadErrorContext m)-  => m a-  -> (ErrorWithContext e -> m a)-  -> m a-catchWithContext m handler = catchJust pre m handler-  where pre :: Exception e => SomeException -> Maybe (ErrorWithContext e)-        pre someExn =-          -- First we check if the exception is of the type-          -- 'ErrorWithContext e'. If so, provide it to the handler-          -- directly.-          case fromException someExn of-            Just (ErrorWithContext ctx someExnWithoutCtx :: ErrorWithContext SomeException) ->-              case fromException someExnWithoutCtx of-                Just exn -> Just (ErrorWithContext ctx exn)-                Nothing  -> Nothing-            Nothing  ->-              -- Then we check if the exception is of the type 'e',-              -- (without context). In this case we convert it into an-              -- 'ErrorWithContext e' by adding an empty context and-              -- provide the wrapped exception with context to the-              -- handler.-              case fromException someExn of-                Just exn ->-                  Just (ErrorWithContext (ErrorContext []) exn)-                Nothing ->-                  Nothing---- | Like 'catch', but the handler is required to be context-unaware.--- Is also able to catch exceptions with context, in which case the--- context will be forgotten before the exception will be provided to--- the handler.-catchWithoutContext-  :: forall a e m-   . (MonadCatch m, Exception e) -- , MonadErrorContext m)-  => m a-  -> (e -> m a)-  -> m a-catchWithoutContext m handler = catchJust pre m handler-  where pre :: SomeException -> Maybe e-        pre someExn =-          -- First we check if the exception is of the type-          -- 'ErrorWithContext e'. In this case we forget the context-          -- and provide the exception without context to the handler.-          case fromException someExn :: Maybe (ErrorWithContext SomeException) of-            Just (ErrorWithContext _ctx someExnWithoutContext) ->-              case fromException someExnWithoutContext :: Maybe e of-                Just exn ->-                  Just exn-                Nothing ->-                  Nothing-            Nothing  ->-              -- Then we check if the exception is of type 'e'. If so,-              -- provide it to the handler directly.-              case fromException someExn :: Maybe e of-                Just exn ->-                  Just exn-                Nothing  ->-                  Nothing--tryAnyWithContext-  :: MonadCatch m-  => m a-  -> m (Either (ErrorWithContext SomeException) a)-tryAnyWithContext m =-  catchWithContext (Right `liftM` m) (return . Left)--tryAnyWithoutContext-  :: MonadCatch m-  => m a-  -> m (Either SomeException a)-tryAnyWithoutContext m =-  catchWithoutContext (Right `liftM` m) (return . Left)--tryWithContext-  :: (MonadCatch m, Exception e)-  => m a-  -> m (Either (ErrorWithContext e) a)-tryWithContext m =-  catchWithContext (Right `liftM` m) (return . Left)--tryWithoutContext-  :: (MonadCatch m, Exception e)-  => m a-  -> m (Either e a)-tryWithoutContext m =-  catchWithoutContext (Right `liftM` m) (return . Left)---- | Implement 'MonadResource' for 'ErrorContextT'.-instance (MonadCatch m, MonadResource m) => MonadResource (ErrorContextT m) where-  liftResourceT = lift . liftResourceT---- | Implement 'MonadReader' for 'ErrorContextT'.-instance MonadReader r m => MonadReader r (ErrorContextT m) where-  ask = ErrorContextT (lift ask)-  local f (ErrorContextT (ReaderT m)) =-    ErrorContextT (ReaderT (\ errCtx -> local f (m errCtx)))---- | Encapsulates the error context — essentially a stack of 'Text'--- values.-data ErrorContext = ErrorContext [Text] deriving (Show, Eq)+import           Control.Error.Context.Katip+import           Control.Error.Context.Simple+import           Control.Error.Context.Types --- | Push a context layer (a 'Text') onto the provided 'ErrorContext'.-errorContextPush-  :: Text-  -> ErrorContext-  -> ErrorContext-errorContextPush layer (ErrorContext layers) =-  ErrorContext (layer : layers)+import           Control.Error.Context.Exception+import           Control.Exception.Safe          (SomeException (..), catchAny)+import           Control.Monad.Catch             (Exception (..),+                                                  MonadCatch (..), throwM)+import           Control.Monad.IO.Class+import           Data.Monoid -errorContextAsString :: ErrorContext -> String-errorContextAsString (ErrorContext layers) =-  concat $ map (\ layer -> "  caused by: " <> Text.unpack layer <> "\n") layers+--------------------------------------------------------------------------------  -- | Dump an error with context to stdout. errorWithContextDump@@ -262,43 +72,10 @@   ctx <- errorContextCollect   pure $ ErrorWithContext ctx e --- | Forgets the context from an enriched error.-errorContextForget-  :: ErrorWithContext e-  -> e-errorContextForget (ErrorWithContext _ctx e) = e---- | Context aware version of 'catchAny'.-catchAnyWithContext-  :: MonadCatch m+ensureExceptionContext+  :: (MonadCatch m, MonadErrorContext m)   => m a-  -> (ErrorWithContext SomeException -> m a)   -> m a-catchAnyWithContext m handler = catchJust pre m handler-  where pre :: SomeException -> Maybe (ErrorWithContext SomeException)-        pre someExn =-          case fromException someExn :: Maybe (ErrorWithContext SomeException) of-            Just exn ->-              Just exn-            Nothing ->-              Just (ErrorWithContext (ErrorContext []) someExn)---- | Context aware version of 'catchAny'.-catchAnyWithoutContext-  :: MonadCatch m-  => m a-  -> (SomeException -> m a)-  -> m a-catchAnyWithoutContext m handler = catchJust pre m handler-  where pre :: SomeException -> Maybe SomeException-        pre someExn =-          case fromException someExn :: Maybe (ErrorWithContext SomeException) of-            Just (ErrorWithContext _ctx exnWithoutContext) ->-              Just exnWithoutContext-            Nothing ->-              Just someExn--ensureExceptionContext :: (MonadCatch m, MonadErrorContext m) => m a -> m a ensureExceptionContext m =   catchAny m $ \ someExn ->   case fromException someExn :: Maybe (ErrorWithContext SomeException) of
+ src/Control/Error/Context/Exception.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Control.Error.Context.Exception where++import           Control.Error.Context.Types++import           Control.Exception.Safe      (SomeException (..), catchJust)+import           Control.Monad+import           Control.Monad.Catch         (Exception (..), MonadCatch (..))++-- | Like 'catch', but the handler is required to be context-aware. Is+-- also able to catch exceptions of type 'e' (without context).+catchWithContext+  :: (MonadCatch m, Exception e)+  => m a+  -> (ErrorWithContext e -> m a)+  -> m a+catchWithContext m handler = catchJust pre m handler+  where pre :: Exception e => SomeException -> Maybe (ErrorWithContext e)+        pre someExn =+          -- First we check if the exception is of the type+          -- 'ErrorWithContext e'. If so, provide it to the handler+          -- directly.+          case fromException someExn of+            Just (ErrorWithContext ctx someExnWithoutCtx :: ErrorWithContext SomeException) ->+              case fromException someExnWithoutCtx of+                Just exn -> Just (ErrorWithContext ctx exn)+                Nothing  -> Nothing+            Nothing  ->+              -- Then we check if the exception is of the type 'e',+              -- (without context). In this case we convert it into an+              -- 'ErrorWithContext e' by adding an empty context and+              -- provide the wrapped exception with context to the+              -- handler.+              case fromException someExn of+                Just exn ->+                  Just (ErrorWithContext mempty exn)+                Nothing ->+                  Nothing++-- | Like 'catch', but the handler is required to be context-unaware.+-- Is also able to catch exceptions with context, in which case the+-- context will be forgotten before the exception will be provided to+-- the handler.+catchWithoutContext+  :: forall a e m+   . (MonadCatch m, Exception e)+  => m a+  -> (e -> m a)+  -> m a+catchWithoutContext m handler = catchJust pre m handler+  where pre :: SomeException -> Maybe e+        pre someExn =+          -- First we check if the exception is of the type+          -- 'ErrorWithContext e'. In this case we forget the context+          -- and provide the exception without context to the handler.+          case fromException someExn :: Maybe (ErrorWithContext SomeException) of+            Just (ErrorWithContext _ctx someExnWithoutContext) ->+              case fromException someExnWithoutContext :: Maybe e of+                Just exn ->+                  Just exn+                Nothing ->+                  Nothing+            Nothing  ->+              -- Then we check if the exception is of type 'e'. If so,+              -- provide it to the handler directly.+              case fromException someExn :: Maybe e of+                Just exn ->+                  Just exn+                Nothing  ->+                  Nothing++tryAnyWithContext+  :: MonadCatch m+  => m a+  -> m (Either (ErrorWithContext SomeException) a)+tryAnyWithContext m =+  catchWithContext (Right `liftM` m) (return . Left)++tryAnyWithoutContext+  :: MonadCatch m+  => m a+  -> m (Either SomeException a)+tryAnyWithoutContext m =+  catchWithoutContext (Right `liftM` m) (return . Left)++tryWithContext+  :: (MonadCatch m, Exception e)+  => m a+  -> m (Either (ErrorWithContext e) a)+tryWithContext m =+  catchWithContext (Right `liftM` m) (return . Left)++tryWithoutContext+  :: (MonadCatch m, Exception e)+  => m a+  -> m (Either e a)+tryWithoutContext m =+  catchWithoutContext (Right `liftM` m) (return . Left)++-- | Forgets the context from an enriched error.+errorContextForget+  :: ErrorWithContext e+  -> e+errorContextForget (ErrorWithContext _ctx e) = e++-- | Context aware version of 'catchAny'.+catchAnyWithContext+  :: MonadCatch m+  => m a+  -> (ErrorWithContext SomeException -> m a)+  -> m a+catchAnyWithContext m handler = catchJust pre m handler+  where pre :: SomeException -> Maybe (ErrorWithContext SomeException)+        pre someExn =+          case fromException someExn :: Maybe (ErrorWithContext SomeException) of+            Just exn ->+              Just exn+            Nothing ->+              Just (ErrorWithContext mempty someExn)++-- | Context aware version of 'catchAny'.+catchAnyWithoutContext+  :: MonadCatch m+  => m a+  -> (SomeException -> m a)+  -> m a+catchAnyWithoutContext m handler = catchJust pre m handler+  where pre :: SomeException -> Maybe SomeException+        pre someExn =+          case fromException someExn :: Maybe (ErrorWithContext SomeException) of+            Just (ErrorWithContext _ctx exnWithoutContext) ->+              Just exnWithoutContext+            Nothing ->+              Just someExn
+ src/Control/Error/Context/Katip.hs view
@@ -0,0 +1,101 @@+{-|+Module      : Control.Error.Context.Katip+Description : API for enriching errors with contexts+Copyright   : (c) Moritz Clasmeier 2018+License     : BSD3+Maintainer  : mtesseract@silverratio.net+Stability   : experimental+Portability : POSIX++Provides an API for enriching errors with contexts.+-}++{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE FunctionalDependencies     #-}+{-# LANGUAGE GADTs                      #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses      #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE UndecidableInstances       #-}++module Control.Error.Context.Katip (ErrorContextKatipT(..)) where++import           Control.Error.Context.Exception+import           Control.Error.Context.Types+import           Control.Exception.Safe          (SomeException (..), catchAny)+import           Control.Monad.Catch             (Exception (..),+                                                  MonadCatch (..), MonadThrow,+                                                  throwM)+import           Control.Monad.IO.Unlift+import           Control.Monad.Reader+import           Control.Monad.State+import           Control.Monad.Trans.Resource+import           Control.Monad.Writer+import           Katip++-- | Data type implementing 'MonadErrorContext'.+newtype ErrorContextKatipT m a =+  ErrorContextKatipT { runErrorContextKatipT :: m a+                } deriving ( Functor+                           , Applicative+                           , Monad+                           -- , MonadTrans+                           , MonadState s+                           , MonadWriter w )++instance MonadTrans ErrorContextKatipT where+  lift = ErrorContextKatipT++instance (MonadCatch m, KatipContext m, MonadIO m, Katip m) => Katip (ErrorContextKatipT m) where+  getLogEnv = ErrorContextKatipT getLogEnv+  localLogEnv f (ErrorContextKatipT m) = ErrorContextKatipT (localLogEnv f m)++instance (MonadCatch m, KatipContext m) => KatipContext (ErrorContextKatipT m) where+  getKatipContext = ErrorContextKatipT getKatipContext+  localKatipContext f (ErrorContextKatipT m) = ErrorContextKatipT (localKatipContext f m)+  getKatipNamespace = ErrorContextKatipT getKatipNamespace+  localKatipNamespace f (ErrorContextKatipT m) = ErrorContextKatipT (localKatipNamespace f m)++instance (KatipContext m, MonadCatch m, MonadIO m) => MonadIO (ErrorContextKatipT m) where+  liftIO m = do+    context   <- toObject <$> lift getKatipContext+    namespace <- lift getKatipNamespace+    let ctx = ErrorContext context (unNamespace namespace)+    lift $ errorContextualizeIO ctx m++    where errorContextualizeIO ctx io = liftIO $+            catchAny io $ \ (SomeException exn) -> throwM (ErrorWithContext ctx exn)++instance (KatipContext m, MonadCatch m) => MonadThrow (ErrorContextKatipT m) where+  throwM e = do+    case fromException (toException e) :: Maybe (ErrorWithContext SomeException) of+      Just exnWithCtx ->+        lift $ throwM exnWithCtx+      Nothing -> do+        ctx <- errorContextCollect+        lift $ throwM (ErrorWithContext ctx (SomeException e))++instance (MonadCatch m, KatipContext m) => MonadErrorContext (ErrorContextKatipT m) where+  errorContextCollect = do+    context   <- toObject <$> lift getKatipContext+    namespace <- lift getKatipNamespace+    pure $ ErrorContext context (unNamespace namespace)+  withErrorNamespace label =+    katipAddNamespace (Namespace [label])+  withErrorContext label val =+    katipAddContext (sl label val)++instance (KatipContext m, MonadCatch m) => MonadCatch (ErrorContextKatipT m) where+  catch (ErrorContextKatipT m) c =+    ErrorContextKatipT $+    m `catchWithoutContext` \ exn -> runErrorContextKatipT (c exn)++instance (KatipContext m, MonadCatch m, MonadResource m) => MonadResource (ErrorContextKatipT m) where+  liftResourceT = liftResourceT++instance MonadReader r m => MonadReader r (ErrorContextKatipT m) where+  ask = ErrorContextKatipT ask+  local f (ErrorContextKatipT m) =+    ErrorContextKatipT (local f m)
+ src/Control/Error/Context/Simple.hs view
@@ -0,0 +1,101 @@+{-|+Module      : Control.Error.Context.Simple+Description : API for enriching errors with contexts+Copyright   : (c) Moritz Clasmeier 2018+License     : BSD3+Maintainer  : mtesseract@silverratio.net+Stability   : experimental+Portability : POSIX+-}++{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE FunctionalDependencies     #-}+{-# LANGUAGE GADTs                      #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses      #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE UndecidableInstances       #-}++module Control.Error.Context.Simple+  ( ErrorContextT(..)+  , runErrorContextT+  ) where++import           Control.Error.Context.Types+import           Data.Aeson++import           Control.Error.Context.Exception+import           Control.Exception.Safe          (SomeException (..), catchAny)+import           Control.Monad.Catch             (Exception (..),+                                                  MonadCatch (..), MonadThrow,+                                                  throwM)+import           Control.Monad.IO.Unlift+import           Control.Monad.Reader+import           Control.Monad.State+import           Control.Monad.Trans.Resource+import           Control.Monad.Writer+import qualified Data.HashMap.Strict             as HashMap+import           Data.Text                       (Text)++-- | Data type implementing 'MonadErrorContext'.+newtype ErrorContextT m a =+  ErrorContextT { _runErrorContextT :: ReaderT ErrorContext m a+                } deriving ( Functor+                           , Applicative+                           , Monad+                           , MonadTrans+                           , MonadState s+                           , MonadWriter w )++runErrorContextT :: ErrorContextT m a -> m a+runErrorContextT =+  flip runReaderT mempty . _runErrorContextT++instance (MonadCatch m, MonadIO m) => MonadIO (ErrorContextT m) where+  liftIO m = do+    ctx <- errorContextCollect+    lift $ errorContextualizeIO ctx m++    where errorContextualizeIO ctx io = liftIO $+            catchAny io $ \ (SomeException exn) -> throwM (ErrorWithContext ctx exn)++instance (MonadCatch m) => MonadThrow (ErrorContextT m) where+  throwM e = do+    case fromException (toException e) :: Maybe (ErrorWithContext SomeException) of+      Just exnWithCtx ->+        lift $ throwM exnWithCtx+      Nothing -> do+        ctx <- errorContextCollect+        lift $ throwM (ErrorWithContext ctx (SomeException e))++errorNamespacePush :: Text -> ErrorContext -> ErrorContext+errorNamespacePush label ctx =+  let currentNamespace = errorContextNamespace ctx+  in ctx { errorContextNamespace = currentNamespace ++ [label] }++errorContextAdd :: Text -> Value -> ErrorContext -> ErrorContext+errorContextAdd label val ctx =+  let currentKVs = errorContextKVs ctx+  in ctx { errorContextKVs = HashMap.insert label val currentKVs }++instance (MonadCatch m) => MonadErrorContext (ErrorContextT m) where+  errorContextCollect = ErrorContextT ask+  withErrorNamespace layer (ErrorContextT m) =+    ErrorContextT (local (errorNamespacePush layer) m)+  withErrorContext label val (ErrorContextT m) =+    ErrorContextT (local (errorContextAdd label (toJSON val)) m)++instance (MonadCatch m) => MonadCatch (ErrorContextT m) where+  catch (ErrorContextT m) c =+    ErrorContextT $+    m `catchWithoutContext` \ exn -> _runErrorContextT (c exn)++instance (MonadCatch m, MonadResource m) => MonadResource (ErrorContextT m) where+  liftResourceT = liftResourceT++instance MonadReader r m => MonadReader r (ErrorContextT m) where+  ask = ErrorContextT (lift ask)+  local f (ErrorContextT (ReaderT m)) =+    ErrorContextT (ReaderT (\ errCtx -> local f (m errCtx)))
+ src/Control/Error/Context/Types.hs view
@@ -0,0 +1,102 @@+{-|+Module      : Control.Error.Context.Types+Description : API for enriching errors with contexts+Copyright   : (c) Moritz Clasmeier 2018+License     : BSD3+Maintainer  : mtesseract@silverratio.net+Stability   : experimental+Portability : POSIX++Provides an API for enriching errors with contexts.+-}++{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE FunctionalDependencies     #-}+{-# LANGUAGE GADTs                      #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses      #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE UndecidableInstances       #-}++module Control.Error.Context.Types+  ( ErrorContext(..)+  , ErrorWithContext(..)+  , errorContextAsString+  , MonadErrorContext(..)+  ) where++import           Control.Exception+import           Control.Monad.Catch  (MonadThrow)+import           Data.Aeson+import qualified Data.ByteString.Lazy as ByteString.Lazy+import           Data.Function        ((&))+import           Data.HashMap.Strict  (HashMap)+import qualified Data.HashMap.Strict  as HashMap+import           Data.Monoid+import           Data.Text            (Text)+import qualified Data.Text            as Text+import qualified Data.Text.Encoding   as Text+import           Data.Typeable++-- | Boundles an error with an 'ErrorContext'.+data ErrorWithContext e =+  ErrorWithContext ErrorContext e++instance Show e => Show (ErrorWithContext e) where+  show (ErrorWithContext _ctx e) = show e++instance Functor ErrorWithContext where+  fmap f (ErrorWithContext ctx e) = ErrorWithContext ctx (f e)++data ErrorContext =+  ErrorContext { errorContextKVs       :: HashMap Text Value+               , errorContextNamespace :: [Text] }++instance Monoid ErrorContext where+  mempty = ErrorContext mempty mempty+  (ErrorContext kvs namespace) `mappend` (ErrorContext kvs' namespace') =+    ErrorContext (kvs <> kvs') (namespace <> namespace')++-- | An @ErrorWithContext e@ can be used as an exception.+instance Exception e => Exception (ErrorWithContext e) where+  toException exn = SomeException exn+  fromException (SomeException someExn) =+    case cast someExn :: Maybe (ErrorWithContext e) of+      Just (exnWithCtx @ (ErrorWithContext _ctx _exn)) ->+        Just exnWithCtx+      Nothing ->+        Nothing+  displayException (ErrorWithContext ctx exn) =+    "Exception: " <> displayException exn <> "\n"+    <> errorContextAsString ctx++errorContextAsString :: ErrorContext -> String+errorContextAsString (ErrorContext hashmap layers) =+  concat $ prettyPrintKvs ++ prettyPrintCauses++  where prettyPrintKvs =+          hashmap+          & HashMap.toList+          & (map $ \ (label, val) ->+                     let labelS = label+                                  & Text.unpack+                         valS   = val+                                  & encode+                                  & ByteString.Lazy.toStrict+                                  & Text.decodeUtf8+                                  & Text.unpack+                     in "           " <> labelS <> ": " <> valS <> "\n")++        prettyPrintCauses =+          layers+          & (map $ \ layer ->+                     let layerS = Text.unpack layer+                     in "  caused by: " <> layerS <> "\n")++-- | Monad type class providing contextualized errors.+class (Monad m, MonadThrow m) => MonadErrorContext m where+  errorContextCollect :: m ErrorContext     -- ^ Return the current error context.+  withErrorContext :: ToJSON v => Text -> v -> m a -> m a+  withErrorNamespace :: Text -> m a -> m a
test/Control/Error/Context/Test.hs view
@@ -1,241 +1,307 @@ {-# LANGUAGE FlexibleInstances   #-}+{-# LANGUAGE GADTs               #-} {-# LANGUAGE OverloadedStrings   #-} {-# LANGUAGE QuasiQuotes         #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE RecordWildCards     #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell     #-}  module Control.Error.Context.Test (tests) where +import Data.Aeson import           Control.Error.Context-import           Control.Exception      (Exception (..), throw, throwIO)+import           Control.Exception           (Exception (..), throw, throwIO) import           Control.Monad-import           Control.Monad.Catch    (catch, throwM, try)+import           Control.Monad.Catch         (MonadCatch, catch, throwM, try) import           Control.Monad.IO.Class+import qualified Data.HashMap.Strict as HashMap+import Data.HashMap.Strict (HashMap)+import           Data.Text                   (Text)+import           Katip import           Test.Tasty import           Test.Tasty.HUnit  tests :: TestTree-tests =-  testGroup-    "Tests"-    [-      testCase "Contextualize IO Exception"-        testContextualizeIOException+tests = testGroup "Tests" $+  [ testGroup "Simple (ErrorContextT)" (testsWithConf testConfSimple)+  , testGroup "Katip (ErrorContextKatipT)" (testsWithConf testConfKatip)+  , testGroup "Test Examples" testExamples+  ]++testExamples :: [TestTree]+testExamples = +  [ testCase "simpleExample" testExample ]++testsWithConf :: TestConf -> [TestTree]+testsWithConf conf =+    [ testCase "Contextualize IO Exception"+        (testContextualizeIOException conf)     , testCase "throwM"-        testThrow+        (testThrow conf)     , testCase "catchAnyWithContext"-        testCatchAnyWithContext+        (testCatchAnyWithContext conf)     , testCase "catchAnyWithContext/pure"-        testCatchAnyWithContextPure+        (testCatchAnyWithContextPure conf)     , testCase "catchAnyWithoutContext"-        testCatchAnyWithoutContext+        (testCatchAnyWithoutContext conf)     , testCase "catchAnyWithoutContext/pure"-        testCatchAnyWithoutContextPure+        (testCatchAnyWithoutContextPure conf)     , testCase "Catch context-enriched exception without context"-        testCatchWithoutContext+        (testCatchWithoutContext conf)     , testCase "Contextualize error value"-        testContextualizeErrorValue+        (testContextualizeErrorValue conf)     , testCase "Forgetting error context"-        testForgetErrorContext+        (testForgetErrorContext conf)     , testCase "Dumping error context"-        testDumpErrorContext+        (testDumpErrorContext conf)     , testCase "Throw and catch"-        testThrowAndCatch+        (testThrowAndCatch conf)     , testCase "Catch non-contextualized exception with context"-        testNonContextualizedCatchWithContext+        (testNonContextualizedCatchWithContext conf)     , testCase "ensureExceptionContext"-        testEnsureExceptionContext+        (testEnsureExceptionContext conf)     , testCase "catch head exception"-        testCatchHeadException+        (testCatchHeadException conf)     , testCase "tryAnyWithoutContext"-        testTryAnyWithoutContext+        (testTryAnyWithoutContext conf)     , testCase "tryAnyWithoutContext/pure"-        testTryAnyWithoutContextPure+        (testTryAnyWithoutContextPure conf)     , testCase "tryAnyWithContext"-        testTryAnyWithContext+        (testTryAnyWithContext conf)     , testCase "tryAnyWithContext/pure"-        testTryAnyWithContextPure+        (testTryAnyWithContextPure conf)     , testCase "tryWithContext"-        testTryWithContext+        (testTryWithContext conf)     , testCase "tryWithContext/pure"-        testTryWithContextPure+        (testTryWithContextPure conf)     , testCase "tryWithoutContext"-        testTryWithoutContext+        (testTryWithoutContext conf)     , testCase "tryWithoutContext/pure"-        testTryWithoutContextPure+        (testTryWithoutContextPure conf)     , testCase "Throw and catch"-        testThrowAndCatch+        (testThrowAndCatch conf)+    , testCase "contextKvRetrieval"+        (testContextKv conf)+    , testCase "contextKvOverwrite"+        (testContextKvOverwrite conf)     ]  data TestException = TestException deriving (Show, Eq)  instance Exception TestException -testContextualizeIOException :: Assertion-testContextualizeIOException = do-  Left (ErrorWithContext (ErrorContext ctx) TestException) <- try . runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $+data TestConf where+  TestConf :: forall m. (MonadIO m, MonadCatch m, MonadErrorContext m) =>+              { runStackT         :: forall a. m a -> IO a }+           -> TestConf++testConfKatip :: TestConf+testConfKatip =+  TestConf { runStackT = \ m -> do+               logEnv <- liftIO $ initLogEnv "test-suite" "test"+               runKatipContextT logEnv () (Namespace []) $ runErrorContextKatipT m+           }++testConfSimple :: TestConf+testConfSimple =+  TestConf { runStackT = runErrorContextT }++extractKVs :: ErrorContext -> HashMap Text Value+extractKVs (ErrorContext kvs _namespace) =+  kvs++testContextualizeIOException :: TestConf -> Assertion+testContextualizeIOException TestConf { .. } = do+  Left (ErrorWithContext ctx TestException) <- try . runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $     liftIO failingIOException-  ["B", "A"] @=? ctx+  ["A", "B"] @=? errorContextNamespace ctx    where failingIOException :: IO ()         failingIOException =           throwIO TestException -testCatchWithoutContext :: Assertion-testCatchWithoutContext = do-  TestException <- runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $+testThrow :: TestConf -> Assertion+testThrow TestConf { .. } = do+  catch (runStackT (throwM TestException)) $ \ someExn -> do+    let Just (ErrorWithContext _ctx someInnerExn) = fromException someExn+    liftIO $ Just TestException @=? fromException someInnerExn++testCatchWithoutContext :: TestConf -> Assertion+testCatchWithoutContext TestConf { .. } = do+  TestException <- runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $     catchWithoutContext (throwM TestException) $ \ (exn :: TestException) -> do       pure exn   pure () -testContextualizeErrorValue :: Assertion-testContextualizeErrorValue = do-  ErrorWithContext (ErrorContext ctx) TestException <- runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $+testContextualizeErrorValue :: TestConf -> Assertion+testContextualizeErrorValue TestConf { .. } = do+  ErrorWithContext ctx TestException <- runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $     errorContextualize TestException-  ["B", "A"] @=? ctx+  ["A", "B"] @=? errorContextNamespace ctx -testForgetErrorContext :: Assertion-testForgetErrorContext = do-  errWithCtx @ (ErrorWithContext _ctx TestException) <- runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $+testForgetErrorContext :: TestConf -> Assertion+testForgetErrorContext TestConf { .. } = do+  errWithCtx @ (ErrorWithContext _ctx TestException) <- runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $     errorContextualize TestException   TestException @=? errorContextForget errWithCtx -testDumpErrorContext :: Assertion-testDumpErrorContext = do-  errWithCtx @ (ErrorWithContext _ctx _exn) <- runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $+testDumpErrorContext :: TestConf -> Assertion+testDumpErrorContext TestConf { .. } = do+  errWithCtx @ (ErrorWithContext _ctx _exn) <- runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $     errorContextualize TestException   errorWithContextDump errWithCtx -testThrowAndCatch :: Assertion-testThrowAndCatch = do-  void . runErrorContextT $+testThrowAndCatch :: TestConf -> Assertion+testThrowAndCatch TestConf { .. } = do+  void . runStackT $     catch (throwM TestException) $ \ TestException -> pure () -testThrow :: Assertion-testThrow = do-  catch (runErrorContextT (throwM TestException)) $ \ someExn -> do-    let Just (ErrorWithContext _ctx someInnerExn) = fromException someExn-    liftIO $ Just TestException @=? fromException someInnerExn--testCatchAnyWithContext :: Assertion-testCatchAnyWithContext = do-  catchAnyWithContext (runErrorContextT (throwM TestException)) $+testCatchAnyWithContext :: TestConf -> Assertion+testCatchAnyWithContext TestConf { .. } = do+  catchAnyWithContext (runStackT (throwM TestException)) $     \ (ErrorWithContext _ctx someExn) -> do       Just TestException @=? fromException someExn -testCatchAnyWithContextPure :: Assertion-testCatchAnyWithContextPure = do-  catchAnyWithContext (runErrorContextT (throw TestException)) $+testCatchAnyWithContextPure :: TestConf -> Assertion+testCatchAnyWithContextPure TestConf { .. } = do+  catchAnyWithContext (runStackT (throw TestException)) $     \ (ErrorWithContext _ctx someExn) -> do       Just TestException @=? fromException someExn -testCatchAnyWithoutContext :: Assertion-testCatchAnyWithoutContext = do-  catchAnyWithoutContext (runErrorContextT (throwM TestException)) $+testCatchAnyWithoutContext :: TestConf -> Assertion+testCatchAnyWithoutContext TestConf { .. } = do+  catchAnyWithoutContext (runStackT (throwM TestException)) $     \ someExn -> do       Just TestException @=? fromException someExn -testCatchAnyWithoutContextPure :: Assertion-testCatchAnyWithoutContextPure = do-  catchAnyWithoutContext (runErrorContextT (throw TestException)) $+testCatchAnyWithoutContextPure :: TestConf -> Assertion+testCatchAnyWithoutContextPure TestConf { .. } = do+  catchAnyWithoutContext (runStackT (throw TestException)) $     \ someExn -> do       Just TestException @=? fromException someExn -testNonContextualizedCatchWithContext :: Assertion-testNonContextualizedCatchWithContext = do-  ErrorWithContext (ErrorContext ctx) TestException <- runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $ do+testNonContextualizedCatchWithContext :: TestConf -> Assertion+testNonContextualizedCatchWithContext TestConf { .. } = do+  ErrorWithContext ctx TestException <- runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $ do     catchWithContext throwPureException $ \ (exn :: ErrorWithContext TestException) -> do       pure exn-  [] @=? ctx+  [] @=? errorContextNamespace ctx    where throwPureException = throw TestException -testEnsureExceptionContext :: Assertion-testEnsureExceptionContext = do-  Left someExn <- try . runErrorContextT $-    withErrorContext "A" $-    withErrorContext "B" $ do+testEnsureExceptionContext :: TestConf -> Assertion+testEnsureExceptionContext TestConf { .. } = do+  Left someExn <- try . runStackT $+    withErrorNamespace "A" $+    withErrorNamespace "B" $ do     ensureExceptionContext $ do       throw TestException   let Just (ErrorWithContext ctx someExnWithoutCtx) = fromException someExn   Just TestException @=? fromException someExnWithoutCtx-  ErrorContext ["B", "A"] @=? ctx+  ["A", "B"] @=? errorContextNamespace ctx -testCatchHeadException :: Assertion-testCatchHeadException = do-  Left errWithCtx <- tryAnyWithContext . runErrorContextT $ do-    withErrorContext "Here I am, calling head on an empty list!" $+testCatchHeadException :: TestConf -> Assertion+testCatchHeadException TestConf { .. } = do+  Left errWithCtx <- tryAnyWithContext . runStackT $ do+    withErrorNamespace "Here I am, calling head on an empty list!" $       ensureExceptionContext $ seq (head []) (pure ())   let (ErrorWithContext _ctx _exnWithoutCtx) = errWithCtx   putStrLn . displayException $ errWithCtx -testTryAnyWithContext :: Assertion-testTryAnyWithContext = do-  Left (ErrorWithContext _ctx someExn) <- tryAnyWithContext . runErrorContextT $ do+testTryAnyWithContext :: TestConf -> Assertion+testTryAnyWithContext TestConf { .. } = do+  Left (ErrorWithContext _ctx someExn) <- tryAnyWithContext . runStackT $ do     void $ throwM TestException     pure ()   Just TestException @=? fromException someExn -testTryAnyWithContextPure :: Assertion-testTryAnyWithContextPure = do-  Left (ErrorWithContext _ctx someExn) <- tryAnyWithContext . runErrorContextT $+testTryAnyWithContextPure :: TestConf -> Assertion+testTryAnyWithContextPure TestConf { .. } = do+  Left (ErrorWithContext _ctx someExn) <- tryAnyWithContext . runStackT $     seq (throw TestException) (pure ())   Just TestException @=? fromException someExn -testTryAnyWithoutContext :: Assertion-testTryAnyWithoutContext = do-  Left someExn <- tryAnyWithoutContext . runErrorContextT $ do+testTryAnyWithoutContext :: TestConf -> Assertion+testTryAnyWithoutContext TestConf { .. } = do+  Left someExn <- tryAnyWithoutContext . runStackT $ do     void $ throwM TestException     pure ()   Just TestException @=? fromException someExn -testTryAnyWithoutContextPure :: Assertion-testTryAnyWithoutContextPure = do-  Left someExn <- tryAnyWithoutContext . runErrorContextT $+testTryAnyWithoutContextPure :: TestConf -> Assertion+testTryAnyWithoutContextPure TestConf { .. } = do+  Left someExn <- tryAnyWithoutContext . runStackT $     seq (throw TestException) (pure ())   Just TestException @=? fromException someExn -testTryWithContext :: Assertion-testTryWithContext = do-  Left (ErrorWithContext _ctx exn) <- tryWithContext . runErrorContextT $ do+testTryWithContext :: TestConf -> Assertion+testTryWithContext TestConf { .. } = do+  Left (ErrorWithContext _ctx exn) <- tryWithContext . runStackT $ do     void $ throwM TestException     pure ()   TestException @=? exn -testTryWithContextPure :: Assertion-testTryWithContextPure = do-  Left (ErrorWithContext _ctx exn) <- tryWithContext . runErrorContextT $+testTryWithContextPure :: TestConf -> Assertion+testTryWithContextPure TestConf { .. } = do+  Left (ErrorWithContext _ctx exn) <- tryWithContext . runStackT $     seq (throw TestException) (pure ())   TestException @=? exn -testTryWithoutContext :: Assertion-testTryWithoutContext = do-  Left exn <- tryWithoutContext . runErrorContextT $ do+testTryWithoutContext :: TestConf -> Assertion+testTryWithoutContext TestConf { .. } = do+  Left exn <- tryWithoutContext . runStackT $ do     void $ throwM TestException     pure ()   TestException @=? exn -testTryWithoutContextPure :: Assertion-testTryWithoutContextPure = do-  Left exn <- tryWithoutContext . runErrorContextT $+testTryWithoutContextPure :: TestConf -> Assertion+testTryWithoutContextPure TestConf { .. } = do+  Left exn <- tryWithoutContext . runStackT $     seq (throw TestException) (pure ())   TestException @=? exn --- testTryAnyWithoutContext :: Assertion--- testTryAnyWithoutContext = do---   Left someExn <- tryAnyWithoutContext . runErrorContextT $ do---     void $ throwM TestException---     pure ()---   Just TestException @=? fromException someExn+testContextKv :: TestConf -> Assertion+testContextKv TestConf { .. } = do+  Left (ErrorWithContext ctx TestException) <- tryWithContext . runStackT $+    withErrorContext "ultimate-answer" answer $+    throwM TestException+  HashMap.fromList [("ultimate-answer", toJSON answer)] @=? extractKVs ctx++  where answer :: Int+        answer = 42++testContextKvOverwrite :: TestConf -> Assertion+testContextKvOverwrite TestConf { .. } = do+  Left (ErrorWithContext ctx TestException) <- tryWithContext . runStackT $+    withErrorContext "ultimate-answer" answer $+    withErrorContext "ultimate-answer" answer' $+    throwM TestException+  HashMap.fromList [("ultimate-answer", toJSON answer')] @=? errorContextKVs ctx++  where answer :: Int+        answer = 42++        answer' :: Int+        answer' = 0++testExample :: IO ()+testExample = do+  Left errWithCtx <- tryAnyWithContext . runErrorContextT $+    withErrorNamespace "middle-earth" $+    withErrorNamespace "mordor" $+    withErrorContext "ring-carrier" ("Frodo" :: Text) $+      throwM TestException+  putStrLn . displayException $ errWithCtx
test/Spec.hs view
@@ -8,3 +8,5 @@     testGroup "Test Suite"     [ Control.Error.Context.Test.tests     ]++