stripe/client/
request_strategy.rs1use std::time::Duration;
2
3use http_types::StatusCode;
4
5#[derive(Clone, Debug)]
6pub enum RequestStrategy {
7 Once,
8 Idempotent(String),
10 Retry(u32),
14 ExponentialBackoff(u32),
18}
19
20impl RequestStrategy {
21 pub fn test(
22 &self,
23 status: Option<StatusCode>,
24 stripe_should_retry: Option<bool>,
25 retry_count: u32,
26 ) -> Outcome {
27 if !stripe_should_retry.unwrap_or(true) {
29 return Outcome::Stop;
30 }
31
32 use RequestStrategy::*;
33
34 match (self, status, retry_count) {
35 (Once | Idempotent(_), _, 0) => Outcome::Continue(None),
37
38 (_, Some(c), _) if c.is_client_error() => Outcome::Stop,
42
43 (Retry(n), _, x) if x < *n => Outcome::Continue(None),
46 (ExponentialBackoff(n), _, x) if x < *n => {
47 Outcome::Continue(Some(calculate_backoff(x)))
48 }
49
50 _ => Outcome::Stop,
52 }
53 }
54
55 #[cfg(feature = "uuid")]
56 pub fn idempotent_with_uuid() -> Self {
57 use uuid::Uuid;
58 Self::Idempotent(Uuid::new_v4().to_string())
59 }
60
61 pub fn get_key(&self) -> Option<String> {
62 match self {
63 RequestStrategy::Once => None,
64 RequestStrategy::Idempotent(key) => Some(key.clone()),
65 #[cfg(feature = "uuid")]
66 RequestStrategy::Retry(_) | RequestStrategy::ExponentialBackoff(_) => {
67 Some(uuid::Uuid::new_v4().to_string())
68 }
69 #[cfg(not(feature = "uuid"))]
70 RequestStrategy::Retry(_) | RequestStrategy::ExponentialBackoff(_) => None,
71 }
72 }
73}
74
75fn calculate_backoff(retry_count: u32) -> Duration {
76 Duration::from_secs(2_u64.pow(retry_count))
77}
78
79#[derive(PartialEq, Eq, Debug)]
80pub enum Outcome {
81 Stop,
82 Continue(Option<Duration>),
83}
84
85#[cfg(test)]
86mod tests {
87 use std::time::Duration;
88
89 use super::{Outcome, RequestStrategy};
90
91 #[test]
92 fn test_idempotent_strategy() {
93 let strategy = RequestStrategy::Idempotent("key".to_string());
94 assert_eq!(strategy.get_key(), Some("key".to_string()));
95 }
96
97 #[test]
98 fn test_once_strategy() {
99 let strategy = RequestStrategy::Once;
100 assert_eq!(strategy.get_key(), None);
101 assert_eq!(strategy.test(None, None, 0), Outcome::Continue(None));
102 assert_eq!(strategy.test(None, None, 1), Outcome::Stop);
103 }
104
105 #[test]
106 #[cfg(feature = "uuid")]
107 fn test_uuid_idempotency() {
108 use uuid::Uuid;
109 let strategy = RequestStrategy::Retry(3);
110 assert!(Uuid::parse_str(&strategy.get_key().unwrap()).is_ok());
111 }
112
113 #[test]
114 #[cfg(not(feature = "uuid"))]
115 fn test_uuid_idempotency() {
116 let strategy = RequestStrategy::Retry(3);
117 assert_eq!(strategy.get_key(), None);
118 }
119
120 #[test]
121 fn test_retry_strategy() {
122 let strategy = RequestStrategy::Retry(3);
123 assert_eq!(strategy.test(None, None, 0), Outcome::Continue(None));
124 assert_eq!(strategy.test(None, None, 1), Outcome::Continue(None));
125 assert_eq!(strategy.test(None, None, 2), Outcome::Continue(None));
126 assert_eq!(strategy.test(None, None, 3), Outcome::Stop);
127 assert_eq!(strategy.test(None, None, 4), Outcome::Stop);
128 }
129
130 #[test]
131 fn test_backoff_strategy() {
132 let strategy = RequestStrategy::ExponentialBackoff(3);
133 assert_eq!(strategy.test(None, None, 0), Outcome::Continue(Some(Duration::from_secs(1))));
134 assert_eq!(strategy.test(None, None, 1), Outcome::Continue(Some(Duration::from_secs(2))));
135 assert_eq!(strategy.test(None, None, 2), Outcome::Continue(Some(Duration::from_secs(4))));
136 assert_eq!(strategy.test(None, None, 3), Outcome::Stop);
137 assert_eq!(strategy.test(None, None, 4), Outcome::Stop);
138 }
139
140 #[test]
141 fn test_retry_header() {
142 let strategy = RequestStrategy::Retry(3);
143 assert_eq!(strategy.test(None, Some(false), 0), Outcome::Stop);
144 }
145}