{-|
Module      : Network.Nakadi.Internal.Retry
Description : Nakadi Client Retry Mechanism
Copyright   : (c) Moritz Schulte 2017, 2018
License     : BSD3
Maintainer  : mtesseract@silverratio.net
Stability   : experimental
Portability : POSIX

This module provides the basic retry mechanism via the retry package.
-}

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase       #-}

module Network.Nakadi.Internal.Retry
  ( retryAction
  ) where

import           Network.Nakadi.Internal.Prelude

import           Control.Lens
import           Control.Retry
import           Network.HTTP.Client
import           Network.HTTP.Types.Status
import qualified Network.Nakadi.Internal.Lenses       as L
import           Network.Nakadi.Internal.Types.Config

-- | Invokes the HTTP Error Callback set in the configuration for the
-- provided 'Request', 'HttpException' and 'RetryStatus'. If no
-- callback is set, this is no-op.
invokeHttpErrorCallback ::
  (MonadIO b)
  => Config b
  -> Request
  -> HttpException
  -> RetryStatus
  -> b ()
invokeHttpErrorCallback config req exn retryStatus =
  case config^.L.httpErrorCallback of
    Just cb -> do
      finalFailure <- isFinalFailure
      cb req exn retryStatus finalFailure
    Nothing -> pure ()

  where isFinalFailure = do
          let policy = liftIORetryPolicy (config^.L.retryPolicy)
          applyPolicy policy retryStatus >>= \case
            Just _  -> pure False
            Nothing -> pure True


liftIORetryPolicy :: MonadIO b => RetryPolicyM IO -> RetryPolicyM b
liftIORetryPolicy rp = RetryPolicyM $ liftIO . getRetryPolicyM rp

-- | Try to execute the provided IO action using the provided retry
-- policy. If executing the IO action raises specific exceptions of
-- type 'HttpException', the action will be potentially retried
-- (depending on the retry policy).
retryAction ::
  (MonadIO b, MonadMask b)
  => Config b
  -> Request
  -> (Request -> b a)
  -> b a
retryAction config req ma =
  let policy = liftIORetryPolicy (config^.L.retryPolicy)
  in recovering policy [handlerHttp] (\_retryStatus -> ma req)

  where handlerHttp retryStatus = Handler $ \exn -> do
          invokeHttpErrorCallback config req exn retryStatus
          pure $ shouldRetry exn
        shouldRetry (HttpExceptionRequest _ exceptionContent) =
          case exceptionContent of
            StatusCodeException response _ ->
              responseStatus response `elem` [status500, status503]
            ResponseTimeout ->
              True
            ConnectionTimeout ->
              True
            ConnectionFailure _ ->
              True
            InternalException _ ->
              True
            ConnectionClosed ->
              True
            _ ->
              False

        shouldRetry _ = False