sark_client/connector/
retry.rs1use http::Method;
2
3use crate::connector::error::Error;
4
5#[derive(Clone, Copy, Debug)]
6pub struct RetryPolicy {
7 pub idempotent_attempts: u8,
8 pub non_idempotent_attempts: u8,
9 pub reconnect_on_stale_non_idempotent: bool,
10}
11
12impl Default for RetryPolicy {
13 fn default() -> Self {
14 Self {
15 idempotent_attempts: 3,
16 non_idempotent_attempts: 2,
17 reconnect_on_stale_non_idempotent: true,
18 }
19 }
20}
21
22impl RetryPolicy {
23 pub fn is_idempotent(method: &Method) -> bool {
24 matches!(
25 *method,
26 Method::GET | Method::HEAD | Method::OPTIONS | Method::PUT | Method::DELETE
27 )
28 }
29
30 pub fn attempts(&self, method: &Method) -> u32 {
31 if Self::is_idempotent(method) {
32 self.idempotent_attempts.max(1) as u32
33 } else {
34 self.non_idempotent_attempts.max(1) as u32
35 }
36 }
37
38 pub fn should_retry(&self, method: &Method, err: &Error) -> bool {
39 if Self::is_idempotent(method) {
40 return matches!(err, Error::Closed | Error::NotConnected | Error::Timeout);
41 }
42 match err {
43 Error::NotConnected => true,
44 Error::Closed => self.reconnect_on_stale_non_idempotent,
45 _ => false,
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn idempotent_retries_all_transient_errors() {
56 let p = RetryPolicy::default();
57 for err in [Error::Closed, Error::NotConnected, Error::Timeout] {
58 assert!(p.should_retry(&Method::GET, &err));
59 }
60 assert!(!p.should_retry(&Method::GET, &Error::Parse("x".into())));
61 assert!(!p.should_retry(&Method::GET, &Error::Backpressure));
62 }
63
64 #[test]
65 fn non_idempotent_never_retries_on_timeout() {
66 let p = RetryPolicy::default();
67 assert!(!p.should_retry(&Method::POST, &Error::Timeout));
68 assert!(!p.should_retry(&Method::PATCH, &Error::Timeout));
69 }
70
71 #[test]
72 fn non_idempotent_retries_not_connected() {
73 let p = RetryPolicy::default();
74 assert!(p.should_retry(&Method::POST, &Error::NotConnected));
75 }
76
77 #[test]
78 fn non_idempotent_closed_follows_reconnect_flag() {
79 let mut p = RetryPolicy {
80 reconnect_on_stale_non_idempotent: true,
81 ..Default::default()
82 };
83 assert!(p.should_retry(&Method::POST, &Error::Closed));
84 p.reconnect_on_stale_non_idempotent = false;
85 assert!(!p.should_retry(&Method::POST, &Error::Closed));
86 }
87
88 #[test]
89 fn patch_is_non_idempotent() {
90 assert!(!RetryPolicy::is_idempotent(&Method::POST));
91 assert!(!RetryPolicy::is_idempotent(&Method::PATCH));
92 assert!(RetryPolicy::is_idempotent(&Method::PUT));
93 assert!(RetryPolicy::is_idempotent(&Method::DELETE));
94 }
95}