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