aws_smithy_runtime_api/client/
retries.rs1pub mod classifiers;
12
13use crate::box_error::BoxError;
14use crate::client::interceptors::context::InterceptorContext;
15use crate::client::runtime_components::sealed::ValidateConfig;
16use crate::client::runtime_components::RuntimeComponents;
17use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
18use std::fmt;
19use std::sync::Arc;
20use std::time::Duration;
21
22use crate::impl_shared_conversions;
23pub use aws_smithy_types::retry::ErrorKind;
24#[cfg(feature = "test-util")]
25pub use test_util::AlwaysRetry;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ShouldAttempt {
30 Yes,
32 No,
34 YesAfterDelay(Duration),
36}
37
38#[cfg(feature = "test-util")]
39impl ShouldAttempt {
40 pub fn expect_delay(self) -> Duration {
42 match self {
43 ShouldAttempt::YesAfterDelay(delay) => delay,
44 _ => panic!("Expected this to be the `YesAfterDelay` variant but it was the `{self:?}` variant instead"),
45 }
46 }
47
48 pub fn expect_no(self) {
50 if ShouldAttempt::No == self {
51 return;
52 }
53
54 panic!("Expected this to be the `No` variant but it was the `{self:?}` variant instead");
55 }
56}
57
58impl_shared_conversions!(convert SharedRetryStrategy from RetryStrategy using SharedRetryStrategy::new);
59
60pub trait RetryStrategy: Send + Sync + fmt::Debug {
67 fn should_attempt_initial_request(
69 &self,
70 runtime_components: &RuntimeComponents,
71 cfg: &ConfigBag,
72 ) -> Result<ShouldAttempt, BoxError>;
73
74 fn should_attempt_retry(
81 &self,
82 context: &InterceptorContext,
83 runtime_components: &RuntimeComponents,
84 cfg: &ConfigBag,
85 ) -> Result<ShouldAttempt, BoxError>;
86}
87
88#[derive(Clone, Debug)]
90pub struct SharedRetryStrategy(Arc<dyn RetryStrategy>);
91
92impl SharedRetryStrategy {
93 pub fn new(retry_strategy: impl RetryStrategy + 'static) -> Self {
95 Self(Arc::new(retry_strategy))
96 }
97}
98
99impl RetryStrategy for SharedRetryStrategy {
100 fn should_attempt_initial_request(
101 &self,
102 runtime_components: &RuntimeComponents,
103 cfg: &ConfigBag,
104 ) -> Result<ShouldAttempt, BoxError> {
105 self.0
106 .should_attempt_initial_request(runtime_components, cfg)
107 }
108
109 fn should_attempt_retry(
110 &self,
111 context: &InterceptorContext,
112 runtime_components: &RuntimeComponents,
113 cfg: &ConfigBag,
114 ) -> Result<ShouldAttempt, BoxError> {
115 self.0
116 .should_attempt_retry(context, runtime_components, cfg)
117 }
118}
119
120impl ValidateConfig for SharedRetryStrategy {}
121
122#[derive(Debug, Clone, Copy)]
127pub struct RequestAttempts {
128 attempts: u32,
129}
130
131impl RequestAttempts {
132 pub fn new(attempts: u32) -> Self {
134 Self { attempts }
135 }
136
137 pub fn attempts(&self) -> u32 {
139 self.attempts
140 }
141}
142
143impl From<u32> for RequestAttempts {
144 fn from(attempts: u32) -> Self {
145 Self::new(attempts)
146 }
147}
148
149impl From<RequestAttempts> for u32 {
150 fn from(value: RequestAttempts) -> Self {
151 value.attempts()
152 }
153}
154
155impl Storable for RequestAttempts {
156 type Storer = StoreReplace<Self>;
157}
158
159#[cfg(feature = "test-util")]
160mod test_util {
161 use super::ErrorKind;
162 use crate::client::interceptors::context::InterceptorContext;
163 use crate::client::retries::classifiers::{ClassifyRetry, RetryAction};
164
165 #[derive(Debug)]
169 pub struct AlwaysRetry(pub ErrorKind);
170
171 impl ClassifyRetry for AlwaysRetry {
172 fn classify_retry(&self, error: &InterceptorContext) -> RetryAction {
173 tracing::debug!("Retrying error {:?} as an {:?}", error, self.0);
174 RetryAction::retryable_error(self.0)
175 }
176
177 fn name(&self) -> &'static str {
178 "Always Retry"
179 }
180 }
181}