temporalio_common_wasm/
retry_policy.rs1use std::time::Duration;
2
3use crate::protos::temporal::api::common::v1::RetryPolicy as ProtoRetryPolicy;
4
5const DEFAULT_INITIAL_INTERVAL: Duration = Duration::from_secs(1);
6const DEFAULT_BACKOFF_COEFFICIENT: f64 = 2.0;
7const MAX_PROTO_DURATION: prost_types::Duration = prost_types::Duration {
8 seconds: 315_576_000_000,
9 nanos: 999_999_999,
10};
11
12fn duration_to_proto(duration: Duration) -> prost_types::Duration {
13 duration.try_into().unwrap_or(MAX_PROTO_DURATION)
14}
15
16#[derive(Clone, Debug, PartialEq)]
20#[non_exhaustive]
21pub struct RetryPolicy {
22 raw: ProtoRetryPolicy,
23}
24
25impl Default for RetryPolicy {
26 fn default() -> Self {
27 Self::builder().build()
28 }
29}
30
31#[bon::bon]
32impl RetryPolicy {
33 #[builder(state_mod(vis = "pub"))]
35 pub fn new(
36 #[builder(default = DEFAULT_INITIAL_INTERVAL)] initial_interval: Duration,
37 #[builder(default = DEFAULT_BACKOFF_COEFFICIENT)] backoff_coefficient: f64,
38 maximum_interval: Option<Duration>,
39 #[builder(default)] maximum_attempts: u32,
40 #[builder(
41 with = |values: impl IntoIterator<Item = impl Into<String>>| values
42 .into_iter()
43 .map(Into::into)
44 .collect(),
45 default
46 )]
47 non_retryable_error_types: Vec<String>,
48 ) -> Self {
49 let mut policy = Self {
50 raw: ProtoRetryPolicy::default(),
51 };
52 policy
53 .set_initial_interval(initial_interval)
54 .set_backoff_coefficient(backoff_coefficient)
55 .set_maximum_interval(maximum_interval)
56 .set_maximum_attempts(maximum_attempts)
57 .set_non_retryable_error_types(non_retryable_error_types);
58 policy
59 }
60
61 pub fn initial_interval(&self) -> Duration {
63 self.raw
64 .initial_interval
65 .map(|duration| duration.try_into().ok().unwrap_or(Duration::ZERO))
66 .unwrap_or(DEFAULT_INITIAL_INTERVAL)
67 }
68
69 pub fn set_initial_interval(&mut self, initial_interval: Duration) -> &mut Self {
71 self.raw.initial_interval = Some(duration_to_proto(initial_interval));
72 self
73 }
74
75 pub fn backoff_coefficient(&self) -> f64 {
77 if self.raw.backoff_coefficient == 0.0 {
78 DEFAULT_BACKOFF_COEFFICIENT
79 } else {
80 self.raw.backoff_coefficient
81 }
82 }
83
84 pub fn set_backoff_coefficient(&mut self, backoff_coefficient: f64) -> &mut Self {
86 self.raw.backoff_coefficient = backoff_coefficient;
87 self
88 }
89
90 pub fn maximum_interval(&self) -> Option<Duration> {
92 self.raw
93 .maximum_interval
94 .map(|duration| duration.try_into().ok().unwrap_or(Duration::ZERO))
95 }
96
97 pub fn set_maximum_interval(&mut self, maximum_interval: Option<Duration>) -> &mut Self {
99 self.raw.maximum_interval = maximum_interval.map(duration_to_proto);
100 self
101 }
102
103 pub fn maximum_attempts(&self) -> u32 {
105 self.raw.maximum_attempts.try_into().unwrap_or_default()
106 }
107
108 pub fn set_maximum_attempts(&mut self, maximum_attempts: u32) -> &mut Self {
111 self.raw.maximum_attempts = maximum_attempts.try_into().unwrap_or(i32::MAX);
112 self
113 }
114
115 pub fn non_retryable_error_types(&self) -> &[String] {
117 &self.raw.non_retryable_error_types
118 }
119
120 pub fn set_non_retryable_error_types(
122 &mut self,
123 values: impl IntoIterator<Item = impl Into<String>>,
124 ) -> &mut Self {
125 self.raw.non_retryable_error_types = values.into_iter().map(Into::into).collect();
126 self
127 }
128
129 pub fn raw(&self) -> &ProtoRetryPolicy {
131 &self.raw
132 }
133
134 pub fn into_raw(self) -> ProtoRetryPolicy {
136 self.raw
137 }
138}
139
140impl From<ProtoRetryPolicy> for RetryPolicy {
141 fn from(value: ProtoRetryPolicy) -> Self {
142 Self { raw: value }
143 }
144}
145
146impl From<RetryPolicy> for ProtoRetryPolicy {
147 fn from(value: RetryPolicy) -> Self {
148 value.raw
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use rstest::rstest;
156
157 #[rstest]
158 #[case::default(RetryPolicy::default())]
159 #[case::builder(RetryPolicy::builder().build())]
160 #[case::proto(RetryPolicy::from(ProtoRetryPolicy::default()))]
161 fn defaults_match_temporal_retry_defaults(#[case] policy: RetryPolicy) {
162 assert_eq!(policy.initial_interval(), Duration::from_secs(1));
163 assert_eq!(policy.backoff_coefficient(), 2.0);
164 assert_eq!(policy.maximum_attempts(), 0);
165 assert_eq!(policy.maximum_interval(), None);
166 }
167
168 #[test]
169 fn retry_policy_round_trips() {
170 let policy = RetryPolicy::builder()
171 .initial_interval(Duration::from_millis(250))
172 .backoff_coefficient(1.5)
173 .maximum_interval(Duration::from_secs(10))
174 .maximum_attempts(5)
175 .non_retryable_error_types(["InvalidInput"])
176 .build();
177
178 assert_eq!(
179 RetryPolicy::from(ProtoRetryPolicy::from(policy.clone())),
180 policy
181 );
182 }
183
184 #[test]
185 fn setters_update_raw_proto() {
186 let mut policy = RetryPolicy::default();
187 policy
188 .set_initial_interval(Duration::from_millis(250))
189 .set_backoff_coefficient(1.5)
190 .set_maximum_interval(Some(Duration::from_secs(10)))
191 .set_maximum_attempts(5)
192 .set_non_retryable_error_types(["InvalidInput"]);
193
194 assert_eq!(policy.initial_interval(), Duration::from_millis(250));
195 assert_eq!(policy.backoff_coefficient(), 1.5);
196 assert_eq!(policy.maximum_interval(), Some(Duration::from_secs(10)));
197 assert_eq!(policy.maximum_attempts(), 5);
198 assert_eq!(policy.non_retryable_error_types(), ["InvalidInput"]);
199 assert_eq!(
200 policy.raw().initial_interval,
201 Duration::from_millis(250).try_into().ok()
202 );
203 assert_eq!(policy.raw().backoff_coefficient, 1.5);
204 assert_eq!(
205 policy.raw().maximum_interval,
206 Duration::from_secs(10).try_into().ok()
207 );
208 assert_eq!(policy.raw().maximum_attempts, 5);
209 assert_eq!(policy.raw().non_retryable_error_types, ["InvalidInput"]);
210 }
211
212 #[test]
213 fn invalid_raw_values_are_normalized_by_getters() {
214 let raw = ProtoRetryPolicy {
215 initial_interval: Some(prost_types::Duration {
216 seconds: i64::MIN,
217 nanos: 999_999_999,
218 }),
219 maximum_interval: Some(prost_types::Duration {
220 seconds: i64::MIN,
221 nanos: 999_999_999,
222 }),
223 maximum_attempts: -1,
224 ..Default::default()
225 };
226 let policy = RetryPolicy::from(raw.clone());
227
228 assert_eq!(policy.initial_interval(), Duration::ZERO);
229 assert_eq!(policy.maximum_interval(), Some(Duration::ZERO));
230 assert_eq!(policy.maximum_attempts(), 0);
231 assert_eq!(policy.raw(), &raw);
232 }
233}