use crate::client::interceptors::context::InterceptorContext;
use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
use std::fmt::Debug;
use std::time::Duration;
use tracing::trace;
pub use aws_smithy_types::retry::ErrorKind;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShouldAttempt {
Yes,
No,
YesAfterDelay(Duration),
}
#[cfg(feature = "test-util")]
impl ShouldAttempt {
pub fn expect_delay(self) -> Duration {
match self {
ShouldAttempt::YesAfterDelay(delay) => delay,
_ => panic!("Expected this to be the `YesAfterDelay` variant but it was the `{self:?}` variant instead"),
}
}
}
pub trait RetryStrategy: Send + Sync + Debug {
fn should_attempt_initial_request(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<ShouldAttempt, BoxError>;
fn should_attempt_retry(
&self,
context: &InterceptorContext,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<ShouldAttempt, BoxError>;
}
#[derive(Clone, Debug)]
pub struct SharedRetryStrategy(Arc<dyn RetryStrategy>);
impl SharedRetryStrategy {
pub fn new(retry_strategy: impl RetryStrategy + 'static) -> Self {
Self(Arc::new(retry_strategy))
}
}
impl RetryStrategy for SharedRetryStrategy {
fn should_attempt_initial_request(
&self,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<ShouldAttempt, BoxError> {
self.0
.should_attempt_initial_request(runtime_components, cfg)
}
fn should_attempt_retry(
&self,
context: &InterceptorContext,
runtime_components: &RuntimeComponents,
cfg: &ConfigBag,
) -> Result<ShouldAttempt, BoxError> {
self.0
.should_attempt_retry(context, runtime_components, cfg)
}
}
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum RetryReason {
Error(ErrorKind),
Explicit(Duration),
}
pub trait ClassifyRetry: Send + Sync + Debug {
fn classify_retry(&self, ctx: &InterceptorContext) -> Option<RetryReason>;
fn name(&self) -> &'static str;
}
#[derive(Clone, Debug)]
pub struct RetryClassifiers {
inner: Vec<Arc<dyn ClassifyRetry>>,
}
impl RetryClassifiers {
pub fn new() -> Self {
Self {
inner: Vec::with_capacity(1),
}
}
pub fn with_classifier(mut self, retry_classifier: impl ClassifyRetry + 'static) -> Self {
self.inner.push(Arc::new(retry_classifier));
self
}
}
impl ClassifyRetry for RetryClassifiers {
fn classify_retry(&self, ctx: &InterceptorContext) -> Option<RetryReason> {
self.inner.iter().find_map(|cr| {
let maybe_reason = cr.classify_retry(ctx);
match maybe_reason.as_ref() {
Some(reason) => trace!(
"\"{}\" classifier classified error as {:?}",
cr.name(),
reason
),
None => trace!("\"{}\" classifier ignored the error", cr.name()),
};
maybe_reason
})
}
fn name(&self) -> &'static str {
"Collection of Classifiers"
}
}
#[derive(Debug, Clone, Copy)]
pub struct RequestAttempts {
attempts: u32,
}
impl RequestAttempts {
pub fn new(attempts: u32) -> Self {
Self { attempts }
}
pub fn attempts(&self) -> u32 {
self.attempts
}
}
impl From<u32> for RequestAttempts {
fn from(attempts: u32) -> Self {
Self::new(attempts)
}
}
impl From<RequestAttempts> for u32 {
fn from(value: RequestAttempts) -> Self {
value.attempts()
}
}
impl Storable for RequestAttempts {
type Storer = StoreReplace<Self>;
}
#[cfg(feature = "test-util")]
mod test_util {
use super::{ClassifyRetry, ErrorKind, RetryReason};
use crate::client::interceptors::context::InterceptorContext;
use tracing::trace;
#[derive(Debug)]
pub struct AlwaysRetry(pub ErrorKind);
impl ClassifyRetry for AlwaysRetry {
fn classify_retry(&self, error: &InterceptorContext) -> Option<RetryReason> {
trace!("Retrying error {:?} as an {:?}", error, self.0);
Some(RetryReason::Error(self.0))
}
fn name(&self) -> &'static str {
"Always Retry"
}
}
}
use crate::box_error::BoxError;
use crate::client::runtime_components::RuntimeComponents;
use std::sync::Arc;
#[cfg(feature = "test-util")]
pub use test_util::AlwaysRetry;