packages feed

retry-effectful-0.1.0.0: src/Effectful/Retry.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

{- | This module provides a Retry effect to adapt
 the @<https://hackage.haskell.org/package/retry retry>@ library for
 the @<https://hackage.haskell.org/package/effectful effectful>@ ecosystem.
-}
module Effectful.Retry (
    -- * Effect
    Retry,
    runRetry,

    -- * Types and Operations
    Control.Retry.RetryPolicyM (..),
    Control.Retry.retryPolicy,
    Control.Retry.retryPolicyDefault,
    Control.Retry.RetryAction (..),
    Control.Retry.toRetryAction,
    Control.Retry.RetryStatus (..),
    Control.Retry.defaultRetryStatus,
    Effectful.Retry.applyPolicy,
    Effectful.Retry.applyAndDelay,

    -- * Applying Retry Policies
    Effectful.Retry.retrying,
    Effectful.Retry.retryingDynamic,
    Effectful.Retry.recovering,
    Effectful.Retry.recoveringDynamic,
    Effectful.Retry.stepping,
    Effectful.Retry.recoverAll,
    Effectful.Retry.skipAsyncExceptions,
    Effectful.Retry.logRetries,
    Control.Retry.defaultLogMsg,
    -- Effectful.Retry.retryOnError,

    -- * Resumable variants
    Effectful.Retry.resumeRetrying,
    Effectful.Retry.resumeRetryingDynamic,
    Effectful.Retry.resumeRecovering,
    Effectful.Retry.resumeRecoveringDynamic,
    Effectful.Retry.resumeRecoverAll,

    -- * Retry Policies
    Control.Retry.constantDelay,
    Control.Retry.exponentialBackoff,
    Control.Retry.fullJitterBackoff,
    Control.Retry.fibonacciBackoff,
    Control.Retry.limitRetries,

    -- * Policy Transformers
    Control.Retry.limitRetriesByDelay,
    Control.Retry.limitRetriesByCumulativeDelay,
    Control.Retry.capDelay,

    -- * Development Helpers
    Control.Retry.simulatePolicy,
    Control.Retry.simulatePolicyPP,
) where

import Effectful
import Effectful.Dispatch.Static
import Effectful.Internal.Env (Env)

import Control.Exception (AsyncException, SomeAsyncException)
import Control.Monad.Catch
import Control.Retry

-- | An empty effect to provide the retry requirements, e.g. MonadIO and MonadMask
data Retry :: Effect

type instance DispatchOf Retry = 'Static 'WithSideEffects
data instance StaticRep Retry = Retry

-- | Peel the 'Retry' effect.
runRetry :: IOE :> es => Eff (Retry : es) a -> Eff es a
runRetry = evalStaticRep Retry

-- effect helper
policyIO :: Env es -> RetryPolicyM (Eff es) -> RetryPolicyM IO
policyIO env (RetryPolicyM policy) = RetryPolicyM $ \s -> unEff (policy s) env

-- effect helper
handlerIO :: Env es -> Handler (Eff es) a -> Handler IO a
handlerIO env (Handler handler) = Handler $ \e -> unEff (handler e) env

-- wrappers
applyPolicy :: Retry :> es => RetryPolicyM (Eff es) -> RetryStatus -> Eff es (Maybe RetryStatus)
applyPolicy policy status = unsafeEff $ \env -> Control.Retry.applyPolicy (policyIO env policy) status

applyAndDelay :: Retry :> es => RetryPolicyM (Eff es) -> RetryStatus -> Eff es (Maybe RetryStatus)
applyAndDelay policy status = unsafeEff $ \env -> Control.Retry.applyAndDelay (policyIO env policy) status

retrying ::
    Retry :> es =>
    RetryPolicyM (Eff es) ->
    (RetryStatus -> b -> Eff es Bool) ->
    (RetryStatus -> Eff es b) ->
    Eff es b
retrying = Effectful.Retry.resumeRetrying defaultRetryStatus

retryingDynamic ::
    Retry :> es =>
    RetryPolicyM (Eff es) ->
    (RetryStatus -> b -> Eff es RetryAction) ->
    (RetryStatus -> Eff es b) ->
    Eff es b
retryingDynamic = Effectful.Retry.resumeRetryingDynamic defaultRetryStatus

resumeRetrying ::
    Retry :> es =>
    RetryStatus ->
    RetryPolicyM (Eff es) ->
    (RetryStatus -> b -> Eff es Bool) ->
    (RetryStatus -> Eff es b) ->
    Eff es b
