-- | Error handling in B9 via extensible effects.
-- B9 wraps errors in `SomeException`.
--
-- @since 0.5.64
module B9.B9Error
  ( throwSomeException,
    throwSomeException_,
    throwB9Error,
    throwB9Error_,
    errorOnException,
    ExcB9,
    WithIoExceptions,
    runExcB9,
    B9Error (MkB9Error),
    fromB9Error,
    catchB9Error,
    catchB9ErrorAsEither,
    finallyB9,
  )
where

import Control.Eff as Eff
import Control.Eff.Exception as Eff
import Control.Exception
  ( Exception,
    SomeException,
    toException,
  )
import qualified Control.Exception as IOExc
import Control.Monad
import Data.String (IsString (..))

-- | The exception effect used in most places in B9.
--  This is `Exc` specialized with `SomeException`.
--
-- @since 0.5.64
type ExcB9 = Exc SomeException

-- | Constraint alias for the exception effect that allows to
-- throw 'SomeException'.
--
-- @since 1.0.0
type WithIoExceptions e = SetMember Exc (Exc SomeException) e

-- | This is a simple runtime exception to indicate that B9 code encountered
-- some exceptional event.
--
-- @since 0.5.64
newtype B9Error = MkB9Error {B9Error -> String
fromB9Error :: String}
  deriving (String -> B9Error
(String -> B9Error) -> IsString B9Error
forall a. (String -> a) -> IsString a
fromString :: String -> B9Error
$cfromString :: String -> B9Error
IsString)

instance Show B9Error where
  show :: B9Error -> String
show (MkB9Error String
msg) = String
"B9 internal error: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
msg

instance Exception B9Error

