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