use crate::client::interceptors::context::InterceptorContext;
use crate::client::runtime_components::sealed::ValidateConfig;
use crate::impl_shared_conversions;
use aws_smithy_types::retry::ErrorKind;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub enum RetryAction {
#[default]
NoActionIndicated,
RetryIndicated(RetryReason),
RetryForbidden,
}
impl fmt::Display for RetryAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoActionIndicated => write!(f, "no action indicated"),
Self::RetryForbidden => write!(f, "retry forbidden"),
Self::RetryIndicated(reason) => write!(f, "retry {reason}"),
}
}
}
impl RetryAction {
pub fn retryable_error(kind: ErrorKind) -> Self {
Self::RetryIndicated(RetryReason::RetryableError {
kind,
retry_after: None,
})
}
pub fn retryable_error_with_explicit_delay(kind: ErrorKind, retry_after: Duration) -> Self {
Self::RetryIndicated(RetryReason::RetryableError {
kind,
retry_after: Some(retry_after),
})
}
pub fn transient_error() -> Self {
Self::retryable_error(ErrorKind::TransientError)
}
pub fn throttling_error() -> Self {
Self::retryable_error(ErrorKind::ThrottlingError)
}
pub fn server_error() -> Self {
Self::retryable_error(ErrorKind::ServerError)
}
pub fn client_error() -> Self {
Self::retryable_error(ErrorKind::ClientError)
}
}
#[non_exhaustive]
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum RetryReason {
RetryableError {
kind: ErrorKind,
retry_after: Option<Duration>,
},
}
impl fmt::Display for RetryReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RetryableError { kind, retry_after } => {
let after = retry_after
.map(|d| format!(" after {d:?}"))
.unwrap_or_default();
write!(f, "{kind} error{after}")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RetryClassifierPriority {
inner: Inner,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Inner {
HttpStatusCodeClassifier,
ModeledAsRetryableClassifier,
TransientErrorClassifier,
Other(i8),
}
impl PartialOrd for RetryClassifierPriority {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(other.as_i8().cmp(&self.as_i8()))
}
}
impl Ord for RetryClassifierPriority {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.as_i8().cmp(&self.as_i8())
}
}
impl RetryClassifierPriority {
pub fn http_status_code_classifier() -> Self {
Self {
inner: Inner::HttpStatusCodeClassifier,
}
}
pub fn modeled_as_retryable_classifier() -> Self {
Self {
inner: Inner::ModeledAsRetryableClassifier,
}
}
pub fn transient_error_classifier() -> Self {
Self {
inner: Inner::TransientErrorClassifier,
}
}
pub fn with_lower_priority_than(other: Self) -> Self {
Self {
inner: Inner::Other(other.as_i8() + 1),
}
}
pub fn with_higher_priority_than(other: Self) -> Self {
Self {
inner: Inner::Other(other.as_i8() - 1),
}
}
fn as_i8(&self) -> i8 {
match self.inner {
Inner::HttpStatusCodeClassifier => 0,
Inner::ModeledAsRetryableClassifier => 10,
Inner::TransientErrorClassifier => 20,
Inner::Other(i) => i,
}
}
}
impl Default for RetryClassifierPriority {
fn default() -> Self {
Self {
inner: Inner::Other(0),
}
}
}
pub trait ClassifyRetry: Send + Sync + fmt::Debug {
fn classify_retry(&self, ctx: &InterceptorContext) -> RetryAction;
fn name(&self) -> &'static str;
fn priority(&self) -> RetryClassifierPriority {
RetryClassifierPriority::default()
}
}
impl_shared_conversions!(convert SharedRetryClassifier from ClassifyRetry using SharedRetryClassifier::new);
#[derive(Debug, Clone)]
pub struct SharedRetryClassifier(Arc<dyn ClassifyRetry>);
impl SharedRetryClassifier {
pub fn new(retry_classifier: impl ClassifyRetry + 'static) -> Self {
Self(Arc::new(retry_classifier))
}
}
impl ClassifyRetry for SharedRetryClassifier {
fn classify_retry(&self, ctx: &InterceptorContext) -> RetryAction {
self.0.classify_retry(ctx)
}
fn name(&self) -> &'static str {
self.0.name()
}
fn priority(&self) -> RetryClassifierPriority {
self.0.priority()
}
}
impl ValidateConfig for SharedRetryClassifier {}
#[cfg(test)]
mod tests {
use super::RetryClassifierPriority;
#[test]
fn test_classifier_lower_priority_than() {
let classifier_a = RetryClassifierPriority::default();
let classifier_b = RetryClassifierPriority::with_lower_priority_than(classifier_a);
let classifier_c = RetryClassifierPriority::with_lower_priority_than(classifier_b);
let mut list = vec![classifier_b, classifier_a, classifier_c];
list.sort();
assert_eq!(vec![classifier_c, classifier_b, classifier_a], list);
}
#[test]
fn test_classifier_higher_priority_than() {
let classifier_c = RetryClassifierPriority::default();
let classifier_b = RetryClassifierPriority::with_higher_priority_than(classifier_c);
let classifier_a = RetryClassifierPriority::with_higher_priority_than(classifier_b);
let mut list = vec![classifier_b, classifier_c, classifier_a];
list.sort();
assert_eq!(vec![classifier_c, classifier_b, classifier_a], list);
}
}