aws_config/default_provider/
retry_config.rs1use crate::provider_config::ProviderConfig;
7use crate::retry::error::{RetryConfigError, RetryConfigErrorKind};
8use aws_runtime::env_config::{EnvConfigError, EnvConfigValue};
9use aws_smithy_types::error::display::DisplayErrorContext;
10use aws_smithy_types::retry::{RetryConfig, RetryMode};
11use std::str::FromStr;
12
13pub fn default_provider() -> Builder {
50 Builder::default()
51}
52
53mod env {
54 pub(super) const MAX_ATTEMPTS: &str = "AWS_MAX_ATTEMPTS";
55 pub(super) const RETRY_MODE: &str = "AWS_RETRY_MODE";
56}
57
58mod profile_keys {
59 pub(super) const MAX_ATTEMPTS: &str = "max_attempts";
60 pub(super) const RETRY_MODE: &str = "retry_mode";
61}
62
63#[derive(Debug, Default)]
65pub struct Builder {
66 provider_config: ProviderConfig,
67}
68
69impl Builder {
70 pub fn configure(mut self, configuration: &ProviderConfig) -> Self {
74 self.provider_config = configuration.clone();
75 self
76 }
77
78 pub fn profile_name(mut self, name: &str) -> Self {
80 self.provider_config = self.provider_config.with_profile_name(name.to_string());
81 self
82 }
83
84 pub async fn retry_config(self) -> RetryConfig {
96 match self.try_retry_config().await {
97 Ok(conf) => conf,
98 Err(e) => panic!("{}", DisplayErrorContext(e)),
99 }
100 }
101
102 pub(crate) async fn try_retry_config(
103 self,
104 ) -> Result<RetryConfig, EnvConfigError<RetryConfigError>> {
105 let env = self.provider_config.env();
106 let profiles = self.provider_config.profile().await;
107 let mut retry_config = RetryConfig::standard();
111 let max_attempts = EnvConfigValue::new()
112 .env(env::MAX_ATTEMPTS)
113 .profile(profile_keys::MAX_ATTEMPTS)
114 .validate(&env, profiles, validate_max_attempts);
115
116 let retry_mode = EnvConfigValue::new()
117 .env(env::RETRY_MODE)
118 .profile(profile_keys::RETRY_MODE)
119 .validate(&env, profiles, |s| {
120 RetryMode::from_str(s)
121 .map_err(|err| RetryConfigErrorKind::InvalidRetryMode { source: err }.into())
122 });
123
124 if let Some(max_attempts) = max_attempts? {
125 retry_config = retry_config.with_max_attempts(max_attempts);
126 }
127
128 if let Some(retry_mode) = retry_mode? {
129 retry_config = retry_config.with_retry_mode(retry_mode);
130 }
131
132 Ok(retry_config)
133 }
134}
135
136fn validate_max_attempts(max_attempts: &str) -> Result<u32, RetryConfigError> {
137 match max_attempts.parse::<u32>() {
138 Ok(0) => Err(RetryConfigErrorKind::MaxAttemptsMustNotBeZero.into()),
139 Ok(max_attempts) => Ok(max_attempts),
140 Err(source) => Err(RetryConfigErrorKind::FailedToParseMaxAttempts { source }.into()),
141 }
142}
143
144#[cfg(test)]
145mod test {
146 use crate::default_provider::retry_config::env;
147 use crate::provider_config::ProviderConfig;
148 use crate::retry::{
149 error::RetryConfigError, error::RetryConfigErrorKind, RetryConfig, RetryMode,
150 };
151 use aws_runtime::env_config::EnvConfigError;
152 use aws_types::os_shim_internal::{Env, Fs};
153
154 async fn test_provider(
155 vars: &[(&str, &str)],
156 ) -> Result<RetryConfig, EnvConfigError<RetryConfigError>> {
157 super::Builder::default()
158 .configure(&ProviderConfig::no_configuration().with_env(Env::from_slice(vars)))
159 .try_retry_config()
160 .await
161 }
162
163 #[tokio::test]
164 async fn test_returns_default_retry_config_from_empty_profile() {
165 let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
166 let fs = Fs::from_slice(&[("config", "[default]\n")]);
167
168 let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);
169
170 let actual_retry_config = super::default_provider()
171 .configure(&provider_config)
172 .retry_config()
173 .await;
174
175 let expected_retry_config = RetryConfig::standard();
176
177 assert_eq!(actual_retry_config, expected_retry_config);
178 assert_eq!(actual_retry_config.max_attempts(), 3);
181 assert_eq!(actual_retry_config.mode(), RetryMode::Standard);
182 }
183
184 #[tokio::test]
185 async fn test_no_retry_config_in_empty_profile() {
186 let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
187 let fs = Fs::from_slice(&[("config", "[default]\n")]);
188
189 let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);
190
191 let actual_retry_config = super::default_provider()
192 .configure(&provider_config)
193 .retry_config()
194 .await;
195
196 let expected_retry_config = RetryConfig::standard();
197
198 assert_eq!(actual_retry_config, expected_retry_config)
199 }
200
201 #[tokio::test]
202 async fn test_creation_of_retry_config_from_profile() {
203 let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
204 let fs = Fs::from_slice(&[(
208 "config",
209 r#"[default]
211max_attempts = 1
212retry_mode = standard
213 "#,
214 )]);
215
216 let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);
217
218 let actual_retry_config = super::default_provider()
219 .configure(&provider_config)
220 .retry_config()
221 .await;
222
223 let expected_retry_config = RetryConfig::standard().with_max_attempts(1);
224
225 assert_eq!(actual_retry_config, expected_retry_config)
226 }
227
228 #[tokio::test]
229 async fn test_env_retry_config_takes_precedence_over_profile_retry_config() {
230 let env = Env::from_slice(&[
231 ("AWS_CONFIG_FILE", "config"),
232 ("AWS_MAX_ATTEMPTS", "42"),
233 ("AWS_RETRY_MODE", "standard"),
234 ]);
235 let fs = Fs::from_slice(&[(
239 "config",
240 r#"[default]
242max_attempts = 88
243retry_mode = standard
244 "#,
245 )]);
246
247 let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);
248
249 let actual_retry_config = super::default_provider()
250 .configure(&provider_config)
251 .retry_config()
252 .await;
253
254 let expected_retry_config = RetryConfig::standard().with_max_attempts(42);
255
256 assert_eq!(actual_retry_config, expected_retry_config)
257 }
258
259 #[tokio::test]
260 #[should_panic = "failed to parse max attempts. source: global profile (`default`) key: `max_attempts`: invalid digit found in string"]
261 async fn test_invalid_profile_retry_config_panics() {
262 let env = Env::from_slice(&[("AWS_CONFIG_FILE", "config")]);
263 let fs = Fs::from_slice(&[(
264 "config",
265 r#"[default]
267max_attempts = potato
268 "#,
269 )]);
270
271 let provider_config = ProviderConfig::no_configuration().with_env(env).with_fs(fs);
272
273 let _ = super::default_provider()
274 .configure(&provider_config)
275 .retry_config()
276 .await;
277 }
278
279 #[tokio::test]
280 async fn defaults() {
281 let built = test_provider(&[]).await.unwrap();
282
283 assert_eq!(built.mode(), RetryMode::Standard);
284 assert_eq!(built.max_attempts(), 3);
285 }
286
287 #[tokio::test]
288 async fn max_attempts_is_read_correctly() {
289 assert_eq!(
290 test_provider(&[(env::MAX_ATTEMPTS, "88")]).await.unwrap(),
291 RetryConfig::standard().with_max_attempts(88)
292 );
293 }
294
295 #[tokio::test]
296 async fn max_attempts_errors_when_it_cant_be_parsed_as_an_integer() {
297 assert!(matches!(
298 test_provider(&[(env::MAX_ATTEMPTS, "not an integer")])
299 .await
300 .unwrap_err()
301 .err(),
302 RetryConfigError {
303 kind: RetryConfigErrorKind::FailedToParseMaxAttempts { .. }
304 }
305 ));
306 }
307
308 #[tokio::test]
309 async fn retry_mode_is_read_correctly() {
310 assert_eq!(
311 test_provider(&[(env::RETRY_MODE, "standard")])
312 .await
313 .unwrap(),
314 RetryConfig::standard()
315 );
316 }
317
318 #[tokio::test]
319 async fn both_fields_can_be_set_at_once() {
320 assert_eq!(
321 test_provider(&[(env::RETRY_MODE, "standard"), (env::MAX_ATTEMPTS, "13")])
322 .await
323 .unwrap(),
324 RetryConfig::standard().with_max_attempts(13)
325 );
326 }
327
328 #[tokio::test]
329 async fn disallow_zero_max_attempts() {
330 let err = test_provider(&[(env::MAX_ATTEMPTS, "0")])
331 .await
332 .unwrap_err();
333 let err = err.err();
334 assert!(matches!(
335 err,
336 RetryConfigError {
337 kind: RetryConfigErrorKind::MaxAttemptsMustNotBeZero { .. }
338 }
339 ));
340 }
341}