amazonka-2.0: Comprehensive Amazon Web Services SDK.
Copyright(c) 2023 Brendan Hay
LicenseMozilla Public License, v. 2.0.
MaintainerBrendan Hay <brendan.g.hay+amazonka@gmail.com>
Stabilityhighly experimental
Portabilitynon-portable (GHC extensions)
Safe HaskellSafe-Inferred
LanguageHaskell2010

Amazonka.Env.Hooks

Description

Hooks carried within an Env, allowing ad-hoc injection of different behaviour during Amazonka's request/response cycle. Hooks are currently experimental, but Amazonka uses the Hooks API to implement its default logging, and you can add your own behaviour here as well. Some examples of things hooks can do:

  • Log all requests Amazonka makes to a separate log, in order to audit which IAM permissions your program actually needs (see requestHook);
  {-# LANGAUGE OverloadedLabels #-}
  import Amazonka
  import Amazonka.Env.Hooks
  import Data.Generics.Labels ()

  main :: IO ()
  main = do
    env <- newEnv discover
      <&> #hooks %~ requestHook (addAWSRequestHook $ \_env req -> req <$ logRequest req)
    ...

  logRequest :: AWSRequest a => a -> IO ()
  logRequest = ...
  
  {-# LANGAUGE OverloadedLabels #-}
  import Amazonka
  import Amazonka.Env.Hooks
  import Data.Generics.Labels ()

  main :: IO ()
  main = do
    env <- newEnv discover
      <&> #hooks %~ configuredRequestHook (addHook $ \_env req -> req & #headers %~ addXRayIdHeader)
    ...

  -- The actual header would normally come from whatever calls into your program,
  -- or you would randomly generate one yourself (hooks run in IO).
  addXRayIdHeader :: [Header] -> [Header]
  addXRayIdHeader = ...
  
  • Selectively silence certain expected errors, such as DynamoDB's ConditionalCheckFailedException (errorHook and silenceError)
  {-# LANGAUGE OverloadedLabels #-}
  import Amazonka
  import Amazonka.Env.Hooks
  import qualified Amazonka.DynamoDB as DynamoDB
  import Data.Generics.Labels ()

  main :: IO ()
  main = do
    env <- newEnv discover
    putItemResponse <- runResourceT $
      send
        (env & #hooks %~ errorHook (silenceError DynamoDB._ConditionalCheckFailedException))
        (DynamoDB.newPutItem ...)
    ...
  

Most functions with names ending in Hook (requestHook, etc.) are intended for use with lenses: partially apply them to get a function Hook a -> Hook a that can go on the RHS of (%~) (the lens modify function). You then use functions like addHookFor to selectively extend the hooks used at any particular time.

Names ending in _ (Hook_, addHookFor_, etc.) concern hooks that return () instead of the hook's input type. These hooks respond to some event but lack the ability to change Amazonka's behaviour; either because it is unsafe to do so, or because it is difficult to do anything meaningful with the updated value.

The request/response flow for a standard send looks like this:

    send (req :: AWSRequest a => a)
                 |
                 V
        Run Hook: request
                 |
                 V
Amazonka: configure req into "Request a"
 (Amazonka-specific HTTP request type)
                 |
                 V
    Run Hook: configuredRequest
                 |
                 V
Amazonka: sign request, turn into standard
    Network.HTTP.Client.Request
                 |
                 +-<---------------------------------.
                 V                                   |
    Run Hook: signedRequest                          |
                 |                                   |
                 V                                   |
    Run Hook: clientRequest                          |
                 |                                   |
                 V                                   |
    Amazonka: send request to AWS           Run Hook: requestRetry
                 |                                   ^
                 V                                   |
    Run Hook: clientResponse                         |
                 |                                   |
                 V                                   |
    Run Hook: rawResponseBody                        |
                 |                                   |
                 V                                   |
    Amazonka: was error? ------------------.         |
                 |            Yes          |         |
                 |                         V         |
                 | No               Run Hook: error  |
                 |                    (NotFinal)     |
                 |                         |         |
                 +-<-----------------------'         |
                 V                                   |
    Amazonka: should retry? -------------------------'
                 |            Yes
                 | No
                 V
    Amazonka: was error? ------------------.
                 |            Yes          |
                 |                         V
                 | No                      |
                 |                         |
    Run Hook: response              Run Hook: error
                 |                     (Final)
                 |                         |
                 V                         |
    Amazonka: parse response               |
                 |                         |
                 +-<-----------------------'
                 V
    Amazonka: return result
Synopsis

Documentation

type Hook a = forall withAuth. Env' withAuth -> a -> IO a Source #

A hook that returns an updated version of its arguments.

type Hook_ a = forall withAuth. Env' withAuth -> a -> IO () Source #

A hook that cannot return an updated version of its argument.

data Hooks Source #

Constructors

Hooks 

Fields

data Finality Source #

Indicates whether an error hook is potentially going to be retried.

See: $sel:error:Hooks

Constructors

NotFinal 
Final 

Instances

Instances details
Bounded Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

Enum Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

Generic Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

Associated Types

type Rep Finality :: Type -> Type #

Methods

from :: Finality -> Rep Finality x #

to :: Rep Finality x -> Finality #

Show Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

Eq Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

Ord Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

type Rep Finality Source # 
Instance details

Defined in Amazonka.Env.Hooks

type Rep Finality = D1 ('MetaData "Finality" "Amazonka.Env.Hooks" "amazonka-2.0-48plDWnPMAk3PGO79vdSa0" 'False) (C1 ('MetaCons "NotFinal" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Final" 'PrefixI 'False) (U1 :: Type -> Type))

Updating members of Hooks

requestHook :: (forall a. (AWSRequest a, Typeable a) => Hook a -> Hook a) -> Hooks -> Hooks Source #

waitHook :: (forall a. (AWSRequest a, Typeable a) => Hook (Wait a) -> Hook (Wait a)) -> Hooks -> Hooks Source #

configuredRequestHook :: (forall a. (AWSRequest a, Typeable a) => Hook (Request a) -> Hook (Request a)) -> Hooks -> Hooks Source #

signedRequestHook :: (forall a. (AWSRequest a, Typeable a) => Hook_ (Signed a) -> Hook_ (Signed a)) -> Hooks -> Hooks Source #

Functions to use with the ones above

noHook :: Hook a -> Hook a Source #

Turn a Hook a into another Hook a that does nothing.

  • - Example: remove all request hooks: requestHook noHook :: Hooks -> Hooks

noHook_ :: Hook_ a -> Hook_ a Source #

Turn a Hook_ a into another Hook_ a that does nothing.

-- Example: Remove all response hooks:
responseHook noHook_ :: Hooks -> Hooks

addHook :: Typeable a => Hook a -> Hook a -> Hook a Source #

Unconditionally add a Hook a to the chain of hooks. If you need to do something with specific request types, you want addHookFor, instead.

addHook_ :: Typeable a => Hook_ a -> Hook_ a -> Hook_ a Source #

Unconditionally add a Hook_ a to the chain of hooks. If you need to do something with specific request types, you want addHookFor_, instead.

addAWSRequestHook :: (AWSRequest a, Typeable a) => Hook a -> Hook a -> Hook a Source #

Like addHook, adds an unconditional hook, but it also captures the AWSRequest a constraint. Useful for handling every AWS request type in a generic way.

addHookFor :: forall a b. (Typeable a, Typeable b) => Hook a -> Hook b -> Hook b Source #

addHookFor @a newHook oldHook When a and b are the same type, run the given 'Hook a' after all others, otherwise only run the existing hooks.

-- Example: Run getObjectRequestHook on anything that is a GetObjectRequest:
requestHook (addHookFor @GetObjectRequest getObjectRequestHook) :: Hooks -> Hooks

addHookFor_ :: forall a b. (Typeable a, Typeable b) => Hook_ a -> Hook_ b -> Hook_ b Source #

When a and b are the same type, run the given 'Hook_ a' after all other hooks have run.

-- Example: Run aSignedRequestHook on anything that is a Signed GetObjectRequest:
requestHook (addHookFor_ @(Signed GetObjectRequest) aSignedRequestHook) :: Hooks -> Hooks

removeHooksFor :: forall a b. (Typeable a, Typeable b) => Hook b -> Hook b Source #

When a and b are the same type, do not call any more hooks.

-- Example: Prevent any request hooks from running against a PutObjectRequest:
requestHook (removeHooksFor @PutObjectRequest) :: Hooks -> Hooks

removeHooksFor_ :: forall a b. (Typeable a, Typeable b) => Hook_ b -> Hook_ b Source #

When a and b are the same type, do not call any more hooks.

-- Example: Prevent any error hooks from running against errors caused by a PutObjectRequest:
errorHook (removeHooksFor @(Finality, Request PutObjectRequest, Error)) :: Hooks -> Hooks

Specialty combinators

silenceError :: Getting Any Error e -> Hook_ (Finality, Request a, Error) -> Hook_ (Finality, Request a, Error) Source #

Run the wrapped hook unless the given Fold or Traversal matches the error. You will probably want to use this with the error matchers defined by each service binding, allowing you to selectively silence specific errors:

-- Assuming `env :: Amazonka.Env` and `putRequest :: DynamoDB.PutRequest`,
-- this silences a single type of error for a single call:
send (env & #hooks %~ errorHook (silenceError DynamoDB._ConditionalCheckFailedException))
silenceError :: Getter Error e     -> Hook_ (Finality, Request a, Error) -> Hook_ (Finality, Request a, Error)
silenceError :: Fold Error e       -> Hook_ (Finality, Request a, Error) -> Hook_ (Finality, Request a, Error)
silenceError :: Iso' Error e       -> Hook_ (Finality, Request a, Error) -> Hook_ (Finality, Request a, Error)
silenceError :: Lens' Error e      -> Hook_ (Finality, Request a, Error) -> Hook_ (Finality, Request a, Error)
silenceError :: Traversal' Error e -> Hook_ (Finality, Request a, Error) -> Hook_ (Finality, Request a, Error)

Building Hooks

addLoggingHooks :: Hooks -> Hooks Source #

Add default logging hooks. The default Env' from newEnv already has logging hooks installed, so you probably only want this if you are building your own Hooks from scratch.

noHooks :: Hooks Source #

Empty Hooks structure which returns everything unmodified.