pub mod context;
pub mod error;
use crate::client::interceptors::context::wrappers::{
FinalizerInterceptorContextMut, FinalizerInterceptorContextRef,
};
use crate::config_bag::ConfigBag;
use aws_smithy_types::error::display::DisplayErrorContext;
pub use context::{
wrappers::{
AfterDeserializationInterceptorContextMut, AfterDeserializationInterceptorContextRef,
BeforeDeserializationInterceptorContextMut, BeforeDeserializationInterceptorContextRef,
BeforeSerializationInterceptorContextMut, BeforeSerializationInterceptorContextRef,
BeforeTransmitInterceptorContextMut, BeforeTransmitInterceptorContextRef,
},
InterceptorContext,
};
use context::{Error, Input, Output};
pub use error::{BoxError, InterceptorError};
use std::ops::Deref;
use std::sync::Arc;
macro_rules! interceptor_trait_fn {
($name:ident, $phase:ident, $docs:tt) => {
#[doc = $docs]
fn $name(&self, context: &$phase<'_>, cfg: &mut ConfigBag) -> Result<(), BoxError> {
let _ctx = context;
let _cfg = cfg;
Ok(())
}
};
(mut $name:ident, $phase:ident, $docs:tt) => {
#[doc = $docs]
fn $name(&self, context: &mut $phase<'_>, cfg: &mut ConfigBag) -> Result<(), BoxError> {
let _ctx = context;
let _cfg = cfg;
Ok(())
}
};
}
pub trait Interceptor: std::fmt::Debug {
interceptor_trait_fn!(
read_before_execution,
BeforeSerializationInterceptorContextRef,
"
A hook called at the start of an execution, before the SDK
does anything else.
**When:** This will **ALWAYS** be called once per execution. The duration
between invocation of this hook and `after_execution` is very close
to full duration of the execution.
**Available Information:** The [InterceptorContext::input()] is
**ALWAYS** available. Other information **WILL NOT** be available.
**Error Behavior:** Errors raised by this hook will be stored
until all interceptors have had their `before_execution` invoked.
Other hooks will then be skipped and execution will jump to
`modify_before_completion` with the raised error as the
[InterceptorContext::output_or_error()]. If multiple
`before_execution` methods raise errors, the latest
will be used and earlier ones will be logged and dropped.
"
);
interceptor_trait_fn!(
mut modify_before_serialization,
BeforeSerializationInterceptorContextMut,
"
A hook called before the input message is marshalled into a
transport message.
This method has the ability to modify and return a new
request message of the same type.
**When:** This will **ALWAYS** be called once per execution, except when a
failure occurs earlier in the request pipeline.
**Available Information:** The [InterceptorContext::input()] is
**ALWAYS** available. This request may have been modified by earlier
`modify_before_serialization` hooks, and may be modified further by
later hooks. Other information **WILL NOT** be available.
**Error Behavior:** If errors are raised by this hook,
execution will jump to `modify_before_completion` with the raised
error as the [InterceptorContext::output_or_error()].
**Return Constraints:** The input message returned by this hook
MUST be the same type of input message passed into this hook.
If not, an error will immediately be raised.
"
);
interceptor_trait_fn!(
read_before_serialization,
BeforeSerializationInterceptorContextRef,
"
A hook called before the input message is marshalled
into a transport
message.
**When:** This will **ALWAYS** be called once per execution, except when a
failure occurs earlier in the request pipeline. The
duration between invocation of this hook and `after_serialization` is
very close to the amount of time spent marshalling the request.
**Available Information:** The [InterceptorContext::input()] is
**ALWAYS** available. Other information **WILL NOT** be available.
**Error Behavior:** If errors are raised by this hook,
execution will jump to `modify_before_completion` with the raised
error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
read_after_serialization,
BeforeTransmitInterceptorContextRef,
"
/// A hook called after the input message is marshalled into
/// a transport message.
///
/// **When:** This will **ALWAYS** be called once per execution, except when a
/// failure occurs earlier in the request pipeline. The duration
/// between invocation of this hook and `before_serialization` is very
/// close to the amount of time spent marshalling the request.
///
/// **Available Information:** The [InterceptorContext::input()]
/// and [InterceptorContext::request()] are **ALWAYS** available.
/// Other information **WILL NOT** be available.
///
/// **Error Behavior:** If errors are raised by this hook,
/// execution will jump to `modify_before_completion` with the raised
/// error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
mut modify_before_retry_loop,
BeforeTransmitInterceptorContextMut,
"
A hook called before the retry loop is entered. This method
has the ability to modify and return a new transport request
message of the same type, except when a failure occurs earlier in the request pipeline.
**Available Information:** The [InterceptorContext::input()]
and [InterceptorContext::request()] are **ALWAYS** available.
Other information **WILL NOT** be available.
**Error Behavior:** If errors are raised by this hook,
execution will jump to `modify_before_completion` with the raised
error as the [InterceptorContext::output_or_error()].
**Return Constraints:** The transport request message returned by this
hook MUST be the same type of request message passed into this hook
If not, an error will immediately be raised.
"
);
interceptor_trait_fn!(
read_before_attempt,
BeforeTransmitInterceptorContextRef,
"
A hook called before each attempt at sending the transmission
request message to the service.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method will be
called multiple times in the event of retries.
**Available Information:** The [InterceptorContext::input()]
and [InterceptorContext::request()] are **ALWAYS** available.
Other information **WILL NOT** be available. In the event of retries,
the `InterceptorContext` will not include changes made in previous
attempts (e.g. by request signers or other interceptors).
**Error Behavior:** Errors raised by this hook will be stored
until all interceptors have had their `before_attempt` invoked.
Other hooks will then be skipped and execution will jump to
`modify_before_attempt_completion` with the raised error as the
[InterceptorContext::output_or_error()]. If multiple
`before_attempt` methods raise errors, the latest will be used
and earlier ones will be logged and dropped.
"
);
interceptor_trait_fn!(
mut modify_before_signing,
BeforeTransmitInterceptorContextMut,
"
A hook called before the transport request message is signed.
This method has the ability to modify and return a new transport
request message of the same type.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries.
**Available Information:** The [InterceptorContext::input()]
and [InterceptorContext::request()] are **ALWAYS** available.
The `http::Request` may have been modified by earlier
`modify_before_signing` hooks, and may be modified further by later
hooks. Other information **WILL NOT** be available. In the event of
retries, the `InterceptorContext` will not include changes made
in previous attempts
(e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
**Return Constraints:** The transport request message returned by this
hook MUST be the same type of request message passed into this hook
If not, an error will immediately be raised.
"
);
interceptor_trait_fn!(
read_before_signing,
BeforeTransmitInterceptorContextRef,
"
A hook called before the transport request message is signed.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries. The duration between
invocation of this hook and `after_signing` is very close to
the amount of time spent signing the request.
**Available Information:** The [InterceptorContext::input()]
and [InterceptorContext::request()] are **ALWAYS** available.
Other information **WILL NOT** be available. In the event of retries,
the `InterceptorContext` will not include changes made in previous
attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
read_after_signing,
BeforeTransmitInterceptorContextRef,
"
A hook called after the transport request message is signed.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries. The duration between
invocation of this hook and `before_signing` is very close to
the amount of time spent signing the request.
**Available Information:** The [InterceptorContext::input()]
and [InterceptorContext::request()] are **ALWAYS** available.
Other information **WILL NOT** be available. In the event of retries,
the `InterceptorContext` will not include changes made in previous
attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
mut modify_before_transmit,
BeforeTransmitInterceptorContextMut,
"
/// A hook called before the transport request message is sent to the
/// service. This method has the ability to modify and return
/// a new transport request message of the same type.
///
/// **When:** This will **ALWAYS** be called once per attempt, except when a
/// failure occurs earlier in the request pipeline. This method may be
/// called multiple times in the event of retries.
///
/// **Available Information:** The [InterceptorContext::input()]
/// and [InterceptorContext::request()] are **ALWAYS** available.
/// The `http::Request` may have been modified by earlier
/// `modify_before_transmit` hooks, and may be modified further by later
/// hooks. Other information **WILL NOT** be available.
/// In the event of retries, the `InterceptorContext` will not include
/// changes made in previous attempts (e.g. by request signers or
other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
**Return Constraints:** The transport request message returned by this
hook MUST be the same type of request message passed into this hook
If not, an error will immediately be raised.
"
);
interceptor_trait_fn!(
read_before_transmit,
BeforeTransmitInterceptorContextRef,
"
A hook called before the transport request message is sent to the
service.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries. The duration between
invocation of this hook and `after_transmit` is very close to
the amount of time spent communicating with the service.
Depending on the protocol, the duration may not include the
time spent reading the response data.
**Available Information:** The [InterceptorContext::input()]
and [InterceptorContext::request()] are **ALWAYS** available.
Other information **WILL NOT** be available. In the event of retries,
the `InterceptorContext` will not include changes made in previous
attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
read_after_transmit,
BeforeDeserializationInterceptorContextRef,
"
A hook called after the transport request message is sent to the
service and a transport response message is received.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries. The duration between
invocation of this hook and `before_transmit` is very close to
the amount of time spent communicating with the service.
Depending on the protocol, the duration may not include the time
spent reading the response data.
**Available Information:** The [InterceptorContext::input()],
[InterceptorContext::request()] and
[InterceptorContext::response()] are **ALWAYS** available.
Other information **WILL NOT** be available. In the event of retries,
the `InterceptorContext` will not include changes made in previous
attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
mut modify_before_deserialization,
BeforeDeserializationInterceptorContextMut,
"
A hook called before the transport response message is unmarshalled.
This method has the ability to modify and return a new transport
response message of the same type.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries.
**Available Information:** The [InterceptorContext::input()],
[InterceptorContext::request()] and
[InterceptorContext::response()] are **ALWAYS** available.
The transmit_response may have been modified by earlier
`modify_before_deserialization` hooks, and may be modified further by
later hooks. Other information **WILL NOT** be available. In the event of
retries, the `InterceptorContext` will not include changes made in
previous attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the
[InterceptorContext::output_or_error()].
**Return Constraints:** The transport response message returned by this
hook MUST be the same type of response message passed into
this hook. If not, an error will immediately be raised.
"
);
interceptor_trait_fn!(
read_before_deserialization,
BeforeDeserializationInterceptorContextRef,
"
A hook called before the transport response message is unmarshalled
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. This method may be
called multiple times in the event of retries. The duration between
invocation of this hook and `after_deserialization` is very close
to the amount of time spent unmarshalling the service response.
Depending on the protocol and operation, the duration may include
the time spent downloading the response data.
**Available Information:** The [InterceptorContext::input()],
[InterceptorContext::request()] and
[InterceptorContext::response()] are **ALWAYS** available.
Other information **WILL NOT** be available. In the event of retries,
the `InterceptorContext` will not include changes made in previous
attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion`
with the raised error as the [InterceptorContext::output_or_error()].
"
);
interceptor_trait_fn!(
read_after_deserialization,
AfterDeserializationInterceptorContextRef,
"
A hook called after the transport response message is unmarshalled.
**When:** This will **ALWAYS** be called once per attempt, except when a
failure occurs earlier in the request pipeline. The duration
between invocation of this hook and `before_deserialization` is
very close to the amount of time spent unmarshalling the
service response. Depending on the protocol and operation,
the duration may include the time spent downloading
the response data.
**Available Information:** The [InterceptorContext::input()],
[InterceptorContext::request()],
[InterceptorContext::response()] and
[InterceptorContext::output_or_error()] are **ALWAYS** available. In the event
of retries, the `InterceptorContext` will not include changes made
in previous attempts (e.g. by request signers or other interceptors).
**Error Behavior:** If errors are raised by this
hook, execution will jump to `modify_before_attempt_completion` with
the raised error as the [InterceptorContext::output_or_error()].
"
);
fn modify_before_attempt_completion(
&self,
context: &mut FinalizerInterceptorContextMut<'_>,
cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let _ctx = context;
let _cfg = cfg;
Ok(())
}
fn read_after_attempt(
&self,
context: &FinalizerInterceptorContextRef<'_>,
cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let _ctx = context;
let _cfg = cfg;
Ok(())
}
fn modify_before_completion(
&self,
context: &mut FinalizerInterceptorContextMut<'_>,
cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let _ctx = context;
let _cfg = cfg;
Ok(())
}
fn read_after_execution(
&self,
context: &FinalizerInterceptorContextRef<'_>,
cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let _ctx = context;
let _cfg = cfg;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SharedInterceptor(Arc<dyn Interceptor + Send + Sync>);
impl SharedInterceptor {
pub fn new(interceptor: impl Interceptor + Send + Sync + 'static) -> Self {
Self(Arc::new(interceptor))
}
}
impl AsRef<dyn Interceptor> for SharedInterceptor {
fn as_ref(&self) -> &(dyn Interceptor + 'static) {
self.0.as_ref()
}
}
impl From<Arc<dyn Interceptor + Send + Sync + 'static>> for SharedInterceptor {
fn from(interceptor: Arc<dyn Interceptor + Send + Sync + 'static>) -> Self {
SharedInterceptor(interceptor)
}
}
impl Deref for SharedInterceptor {
type Target = Arc<dyn Interceptor + Send + Sync>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone, Default)]
pub struct InterceptorRegistrar(Vec<SharedInterceptor>);
impl InterceptorRegistrar {
pub fn register(&mut self, interceptor: SharedInterceptor) {
self.0.push(interceptor);
}
}
impl Extend<SharedInterceptor> for InterceptorRegistrar {
fn extend<T: IntoIterator<Item = SharedInterceptor>>(&mut self, iter: T) {
for interceptor in iter {
self.register(interceptor);
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Interceptors {
client_interceptors: InterceptorRegistrar,
operation_interceptors: InterceptorRegistrar,
}
macro_rules! interceptor_impl_fn {
(mut $interceptor:ident) => {
pub fn $interceptor(
&self,
ctx: &mut InterceptorContext,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let mut ctx = ctx.into();
for interceptor in self.interceptors() {
if let Err(new_error) = interceptor.$interceptor(&mut ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::$interceptor)
}
};
(ref $interceptor:ident) => {
pub fn $interceptor(
&self,
ctx: &InterceptorContext,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let ctx = ctx.into();
for interceptor in self.interceptors() {
if let Err(new_error) = interceptor.$interceptor(&ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::$interceptor)
}
};
}
impl Interceptors {
pub fn new() -> Self {
Self::default()
}
fn interceptors(&self) -> impl Iterator<Item = &SharedInterceptor> {
self.client_interceptors
.0
.iter()
.chain(self.operation_interceptors.0.iter())
}
pub fn client_interceptors_mut(&mut self) -> &mut InterceptorRegistrar {
&mut self.client_interceptors
}
pub fn operation_interceptors_mut(&mut self) -> &mut InterceptorRegistrar {
&mut self.operation_interceptors
}
pub fn client_read_before_execution(
&self,
ctx: &InterceptorContext<Input, Output, Error>,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let ctx: BeforeSerializationInterceptorContextRef<'_> = ctx.into();
for interceptor in self.client_interceptors.0.iter() {
if let Err(new_error) = interceptor.read_before_execution(&ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::read_before_execution)
}
pub fn operation_read_before_execution(
&self,
ctx: &InterceptorContext<Input, Output, Error>,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let ctx: BeforeSerializationInterceptorContextRef<'_> = ctx.into();
for interceptor in self.operation_interceptors.0.iter() {
if let Err(new_error) = interceptor.read_before_execution(&ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::read_before_execution)
}
interceptor_impl_fn!(mut modify_before_serialization);
interceptor_impl_fn!(ref read_before_serialization);
interceptor_impl_fn!(ref read_after_serialization);
interceptor_impl_fn!(mut modify_before_retry_loop);
interceptor_impl_fn!(ref read_before_attempt);
interceptor_impl_fn!(mut modify_before_signing);
interceptor_impl_fn!(ref read_before_signing);
interceptor_impl_fn!(ref read_after_signing);
interceptor_impl_fn!(mut modify_before_transmit);
interceptor_impl_fn!(ref read_before_transmit);
interceptor_impl_fn!(ref read_after_transmit);
interceptor_impl_fn!(mut modify_before_deserialization);
interceptor_impl_fn!(ref read_before_deserialization);
interceptor_impl_fn!(ref read_after_deserialization);
pub fn modify_before_attempt_completion(
&self,
ctx: &mut InterceptorContext<Input, Output, Error>,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let mut ctx: FinalizerInterceptorContextMut<'_> = ctx.into();
for interceptor in self.interceptors() {
if let Err(new_error) = interceptor.modify_before_attempt_completion(&mut ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::modify_before_attempt_completion)
}
pub fn read_after_attempt(
&self,
ctx: &InterceptorContext<Input, Output, Error>,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let ctx: FinalizerInterceptorContextRef<'_> = ctx.into();
for interceptor in self.interceptors() {
if let Err(new_error) = interceptor.read_after_attempt(&ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::read_after_attempt)
}
pub fn modify_before_completion(
&self,
ctx: &mut InterceptorContext<Input, Output, Error>,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let mut ctx: FinalizerInterceptorContextMut<'_> = ctx.into();
for interceptor in self.interceptors() {
if let Err(new_error) = interceptor.modify_before_completion(&mut ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::modify_before_completion)
}
pub fn read_after_execution(
&self,
ctx: &InterceptorContext<Input, Output, Error>,
cfg: &mut ConfigBag,
) -> Result<(), InterceptorError> {
let mut result: Result<(), BoxError> = Ok(());
let ctx: FinalizerInterceptorContextRef<'_> = ctx.into();
for interceptor in self.interceptors() {
if let Err(new_error) = interceptor.read_after_execution(&ctx, cfg) {
if let Err(last_error) = result {
tracing::debug!("{}", DisplayErrorContext(&*last_error));
}
result = Err(new_error);
}
}
result.map_err(InterceptorError::read_after_execution)
}
}
#[cfg(test)]
mod tests {
use crate::client::interceptors::{Interceptor, InterceptorRegistrar, SharedInterceptor};
#[derive(Debug)]
struct TestInterceptor;
impl Interceptor for TestInterceptor {}
#[test]
fn register_interceptor() {
let mut registrar = InterceptorRegistrar::default();
registrar.register(SharedInterceptor::new(TestInterceptor));
assert_eq!(1, registrar.0.len());
}
#[test]
fn bulk_register_interceptors() {
let mut registrar = InterceptorRegistrar::default();
let number_of_interceptors = 3;
let interceptors = vec![SharedInterceptor::new(TestInterceptor); number_of_interceptors];
registrar.extend(interceptors);
assert_eq!(number_of_interceptors, registrar.0.len());
}
}