1use crate::bindings::golem::api::retry as retry_api;
2
3pub use builder::{NamedPolicy, Policy, Predicate, Props, RetryBuilderError, Value};
4pub use retry_api::{NamedRetryPolicy, PredicateValue, RetryPolicy, RetryPredicate};
5
6pub fn get_retry_policies() -> Vec<NamedRetryPolicy> {
8 retry_api::get_retry_policies()
9}
10
11pub fn get_retry_policy_by_name(name: &str) -> Option<NamedRetryPolicy> {
13 retry_api::get_retry_policy_by_name(name)
14}
15
16pub fn resolve_retry_policy(
20 verb: &str,
21 noun_uri: &str,
22 properties: &[(String, PredicateValue)],
23) -> Option<RetryPolicy> {
24 let props: Vec<(String, PredicateValue)> = properties.to_vec();
25 retry_api::resolve_retry_policy(verb, noun_uri, &props)
26}
27
28pub fn set_retry_policy(policy: &NamedRetryPolicy) {
31 retry_api::set_retry_policy(policy);
32}
33
34pub fn set_named_policy(policy: &NamedPolicy) -> Result<(), RetryBuilderError> {
37 let raw = policy.try_to_raw()?;
38 set_retry_policy(&raw);
39 Ok(())
40}
41
42pub fn remove_retry_policy(name: &str) {
44 retry_api::remove_retry_policy(name);
45}
46
47pub struct RetryPolicyGuard {
50 previous: Option<NamedRetryPolicy>,
51 name: String,
52}
53
54impl Drop for RetryPolicyGuard {
55 fn drop(&mut self) {
56 match self.previous.take() {
57 Some(original) => set_retry_policy(&original),
58 None => remove_retry_policy(&self.name),
59 }
60 }
61}
62
63#[must_use]
66pub fn use_retry_policy(policy: NamedRetryPolicy) -> RetryPolicyGuard {
67 let previous = get_retry_policy_by_name(&policy.name);
68 let name = policy.name.clone();
69 set_retry_policy(&policy);
70 RetryPolicyGuard { previous, name }
71}
72
73pub fn use_named_policy(policy: &NamedPolicy) -> Result<RetryPolicyGuard, RetryBuilderError> {
76 let raw = policy.try_to_raw()?;
77 Ok(use_retry_policy(raw))
78}
79
80pub fn with_retry_policy<R>(policy: NamedRetryPolicy, f: impl FnOnce() -> R) -> R {
82 let _guard = use_retry_policy(policy);
83 f()
84}
85
86pub async fn with_retry_policy_async<R, F: std::future::Future<Output = R>>(
88 policy: NamedRetryPolicy,
89 f: impl FnOnce() -> F,
90) -> R {
91 let _guard = use_retry_policy(policy);
92 f().await
93}
94
95pub fn with_named_policy<R>(
97 policy: &NamedPolicy,
98 f: impl FnOnce() -> R,
99) -> Result<R, RetryBuilderError> {
100 let _guard = use_named_policy(policy)?;
101 Ok(f())
102}
103
104pub async fn with_named_policy_async<R, F: std::future::Future<Output = R>>(
110 policy: &NamedPolicy,
111 f: impl FnOnce() -> F,
112) -> Result<R, RetryBuilderError> {
113 let _guard = use_named_policy(policy)?;
114 Ok(f().await)
115}
116
117pub mod builder {
118 use super::retry_api;
119 use std::error::Error;
120 use std::fmt;
121 use std::time::Duration;
122
123 pub struct Props;
125
126 impl Props {
127 pub const VERB: &str = "verb";
128 pub const NOUN_URI: &str = "noun-uri";
129 pub const URI_SCHEME: &str = "uri-scheme";
130 pub const URI_HOST: &str = "uri-host";
131 pub const URI_PORT: &str = "uri-port";
132 pub const URI_PATH: &str = "uri-path";
133 pub const STATUS_CODE: &str = "status-code";
134 pub const ERROR_TYPE: &str = "error-type";
135 pub const FUNCTION: &str = "function";
136 pub const TARGET_COMPONENT_ID: &str = "target-component-id";
137 pub const TARGET_AGENT_TYPE: &str = "target-agent-type";
138 pub const DB_TYPE: &str = "db-type";
139 pub const TRAP_TYPE: &str = "trap-type";
140 }
141
142 #[derive(Clone, Debug, PartialEq, Eq)]
144 pub enum Value {
145 Text(String),
146 Integer(i128),
147 Boolean(bool),
148 }
149
150 impl From<String> for Value {
151 fn from(value: String) -> Self {
152 Self::Text(value)
153 }
154 }
155
156 impl From<&str> for Value {
157 fn from(value: &str) -> Self {
158 Self::Text(value.to_string())
159 }
160 }
161
162 impl From<bool> for Value {
163 fn from(value: bool) -> Self {
164 Self::Boolean(value)
165 }
166 }
167
168 impl From<i8> for Value {
169 fn from(value: i8) -> Self {
170 Self::Integer(value.into())
171 }
172 }
173
174 impl From<i16> for Value {
175 fn from(value: i16) -> Self {
176 Self::Integer(value.into())
177 }
178 }
179
180 impl From<i32> for Value {
181 fn from(value: i32) -> Self {
182 Self::Integer(value.into())
183 }
184 }
185
186 impl From<i64> for Value {
187 fn from(value: i64) -> Self {
188 Self::Integer(value.into())
189 }
190 }
191
192 impl From<isize> for Value {
193 fn from(value: isize) -> Self {
194 Self::Integer(value as i128)
195 }
196 }
197
198 impl From<u8> for Value {
199 fn from(value: u8) -> Self {
200 Self::Integer(value.into())
201 }
202 }
203
204 impl From<u16> for Value {
205 fn from(value: u16) -> Self {
206 Self::Integer(value.into())
207 }
208 }
209
210 impl From<u32> for Value {
211 fn from(value: u32) -> Self {
212 Self::Integer(value.into())
213 }
214 }
215
216 impl From<u64> for Value {
217 fn from(value: u64) -> Self {
218 Self::Integer(value.into())
219 }
220 }
221
222 impl From<usize> for Value {
223 fn from(value: usize) -> Self {
224 Self::Integer(value as i128)
225 }
226 }
227
228 #[derive(Clone, Debug, PartialEq)]
230 pub enum Predicate {
231 Eq {
232 property: String,
233 value: Value,
234 },
235 Neq {
236 property: String,
237 value: Value,
238 },
239 Gt {
240 property: String,
241 value: Value,
242 },
243 Gte {
244 property: String,
245 value: Value,
246 },
247 Lt {
248 property: String,
249 value: Value,
250 },
251 Lte {
252 property: String,
253 value: Value,
254 },
255 Exists(String),
256 OneOf {
257 property: String,
258 values: Vec<Value>,
259 },
260 MatchesGlob {
261 property: String,
262 pattern: String,
263 },
264 StartsWith {
265 property: String,
266 prefix: String,
267 },
268 Contains {
269 property: String,
270 substring: String,
271 },
272 And(Box<Predicate>, Box<Predicate>),
273 Or(Box<Predicate>, Box<Predicate>),
274 Not(Box<Predicate>),
275 Always,
276 Never,
277 }
278
279 impl Predicate {
280 pub fn always() -> Self {
281 Self::Always
282 }
283
284 pub fn never() -> Self {
285 Self::Never
286 }
287
288 pub fn eq(property: impl Into<String>, value: impl Into<Value>) -> Self {
289 Self::Eq {
290 property: property.into(),
291 value: value.into(),
292 }
293 }
294
295 pub fn neq(property: impl Into<String>, value: impl Into<Value>) -> Self {
296 Self::Neq {
297 property: property.into(),
298 value: value.into(),
299 }
300 }
301
302 pub fn gt(property: impl Into<String>, value: impl Into<Value>) -> Self {
303 Self::Gt {
304 property: property.into(),
305 value: value.into(),
306 }
307 }
308
309 pub fn gte(property: impl Into<String>, value: impl Into<Value>) -> Self {
310 Self::Gte {
311 property: property.into(),
312 value: value.into(),
313 }
314 }
315
316 pub fn lt(property: impl Into<String>, value: impl Into<Value>) -> Self {
317 Self::Lt {
318 property: property.into(),
319 value: value.into(),
320 }
321 }
322
323 pub fn lte(property: impl Into<String>, value: impl Into<Value>) -> Self {
324 Self::Lte {
325 property: property.into(),
326 value: value.into(),
327 }
328 }
329
330 pub fn exists(property: impl Into<String>) -> Self {
331 Self::Exists(property.into())
332 }
333
334 pub fn one_of<I, V>(property: impl Into<String>, values: I) -> Self
335 where
336 I: IntoIterator<Item = V>,
337 V: Into<Value>,
338 {
339 Self::OneOf {
340 property: property.into(),
341 values: values.into_iter().map(Into::into).collect(),
342 }
343 }
344
345 pub fn matches_glob(property: impl Into<String>, pattern: impl Into<String>) -> Self {
346 Self::MatchesGlob {
347 property: property.into(),
348 pattern: pattern.into(),
349 }
350 }
351
352 pub fn starts_with(property: impl Into<String>, prefix: impl Into<String>) -> Self {
353 Self::StartsWith {
354 property: property.into(),
355 prefix: prefix.into(),
356 }
357 }
358
359 pub fn contains(property: impl Into<String>, substring: impl Into<String>) -> Self {
360 Self::Contains {
361 property: property.into(),
362 substring: substring.into(),
363 }
364 }
365
366 pub fn and(left: Predicate, right: Predicate) -> Self {
367 Self::And(Box::new(left), Box::new(right))
368 }
369
370 pub fn or(left: Predicate, right: Predicate) -> Self {
371 Self::Or(Box::new(left), Box::new(right))
372 }
373
374 #[allow(clippy::should_implement_trait)]
375 pub fn not(inner: Predicate) -> Self {
376 Self::Not(Box::new(inner))
377 }
378
379 pub fn try_to_raw(&self) -> Result<retry_api::RetryPredicate, RetryBuilderError> {
380 self.clone().try_into()
381 }
382 }
383
384 #[derive(Clone, Debug, PartialEq)]
386 pub enum Policy {
387 Periodic(Duration),
388 Exponential {
389 base_delay: Duration,
390 factor: f64,
391 },
392 Fibonacci {
393 first: Duration,
394 second: Duration,
395 },
396 Immediate,
397 Never,
398 CountBox {
399 max_retries: u32,
400 inner: Box<Policy>,
401 },
402 TimeBox {
403 limit: Duration,
404 inner: Box<Policy>,
405 },
406 Clamp {
407 min_delay: Duration,
408 max_delay: Duration,
409 inner: Box<Policy>,
410 },
411 AddDelay {
412 delay: Duration,
413 inner: Box<Policy>,
414 },
415 Jitter {
416 factor: f64,
417 inner: Box<Policy>,
418 },
419 OnlyWhen {
420 predicate: Predicate,
421 inner: Box<Policy>,
422 },
423 AndThen(Box<Policy>, Box<Policy>),
424 Union(Box<Policy>, Box<Policy>),
425 Intersect(Box<Policy>, Box<Policy>),
426 }
427
428 impl Policy {
429 pub fn immediate() -> Self {
430 Self::Immediate
431 }
432
433 pub fn never() -> Self {
434 Self::Never
435 }
436
437 pub fn periodic(delay: Duration) -> Self {
438 Self::Periodic(delay)
439 }
440
441 pub fn exponential(base_delay: Duration, factor: f64) -> Self {
442 Self::Exponential { base_delay, factor }
443 }
444
445 pub fn fibonacci(first: Duration, second: Duration) -> Self {
446 Self::Fibonacci { first, second }
447 }
448
449 pub fn max_retries(self, max_retries: u32) -> Self {
450 Self::CountBox {
451 max_retries,
452 inner: Box::new(self),
453 }
454 }
455
456 pub fn within(self, limit: Duration) -> Self {
457 Self::TimeBox {
458 limit,
459 inner: Box::new(self),
460 }
461 }
462
463 pub fn clamp(self, min_delay: Duration, max_delay: Duration) -> Self {
464 Self::Clamp {
465 min_delay,
466 max_delay,
467 inner: Box::new(self),
468 }
469 }
470
471 pub fn add_delay(self, delay: Duration) -> Self {
472 Self::AddDelay {
473 delay,
474 inner: Box::new(self),
475 }
476 }
477
478 pub fn with_jitter(self, factor: f64) -> Self {
479 Self::Jitter {
480 factor,
481 inner: Box::new(self),
482 }
483 }
484
485 pub fn only_when(self, predicate: Predicate) -> Self {
486 Self::OnlyWhen {
487 predicate,
488 inner: Box::new(self),
489 }
490 }
491
492 pub fn and_then(self, other: Policy) -> Self {
493 Self::AndThen(Box::new(self), Box::new(other))
494 }
495
496 pub fn union(self, other: Policy) -> Self {
497 Self::Union(Box::new(self), Box::new(other))
498 }
499
500 pub fn intersect(self, other: Policy) -> Self {
501 Self::Intersect(Box::new(self), Box::new(other))
502 }
503
504 pub fn try_to_raw(&self) -> Result<retry_api::RetryPolicy, RetryBuilderError> {
505 self.clone().try_into()
506 }
507 }
508
509 #[derive(Clone, Debug, PartialEq)]
511 pub struct NamedPolicy {
512 name: String,
513 priority: u32,
514 predicate: Predicate,
515 policy: Policy,
516 }
517
518 impl NamedPolicy {
519 pub fn named(name: impl Into<String>, policy: Policy) -> Self {
520 Self {
521 name: name.into(),
522 priority: 0,
523 predicate: Predicate::always(),
524 policy,
525 }
526 }
527
528 pub fn priority(mut self, priority: u32) -> Self {
529 self.priority = priority;
530 self
531 }
532
533 pub fn applies_when(mut self, predicate: Predicate) -> Self {
534 self.predicate = predicate;
535 self
536 }
537
538 pub fn try_to_raw(&self) -> Result<retry_api::NamedRetryPolicy, RetryBuilderError> {
539 self.clone().try_into()
540 }
541 }
542
543 #[derive(Clone, Debug, PartialEq)]
545 pub enum RetryBuilderError {
546 IntegerOutOfRange {
547 value: i128,
548 },
549 DurationOutOfRange {
550 field: &'static str,
551 duration: Duration,
552 },
553 InvalidExponentialFactor {
554 factor: f64,
555 },
556 InvalidJitterFactor {
557 factor: f64,
558 },
559 InvalidClampRange {
560 min_delay: Duration,
561 max_delay: Duration,
562 },
563 }
564
565 impl fmt::Display for RetryBuilderError {
566 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567 match self {
568 Self::IntegerOutOfRange { value } => {
569 write!(
570 f,
571 "predicate integer value {value} does not fit into WIT s64"
572 )
573 }
574 Self::DurationOutOfRange { field, duration } => write!(
575 f,
576 "{field} duration {duration:?} does not fit into WIT duration nanoseconds"
577 ),
578 Self::InvalidExponentialFactor { factor } => write!(
579 f,
580 "exponential factor must be finite and greater than 0, got {factor}"
581 ),
582 Self::InvalidJitterFactor { factor } => write!(
583 f,
584 "jitter factor must be finite and greater than or equal to 0, got {factor}"
585 ),
586 Self::InvalidClampRange {
587 min_delay,
588 max_delay,
589 } => write!(
590 f,
591 "clamp min delay {min_delay:?} must be less than or equal to max delay {max_delay:?}"
592 ),
593 }
594 }
595 }
596
597 impl Error for RetryBuilderError {}
598
599 impl TryFrom<Value> for retry_api::PredicateValue {
600 type Error = RetryBuilderError;
601
602 fn try_from(value: Value) -> Result<Self, Self::Error> {
603 match value {
604 Value::Text(value) => Ok(Self::Text(value)),
605 Value::Boolean(value) => Ok(Self::Boolean(value)),
606 Value::Integer(value) => i64::try_from(value)
607 .map(Self::Integer)
608 .map_err(|_| RetryBuilderError::IntegerOutOfRange { value }),
609 }
610 }
611 }
612
613 impl TryFrom<Predicate> for retry_api::RetryPredicate {
614 type Error = RetryBuilderError;
615
616 fn try_from(predicate: Predicate) -> Result<Self, Self::Error> {
617 let mut nodes = Vec::new();
618 push_predicate_node(predicate, &mut nodes)?;
619 Ok(Self { nodes })
620 }
621 }
622
623 impl TryFrom<Policy> for retry_api::RetryPolicy {
624 type Error = RetryBuilderError;
625
626 fn try_from(policy: Policy) -> Result<Self, Self::Error> {
627 let mut nodes = Vec::new();
628 push_policy_node(policy, &mut nodes)?;
629 Ok(Self { nodes })
630 }
631 }
632
633 impl TryFrom<NamedPolicy> for retry_api::NamedRetryPolicy {
634 type Error = RetryBuilderError;
635
636 fn try_from(policy: NamedPolicy) -> Result<Self, Self::Error> {
637 Ok(Self {
638 name: policy.name,
639 priority: policy.priority,
640 predicate: policy.predicate.try_to_raw()?,
641 policy: policy.policy.try_to_raw()?,
642 })
643 }
644 }
645
646 fn push_predicate_node(
647 predicate: Predicate,
648 nodes: &mut Vec<retry_api::PredicateNode>,
649 ) -> Result<i32, RetryBuilderError> {
650 let index = nodes.len() as i32;
651 nodes.push(retry_api::PredicateNode::PredFalse);
652
653 let node = match predicate {
654 Predicate::Eq { property, value } => {
655 retry_api::PredicateNode::PropEq(retry_api::PropertyComparison {
656 property_name: property,
657 value: value.try_into()?,
658 })
659 }
660 Predicate::Neq { property, value } => {
661 retry_api::PredicateNode::PropNeq(retry_api::PropertyComparison {
662 property_name: property,
663 value: value.try_into()?,
664 })
665 }
666 Predicate::Gt { property, value } => {
667 retry_api::PredicateNode::PropGt(retry_api::PropertyComparison {
668 property_name: property,
669 value: value.try_into()?,
670 })
671 }
672 Predicate::Gte { property, value } => {
673 retry_api::PredicateNode::PropGte(retry_api::PropertyComparison {
674 property_name: property,
675 value: value.try_into()?,
676 })
677 }
678 Predicate::Lt { property, value } => {
679 retry_api::PredicateNode::PropLt(retry_api::PropertyComparison {
680 property_name: property,
681 value: value.try_into()?,
682 })
683 }
684 Predicate::Lte { property, value } => {
685 retry_api::PredicateNode::PropLte(retry_api::PropertyComparison {
686 property_name: property,
687 value: value.try_into()?,
688 })
689 }
690 Predicate::Exists(property) => retry_api::PredicateNode::PropExists(property),
691 Predicate::OneOf { property, values } => {
692 retry_api::PredicateNode::PropIn(retry_api::PropertySetCheck {
693 property_name: property,
694 values: values
695 .into_iter()
696 .map(TryInto::try_into)
697 .collect::<Result<Vec<_>, _>>()?,
698 })
699 }
700 Predicate::MatchesGlob { property, pattern } => {
701 retry_api::PredicateNode::PropMatches(retry_api::PropertyPattern {
702 property_name: property,
703 pattern,
704 })
705 }
706 Predicate::StartsWith { property, prefix } => {
707 retry_api::PredicateNode::PropStartsWith(retry_api::PropertyPattern {
708 property_name: property,
709 pattern: prefix,
710 })
711 }
712 Predicate::Contains {
713 property,
714 substring,
715 } => retry_api::PredicateNode::PropContains(retry_api::PropertyPattern {
716 property_name: property,
717 pattern: substring,
718 }),
719 Predicate::And(left, right) => {
720 let left_index = push_predicate_node(*left, nodes)?;
721 let right_index = push_predicate_node(*right, nodes)?;
722 retry_api::PredicateNode::PredAnd((left_index, right_index))
723 }
724 Predicate::Or(left, right) => {
725 let left_index = push_predicate_node(*left, nodes)?;
726 let right_index = push_predicate_node(*right, nodes)?;
727 retry_api::PredicateNode::PredOr((left_index, right_index))
728 }
729 Predicate::Not(inner) => {
730 let inner_index = push_predicate_node(*inner, nodes)?;
731 retry_api::PredicateNode::PredNot(inner_index)
732 }
733 Predicate::Always => retry_api::PredicateNode::PredTrue,
734 Predicate::Never => retry_api::PredicateNode::PredFalse,
735 };
736
737 nodes[index as usize] = node;
738 Ok(index)
739 }
740
741 fn push_policy_node(
742 policy: Policy,
743 nodes: &mut Vec<retry_api::PolicyNode>,
744 ) -> Result<i32, RetryBuilderError> {
745 let index = nodes.len() as i32;
746 nodes.push(retry_api::PolicyNode::Never);
747
748 let node = match policy {
749 Policy::Periodic(delay) => {
750 retry_api::PolicyNode::Periodic(duration_to_wit(delay, "periodic delay")?)
751 }
752 Policy::Exponential { base_delay, factor } => {
753 if !factor.is_finite() || factor <= 0.0 {
754 return Err(RetryBuilderError::InvalidExponentialFactor { factor });
755 }
756
757 retry_api::PolicyNode::Exponential(retry_api::ExponentialConfig {
758 base_delay: duration_to_wit(base_delay, "exponential base delay")?,
759 factor,
760 })
761 }
762 Policy::Fibonacci { first, second } => {
763 retry_api::PolicyNode::Fibonacci(retry_api::FibonacciConfig {
764 first: duration_to_wit(first, "fibonacci first delay")?,
765 second: duration_to_wit(second, "fibonacci second delay")?,
766 })
767 }
768 Policy::Immediate => retry_api::PolicyNode::Immediate,
769 Policy::Never => retry_api::PolicyNode::Never,
770 Policy::CountBox { max_retries, inner } => {
771 let inner_index = push_policy_node(*inner, nodes)?;
772 retry_api::PolicyNode::CountBox(retry_api::CountBoxConfig {
773 max_retries,
774 inner: inner_index,
775 })
776 }
777 Policy::TimeBox { limit, inner } => {
778 let inner_index = push_policy_node(*inner, nodes)?;
779 retry_api::PolicyNode::TimeBox(retry_api::TimeBoxConfig {
780 limit: duration_to_wit(limit, "time-box limit")?,
781 inner: inner_index,
782 })
783 }
784 Policy::Clamp {
785 min_delay,
786 max_delay,
787 inner,
788 } => {
789 if min_delay > max_delay {
790 return Err(RetryBuilderError::InvalidClampRange {
791 min_delay,
792 max_delay,
793 });
794 }
795
796 let inner_index = push_policy_node(*inner, nodes)?;
797 retry_api::PolicyNode::ClampDelay(retry_api::ClampConfig {
798 min_delay: duration_to_wit(min_delay, "clamp min delay")?,
799 max_delay: duration_to_wit(max_delay, "clamp max delay")?,
800 inner: inner_index,
801 })
802 }
803 Policy::AddDelay { delay, inner } => {
804 let inner_index = push_policy_node(*inner, nodes)?;
805 retry_api::PolicyNode::AddDelay(retry_api::AddDelayConfig {
806 delay: duration_to_wit(delay, "added delay")?,
807 inner: inner_index,
808 })
809 }
810 Policy::Jitter { factor, inner } => {
811 if !factor.is_finite() || factor < 0.0 {
812 return Err(RetryBuilderError::InvalidJitterFactor { factor });
813 }
814
815 let inner_index = push_policy_node(*inner, nodes)?;
816 retry_api::PolicyNode::Jitter(retry_api::JitterConfig {
817 factor,
818 inner: inner_index,
819 })
820 }
821 Policy::OnlyWhen { predicate, inner } => {
822 let inner_index = push_policy_node(*inner, nodes)?;
823 retry_api::PolicyNode::FilteredOn(retry_api::FilteredConfig {
824 predicate: predicate.try_to_raw()?,
825 inner: inner_index,
826 })
827 }
828 Policy::AndThen(left, right) => {
829 let left_index = push_policy_node(*left, nodes)?;
830 let right_index = push_policy_node(*right, nodes)?;
831 retry_api::PolicyNode::AndThen((left_index, right_index))
832 }
833 Policy::Union(left, right) => {
834 let left_index = push_policy_node(*left, nodes)?;
835 let right_index = push_policy_node(*right, nodes)?;
836 retry_api::PolicyNode::PolicyUnion((left_index, right_index))
837 }
838 Policy::Intersect(left, right) => {
839 let left_index = push_policy_node(*left, nodes)?;
840 let right_index = push_policy_node(*right, nodes)?;
841 retry_api::PolicyNode::PolicyIntersect((left_index, right_index))
842 }
843 };
844
845 nodes[index as usize] = node;
846 Ok(index)
847 }
848
849 fn duration_to_wit(duration: Duration, field: &'static str) -> Result<u64, RetryBuilderError> {
850 let nanos = duration.as_nanos();
851 if nanos > u64::MAX as u128 {
852 return Err(RetryBuilderError::DurationOutOfRange { field, duration });
853 }
854
855 Ok(nanos as u64)
856 }
857}
858
859#[cfg(test)]
860mod tests {
861 use std::time::Duration;
862
863 use test_r::test;
864
865 use super::builder::{NamedPolicy, Policy, Predicate, Props, RetryBuilderError};
866 use super::retry_api;
867
868 #[test]
869 fn named_policy_builder_flattens_to_raw_wit_types() {
870 let named = NamedPolicy::named(
871 "transient-http",
872 Policy::exponential(Duration::from_millis(200), 2.0)
873 .clamp(Duration::from_millis(100), Duration::from_secs(5))
874 .only_when(Predicate::one_of(Props::STATUS_CODE, [502_u16, 503, 504]))
875 .max_retries(3),
876 )
877 .priority(10)
878 .applies_when(Predicate::and(
879 Predicate::eq(Props::VERB, "GET"),
880 Predicate::matches_glob(Props::URI_HOST, "*.example.com"),
881 ));
882
883 let raw = named
884 .try_to_raw()
885 .expect("builder should convert to raw WIT types");
886
887 assert_eq!(raw.name, "transient-http");
888 assert_eq!(raw.priority, 10);
889 assert_eq!(raw.predicate.nodes.len(), 3);
890 match &raw.predicate.nodes[0] {
891 retry_api::PredicateNode::PredAnd((left, right)) => {
892 assert_eq!((*left, *right), (1, 2));
893 }
894 node => panic!("unexpected root predicate node: {node:?}"),
895 }
896 match &raw.predicate.nodes[1] {
897 retry_api::PredicateNode::PropEq(retry_api::PropertyComparison {
898 property_name,
899 value: retry_api::PredicateValue::Text(value),
900 }) => {
901 assert_eq!(property_name, Props::VERB);
902 assert_eq!(value, "GET");
903 }
904 node => panic!("unexpected left predicate node: {node:?}"),
905 }
906 match &raw.predicate.nodes[2] {
907 retry_api::PredicateNode::PropMatches(retry_api::PropertyPattern {
908 property_name,
909 pattern,
910 }) => {
911 assert_eq!(property_name, Props::URI_HOST);
912 assert_eq!(pattern, "*.example.com");
913 }
914 node => panic!("unexpected right predicate node: {node:?}"),
915 }
916
917 assert_eq!(raw.policy.nodes.len(), 4);
918 match &raw.policy.nodes[0] {
919 retry_api::PolicyNode::CountBox(retry_api::CountBoxConfig { max_retries, inner }) => {
920 assert_eq!((*max_retries, *inner), (3, 1));
921 }
922 node => panic!("unexpected root policy node: {node:?}"),
923 }
924 match &raw.policy.nodes[1] {
925 retry_api::PolicyNode::FilteredOn(retry_api::FilteredConfig { predicate, inner }) => {
926 assert_eq!(*inner, 2);
927 assert_eq!(predicate.nodes.len(), 1);
928 match &predicate.nodes[0] {
929 retry_api::PredicateNode::PropIn(retry_api::PropertySetCheck {
930 property_name,
931 values,
932 }) => {
933 assert_eq!(property_name, Props::STATUS_CODE);
934 assert_eq!(values.len(), 3);
935 match &values[..] {
936 [
937 retry_api::PredicateValue::Integer(502),
938 retry_api::PredicateValue::Integer(503),
939 retry_api::PredicateValue::Integer(504),
940 ] => {}
941 other => panic!("unexpected predicate values: {other:?}"),
942 }
943 }
944 node => panic!("unexpected filtered predicate node: {node:?}"),
945 }
946 }
947 node => panic!("unexpected filtered policy node: {node:?}"),
948 }
949 match &raw.policy.nodes[2] {
950 retry_api::PolicyNode::ClampDelay(retry_api::ClampConfig {
951 min_delay,
952 max_delay,
953 inner,
954 }) => {
955 assert_eq!(
956 (*min_delay, *max_delay, *inner),
957 (100_000_000, 5_000_000_000, 3)
958 );
959 }
960 node => panic!("unexpected clamp policy node: {node:?}"),
961 }
962 match &raw.policy.nodes[3] {
963 retry_api::PolicyNode::Exponential(retry_api::ExponentialConfig {
964 base_delay,
965 factor,
966 }) => {
967 assert_eq!(*base_delay, 200_000_000);
968 assert_eq!(*factor, 2.0);
969 }
970 node => panic!("unexpected exponential policy node: {node:?}"),
971 }
972 }
973
974 #[test]
975 fn predicate_builder_rejects_integer_values_that_do_not_fit_wit() {
976 let predicate = Predicate::eq(Props::STATUS_CODE, u64::try_from(i64::MAX).unwrap() + 1);
977
978 assert_eq!(
979 predicate.try_to_raw().unwrap_err(),
980 RetryBuilderError::IntegerOutOfRange {
981 value: i128::from(i64::MAX) + 1,
982 }
983 );
984 }
985
986 #[test]
987 fn policy_builder_rejects_invalid_exponential_factors() {
988 let policy = Policy::exponential(Duration::from_millis(100), 0.0);
989
990 assert_eq!(
991 policy.try_to_raw().unwrap_err(),
992 RetryBuilderError::InvalidExponentialFactor { factor: 0.0 }
993 );
994
995 assert!(matches!(
996 Policy::exponential(Duration::from_millis(100), f64::NAN)
997 .try_to_raw()
998 .unwrap_err(),
999 RetryBuilderError::InvalidExponentialFactor { factor } if factor.is_nan()
1000 ));
1001 }
1002
1003 #[test]
1004 fn policy_builder_rejects_invalid_jitter_factors() {
1005 let policy = Policy::periodic(Duration::from_millis(100)).with_jitter(-0.1);
1006
1007 assert_eq!(
1008 policy.try_to_raw().unwrap_err(),
1009 RetryBuilderError::InvalidJitterFactor { factor: -0.1 }
1010 );
1011 }
1012
1013 #[test]
1014 fn policy_builder_rejects_invalid_clamp_ranges() {
1015 let policy = Policy::periodic(Duration::from_millis(100))
1016 .clamp(Duration::from_secs(2), Duration::from_secs(1));
1017
1018 assert_eq!(
1019 policy.try_to_raw().unwrap_err(),
1020 RetryBuilderError::InvalidClampRange {
1021 min_delay: Duration::from_secs(2),
1022 max_delay: Duration::from_secs(1),
1023 }
1024 );
1025 }
1026
1027 #[test]
1028 fn policy_builder_rejects_durations_that_do_not_fit_wit() {
1029 let policy = Policy::periodic(Duration::MAX);
1030
1031 assert_eq!(
1032 policy.try_to_raw().unwrap_err(),
1033 RetryBuilderError::DurationOutOfRange {
1034 field: "periodic delay",
1035 duration: Duration::MAX,
1036 }
1037 );
1038 }
1039}