resumeRetrying retryStatus policy chk =
    Effectful.Retry.resumeRetryingDynamic
        retryStatus
        policy
        (\rs -> fmap toRetryAction . chk rs)

resumeRetryingDynamic ::
    Retry :> es =>
    RetryStatus ->
    RetryPolicyM (Eff es) ->
    (RetryStatus -> b -> Eff es RetryAction) ->
    (RetryStatus -> Eff es b) ->
    Eff es b
resumeRetryingDynamic retryStatus policy chk f =
    unsafeEff $ \env ->
        Control.Retry.resumeRetryingDynamic
            retryStatus
            (policyIO env policy)
            (\rs b -> unEff (chk rs b) env)
            (\rs -> unEff (f rs) env)

recovering ::
    Retry :> es =>
    RetryPolicyM (Eff es) ->
    [RetryStatus -> Handler (Eff es) Bool] ->
    (RetryStatus -> Eff es a) ->
    Eff es a
recovering = Effectful.Retry.resumeRecovering defaultRetryStatus

resumeRecovering ::
    Retry :> es =>
    RetryStatus ->
    RetryPolicyM (Eff es) ->
    [RetryStatus -> Handler (Eff es) Bool] ->
    (RetryStatus -> Eff es a) ->
    Eff es a
resumeRecovering retryStatus policy hs =
    Effectful.Retry.resumeRecoveringDynamic retryStatus policy hs'
  where
    hs' = map (fmap toRetryAction .) hs

recoveringDynamic ::
    Retry :> es =>
    RetryPolicyM (Eff es) ->
    [RetryStatus -> Handler (Eff es) RetryAction] ->
    (RetryStatus -> Eff es a) ->
    Eff es a
recoveringDynamic = Effectful.Retry.resumeRecoveringDynamic defaultRetryStatus

resumeRecoveringDynamic ::
    Retry :> es =>
    RetryStatus ->
    RetryPolicyM (Eff es) ->
    [RetryStatus -> Handler (Eff es) RetryAction] ->
    (RetryStatus -> Eff es a) ->
    Eff es a
resumeRecoveringDynamic retryStatus policy hs f =
    unsafeEff $ \env ->
        Control.Retry.resumeRecoveringDynamic
            retryStatus
            (policyIO env policy)
            (fmap (handlerIO env) <$> hs)
            (\rs -> unEff (f rs) env)

stepping ::
    Retry :> es =>
    RetryPolicyM (Eff es) ->
    [RetryStatus -> Handler (Eff es) Bool] ->
    (RetryStatus -> Eff es ()) ->
    (RetryStatus -> Eff es a) ->
    RetryStatus ->
    Eff es (Maybe a)
stepping policy hs schedule f s =
    unsafeEff $ \env ->
        Control.Retry.stepping
            (policyIO env policy)
            (fmap (handlerIO env) <$> hs)
            (\rs -> unEff (schedule rs) env)
            (\rs -> unEff (f rs) env)
            s

recoverAll ::
    Retry :> es =>
    RetryPolicyM (Eff es) ->
    (RetryStatus -> Eff es a) ->
    Eff es a
recoverAll = Effectful.Retry.resumeRecoverAll defaultRetryStatus

resumeRecoverAll :: Retry :> es => RetryStatus -> RetryPolicyM (Eff es) -> (RetryStatus -> Eff es a) -> Eff es a
resumeRecoverAll retryStatus policy f =
    unsafeEff $ \env ->
        Control.Retry.resumeRecoverAll
            retryStatus
            (policyIO env policy)
            (\rs -> unEff (f rs) env)

skipAsyncExceptions ::
    Retry :> es =>
    [RetryStatus -> Handler (Eff es) Bool]
skipAsyncExceptions = [asyncH, someAsyncH]
  where
    asyncH _ = Handler $ \(_ :: AsyncException) -> return False
    someAsyncH _ = Handler $ \(_ :: SomeAsyncException) -> return False

logRetries ::
    (Retry :> es, Exception e) =>
    (e -> Eff es Bool) ->
    (Bool -> e -> RetryStatus -> Eff es ()) ->
    RetryStatus ->
    Handler (Eff es) Bool
logRetries test reporter status = Handler $ \err -> do
    result <- test err
    reporter result err status
    return result

{- TODO
retryOnError
    :: (Error e :> es, Retry :> es)
    => RetryPolicyM (Eff es)
    -> (RetryStatus -> e -> Eff es Bool)
    -> (RetryStatus -> Eff es a)
    -> Eff es a
retryOnError policy chk f = go defaultRetryStatus
  where
    go stat = do
-}