aws_smithy_runtime/client/
retries.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6/// Smithy retry classifiers.
7pub mod classifiers;
8
9/// Smithy retry strategies.
10pub mod strategy;
11
12mod client_rate_limiter;
13mod token_bucket;
14
15use aws_smithy_types::config_bag::{Storable, StoreReplace};
16use std::fmt;
17
18pub use client_rate_limiter::{
19    ClientRateLimiter, ClientRateLimiterBuilder, ClientRateLimiterPartition,
20};
21pub use token_bucket::{TokenBucket, TokenBucketBuilder};
22
23use std::borrow::Cow;
24
25/// Represents the retry partition, e.g. an endpoint, a region
26///
27/// A retry partition created with [`RetryPartition::new`] uses built-in
28/// token bucket and rate limiter settings, with no option for customization.
29/// Default partitions with the same name share the same token bucket
30/// and client rate limiter.
31///
32/// To customize these components, use a custom retry partition via [`RetryPartition::custom`].
33/// A custom partition owns its token bucket and rate limiter, which:
34/// - Are independent from those in any default partition.
35/// - Are not shared with other custom partitions, even if they have the same name.
36///
37/// To share a token bucket and rate limiter among custom partitions,
38/// either clone the custom partition itself or clone these components
39/// beforehand and pass them to each custom partition.
40#[non_exhaustive]
41#[derive(Clone, Debug)]
42pub struct RetryPartition {
43    pub(crate) inner: RetryPartitionInner,
44}
45
46#[derive(Clone, Debug)]
47pub(crate) enum RetryPartitionInner {
48    Default(Cow<'static, str>),
49    Custom {
50        name: Cow<'static, str>,
51        token_bucket: TokenBucket,
52        client_rate_limiter: ClientRateLimiter,
53    },
54}
55
56impl RetryPartition {
57    /// Creates a new `RetryPartition` from the given `name`.
58    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
59        Self {
60            inner: RetryPartitionInner::Default(name.into()),
61        }
62    }
63
64    /// Creates a builder for a custom `RetryPartition`.
65    pub fn custom(name: impl Into<Cow<'static, str>>) -> RetryPartitionBuilder {
66        RetryPartitionBuilder {
67            name: name.into(),
68            token_bucket: None,
69            client_rate_limiter: None,
70        }
71    }
72
73    fn name(&self) -> &str {
74        match &self.inner {
75            RetryPartitionInner::Default(name) => name,
76            RetryPartitionInner::Custom { name, .. } => name,
77        }
78    }
79}
80
81impl PartialEq for RetryPartition {
82    fn eq(&self, other: &Self) -> bool {
83        match (&self.inner, &other.inner) {
84            (RetryPartitionInner::Default(name1), RetryPartitionInner::Default(name2)) => {
85                name1 == name2
86            }
87            (
88                RetryPartitionInner::Custom { name: name1, .. },
89                RetryPartitionInner::Custom { name: name2, .. },
90            ) => name1 == name2,
91            // Different variants: not equal
92            _ => false,
93        }
94    }
95}
96
97impl Eq for RetryPartition {}
98
99impl std::hash::Hash for RetryPartition {
100    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101        match &self.inner {
102            RetryPartitionInner::Default(name) => {
103                // Hash discriminant for Default variant
104                0u8.hash(state);
105                name.hash(state);
106            }
107            RetryPartitionInner::Custom { name, .. } => {
108                // Hash discriminant for Configured variant
109                1u8.hash(state);
110                name.hash(state);
111            }
112        }
113    }
114}
115
116impl fmt::Display for RetryPartition {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.write_str(self.name())
119    }
120}
121
122impl Storable for RetryPartition {
123    type Storer = StoreReplace<RetryPartition>;
124}
125
126/// Builder for creating custom retry partitions.
127pub struct RetryPartitionBuilder {
128    name: Cow<'static, str>,
129    token_bucket: Option<TokenBucket>,
130    client_rate_limiter: Option<ClientRateLimiter>,
131}
132
133impl RetryPartitionBuilder {
134    /// Sets the token bucket.
135    pub fn token_bucket(mut self, token_bucket: TokenBucket) -> Self {
136        self.token_bucket = Some(token_bucket);
137        self
138    }
139
140    /// Sets the client rate limiter.
141    pub fn client_rate_limiter(mut self, client_rate_limiter: ClientRateLimiter) -> Self {
142        self.client_rate_limiter = Some(client_rate_limiter);
143        self
144    }
145
146    /// Builds the custom retry partition.
147    pub fn build(self) -> RetryPartition {
148        RetryPartition {
149            inner: RetryPartitionInner::Custom {
150                name: self.name,
151                token_bucket: self.token_bucket.unwrap_or_default(),
152                client_rate_limiter: self.client_rate_limiter.unwrap_or_default(),
153            },
154        }
155    }
156}
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use std::collections::hash_map::DefaultHasher;
161    use std::hash::{Hash, Hasher};
162
163    fn hash_value<T: Hash>(t: &T) -> u64 {
164        let mut hasher = DefaultHasher::new();
165        t.hash(&mut hasher);
166        hasher.finish()
167    }
168
169    #[test]
170    fn test_retry_partition_equality() {
171        let default1 = RetryPartition::new("test");
172        let default2 = RetryPartition::new("test");
173        let default3 = RetryPartition::new("other");
174
175        let configured1 = RetryPartition::custom("test").build();
176        let configured2 = RetryPartition::custom("test").build();
177        let configured3 = RetryPartition::custom("other").build();
178
179        // Same variant, same name
180        assert_eq!(default1, default2);
181        assert_eq!(configured1, configured2);
182
183        // Same variant, different name
184        assert_ne!(default1, default3);
185        assert_ne!(configured1, configured3);
186
187        // Different variant, same name
188        assert_ne!(default1, configured1);
189    }
190
191    #[test]
192    fn test_retry_partition_hash() {
193        let default = RetryPartition::new("test");
194        let configured = RetryPartition::custom("test").build();
195
196        // Different variants with same name should have different hashes
197        assert_ne!(hash_value(&default), hash_value(&configured));
198
199        // Same variants with same name should have same hashes
200        let default2 = RetryPartition::new("test");
201        assert_eq!(hash_value(&default), hash_value(&default2));
202    }
203}