-- | Run an `ExcB9`.
--
-- @since 0.5.64
runExcB9 :: Eff (ExcB9 ': e) a -> Eff e (Either SomeException a)
runExcB9 :: Eff (ExcB9 : e) a -> Eff e (Either SomeException a)
runExcB9 = Eff (ExcB9 : e) a -> Eff e (Either SomeException a)
forall e (r :: [* -> *]) a. Eff (Exc e : r) a -> Eff r (Either e a)
runError

-- | Run an `ExcB9` and rethrow the exception with `error`.
--
-- @since 0.5.64
errorOnException :: Lifted IO e => Eff (ExcB9 ': e) a -> Eff e a
errorOnException :: Eff (ExcB9 : e) a -> Eff e a
errorOnException = Eff (ExcB9 : e) a -> Eff e (Either SomeException a)
forall e (r :: [* -> *]) a. Eff (Exc e : r) a -> Eff r (Either e a)
runError (Eff (ExcB9 : e) a -> Eff e (Either SomeException a))
-> (Either SomeException a -> Eff e a)
-> Eff (ExcB9 : e) a
-> Eff e a
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> (SomeException -> Eff e a)
-> (a -> Eff e a) -> Either SomeException a -> Eff e a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (IO a -> Eff e a
forall (m :: * -> *) (r :: [* -> *]) a.
Lifted m r =>
m a -> Eff r a
lift (IO a -> Eff e a)
-> (SomeException -> IO a) -> SomeException -> Eff e a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SomeException -> IO a
forall a e. Exception e => e -> a
IOExc.throw) a -> Eff e a
forall (f :: * -> *) a. Applicative f => a -> f a
pure

-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects
--
-- @since 0.5.64
throwSomeException :: (Member ExcB9 e, Exception x) => x -> Eff e a
throwSomeException :: x -> Eff e a
throwSomeException = SomeException -> Eff e a
forall e (r :: [* -> *]) a. Member (Exc e) r => e -> Eff r a
throwError (SomeException -> Eff e a) -> (x -> SomeException) -> x -> Eff e a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. x -> SomeException
forall e. Exception e => e -> SomeException
toException

-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects
--
-- @since 0.5.64
throwSomeException_ :: (Member ExcB9 e, Exception x) => x -> Eff e ()
throwSomeException_ :: x -> Eff e ()
throwSomeException_ = SomeException -> Eff e ()
forall e (r :: [* -> *]). Member (Exc e) r => e -> Eff r ()
throwError_ (SomeException -> Eff e ())
-> (x -> SomeException) -> x -> Eff e ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. x -> SomeException
forall e. Exception e => e -> SomeException
toException

-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects
--
-- @since 0.5.64
throwB9Error :: Member ExcB9 e => String -> Eff e a
throwB9Error :: String -> Eff e a
throwB9Error = B9Error -> Eff e a
forall (e :: [* -> *]) x a.
(Member ExcB9 e, Exception x) =>
x -> Eff e a
throwSomeException (B9Error -> Eff e a) -> (String -> B9Error) -> String -> Eff e a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> B9Error
MkB9Error

-- | 'SomeException' wrapped into 'Exc'ecption 'Eff'ects
--
-- @since 0.5.64
throwB9Error_ :: Member ExcB9 e => String -> Eff e ()
throwB9Error_ :: String -> Eff e ()
throwB9Error_ = B9Error -> Eff e ()
forall (e :: [* -> *]) x.
(Member ExcB9 e, Exception x) =>
x -> Eff e ()
throwSomeException_ (B9Error -> Eff e ()) -> (String -> B9Error) -> String -> Eff e ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> B9Error
MkB9Error

-- | Catch exceptions.
--
-- @since 0.5.64
catchB9Error ::
  Member ExcB9 e => Eff e a -> (SomeException -> Eff e a) -> Eff e a
catchB9Error :: Eff e a -> (SomeException -> Eff e a) -> Eff e a
catchB9Error = Eff e a -> (SomeException -> Eff e a) -> Eff e a
forall e (r :: [* -> *]) a.
Member (Exc e) r =>
Eff r a -> (e -> Eff r a) -> Eff r a
catchError

-- | Catch exceptions and return them via 'Either'.
--
-- @since 0.5.64
catchB9ErrorAsEither ::
  Member ExcB9 e => Eff e a -> Eff e (Either SomeException a)
catchB9ErrorAsEither :: Eff e a -> Eff e (Either SomeException a)
catchB9ErrorAsEither Eff e a
x = Eff e (Either SomeException a)
-> (SomeException -> Eff e (Either SomeException a))
-> Eff e (Either SomeException a)
forall (e :: [* -> *]) a.
Member ExcB9 e =>
Eff e a -> (SomeException -> Eff e a) -> Eff e a
catchB9Error (a -> Either SomeException a
forall a b. b -> Either a b
Right (a -> Either SomeException a)
-> Eff e a -> Eff e (Either SomeException a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Eff e a
x) (Either SomeException a -> Eff e (Either SomeException a)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either SomeException a -> Eff e (Either SomeException a))
-> (SomeException -> Either SomeException a)
-> SomeException
-> Eff e (Either SomeException a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SomeException -> Either SomeException a
forall a b. a -> Either a b
Left)

-- | Always execute an action and rethrow any exceptions caught.
--
-- @since 1.0.0
finallyB9 :: Member ExcB9 e => Eff e a -> Eff e () -> Eff e a
finallyB9 :: Eff e a -> Eff e () -> Eff e a
finallyB9 Eff e a
mainAction Eff e ()
cleanupAction =
  Eff e a -> (SomeException -> Eff e a) -> Eff e a
forall (e :: [* -> *]) a.
Member ExcB9 e =>
Eff e a -> (SomeException -> Eff e a) -> Eff e a
catchB9Error
    ( do
        a
res <- Eff e a
mainAction
        Eff e ()
cleanupAction
        a -> Eff e a
forall (m :: * -> *) a. Monad m => a -> m a
return a
res
    )
    (\SomeException
e -> Eff e ()
cleanupAction Eff e () -> Eff e a -> Eff e a
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> SomeException -> Eff e a
forall (e :: [* -> *]) x a.
(Member ExcB9 e, Exception x) =>
x -> Eff e a
throwSomeException SomeException
e)