1use std::fmt;
28use std::str::FromStr;
29
30use crate::config::{MillisNonZero, TimeoutConfig};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct BackoffInterval {
64 pub min_ms: MillisNonZero,
66 pub max_ms: MillisNonZero,
68}
69
70impl Default for BackoffInterval {
71 fn default() -> Self {
72 Self {
73 min_ms: MillisNonZero::new(250).unwrap(),
74 max_ms: MillisNonZero::new(60_000).unwrap(),
75 }
76 }
77}
78
79impl fmt::Display for BackoffInterval {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 let min = format_ms(self.min_ms.get() as u64);
82 let max = format_ms(self.max_ms.get() as u64);
83 write!(f, "{min}..{max}")
84 }
85}
86
87fn format_ms(ms: u64) -> String {
92 if ms == 0 {
93 return "0ms".to_string();
94 }
95 if ms.is_multiple_of(86_400_000) {
96 format!("{}d", ms / 86_400_000)
97 } else if ms.is_multiple_of(3_600_000) {
98 format!("{}h", ms / 3_600_000)
99 } else if ms.is_multiple_of(60_000) {
100 format!("{}m", ms / 60_000)
101 } else if ms.is_multiple_of(1_000) {
102 format!("{}s", ms / 1_000)
103 } else {
104 format!("{ms}ms")
105 }
106}
107
108impl From<BackoffInterval> for TimeoutConfig {
112 fn from(backoff: BackoffInterval) -> Self {
113 TimeoutConfig {
114 backoff,
115 ..TimeoutConfig::default()
116 }
117 }
118}
119
120impl std::str::FromStr for TimeoutConfig {
137 type Err = ParseError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 Ok(s.parse::<BackoffInterval>()?.into())
141 }
142}
143
144#[cfg(feature = "serde")]
153impl serde::Serialize for TimeoutConfig {
154 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
155 self.backoff.serialize(serializer)
156 }
157}
158
159#[cfg(feature = "serde")]
162impl<'de> serde::Deserialize<'de> for TimeoutConfig {
163 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
164 Ok(BackoffInterval::deserialize(deserializer)?.into())
165 }
166}
167
168#[cfg(feature = "schemars")]
177const BACKOFF_INTERVAL_PATTERN: &str = r"^\d+(\.\d+)?\s*(ns|us|ms|s|m|h|d|nanoseconds?|microseconds?|milliseconds?|seconds?|minutes?|hours?|days?)\s*\.\.\s*\d+(\.\d+)?\s*(ns|us|ms|s|m|h|d|nanoseconds?|microseconds?|milliseconds?|seconds?|minutes?|hours?|days?)$";
178
179#[cfg(feature = "schemars")]
180impl schemars::JsonSchema for BackoffInterval {
181 fn inline_schema() -> bool {
182 true
183 }
184
185 fn schema_name() -> std::borrow::Cow<'static, str> {
186 "BackoffInterval".into()
187 }
188
189 fn schema_id() -> std::borrow::Cow<'static, str> {
190 concat!(module_path!(), "::BackoffInterval").into()
191 }
192
193 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
194 schemars::json_schema!({
195 "type": "string",
196 "pattern": BACKOFF_INTERVAL_PATTERN,
197 "examples": ["250ms..1m", "10ms..60s", "0.5s..5m", "1s..1h"],
198 "description": concat!(
199 "Timeout floor and ceiling expressed as a duration range: \"<min>..<max>\".\n",
200 "\n",
201 "Each bound is a non-negative number followed by a unit. ",
202 "Fractional values are allowed (e.g. \"0.5s\"). ",
203 "Spaces between the number and the unit are permitted (e.g. \"10 ms\").\n",
204 "\n",
205 "Supported units:\n",
206 " ns / nanoseconds\n",
207 " us / microseconds\n",
208 " ms / milliseconds\n",
209 " s / seconds\n",
210 " m / minutes\n",
211 " h / hours\n",
212 " d / days\n",
213 "\n",
214 "Both compact (\"ms\", \"s\", \"m\") and verbose (\"milliseconds\", \"seconds\", \"minutes\") ",
215 "forms are accepted.\n",
216 "\n",
217 "The minimum must be greater than zero and must not exceed the maximum.\n",
218 "\n",
219 "Examples: \"250ms..1m\", \"10ms..60s\", \"0.5s..5m\", \"1s..1h\"."
220 )
221 })
222 }
223}
224
225#[cfg(feature = "schemars")]
228impl schemars::JsonSchema for TimeoutConfig {
229 fn inline_schema() -> bool {
230 true
231 }
232
233 fn schema_name() -> std::borrow::Cow<'static, str> {
234 "TimeoutConfig".into()
235 }
236
237 fn schema_id() -> std::borrow::Cow<'static, str> {
238 concat!(module_path!(), "::TimeoutConfig").into()
239 }
240
241 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
242 BackoffInterval::json_schema(generator)
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum ParseError {
254 Empty,
256 MissingRangeSeparator,
258 InvalidMin(String),
260 InvalidMax(String),
262 ZeroMin,
264 ZeroMax,
266 MinOverflow(u64),
268 MaxOverflow(u64),
270 MinExceedsMax { min_ms: u64, max_ms: u64 },
272}
273
274impl fmt::Display for ParseError {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 match self {
277 Self::Empty => write!(f, "empty input"),
278 Self::MissingRangeSeparator => write!(f, "missing '..' range separator"),
279 Self::InvalidMin(s) => write!(f, "invalid min timeout: {s}"),
280 Self::InvalidMax(s) => write!(f, "invalid max timeout: {s}"),
281 Self::ZeroMin => write!(f, "min timeout must be > 0"),
282 Self::ZeroMax => write!(f, "max timeout must be > 0"),
283 Self::MinOverflow(v) => write!(f, "min timeout ({v}ms) exceeds u32::MAX"),
284 Self::MaxOverflow(v) => write!(f, "max timeout ({v}ms) exceeds u32::MAX"),
285 Self::MinExceedsMax { min_ms, max_ms } => {
286 write!(
287 f,
288 "min timeout ({min_ms}ms) exceeds max timeout ({max_ms}ms)"
289 )
290 }
291 }
292 }
293}
294
295impl std::error::Error for ParseError {}
296
297#[cfg(feature = "serde")]
303impl serde::Serialize for BackoffInterval {
304 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
305 serializer.collect_str(self)
306 }
307}
308
309#[cfg(feature = "serde")]
312impl<'de> serde::Deserialize<'de> for BackoffInterval {
313 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
314 let s = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
315 s.parse().map_err(serde::de::Error::custom)
316 }
317}
318
319impl FromStr for BackoffInterval {
320 type Err = ParseError;
321
322 fn from_str(s: &str) -> Result<Self, Self::Err> {
323 let s = s.trim();
324 if s.is_empty() {
325 return Err(ParseError::Empty);
326 }
327
328 let (min_str, max_str) = s
329 .split_once("..")
330 .ok_or(ParseError::MissingRangeSeparator)?;
331
332 let min_raw =
333 parse_duration_ms(min_str.trim()).map_err(|e| ParseError::InvalidMin(e.to_string()))?;
334 let max_raw =
335 parse_duration_ms(max_str.trim()).map_err(|e| ParseError::InvalidMax(e.to_string()))?;
336
337 if min_raw == 0 {
338 return Err(ParseError::ZeroMin);
339 }
340 if max_raw == 0 {
341 return Err(ParseError::ZeroMax);
342 }
343 if min_raw > max_raw {
344 return Err(ParseError::MinExceedsMax {
345 min_ms: min_raw,
346 max_ms: max_raw,
347 });
348 }
349
350 let min_u32 = u32::try_from(min_raw).map_err(|_| ParseError::MinOverflow(min_raw))?;
351 let max_u32 = u32::try_from(max_raw).map_err(|_| ParseError::MaxOverflow(max_raw))?;
352
353 let min_ms = MillisNonZero::new(min_u32).unwrap();
355 let max_ms = MillisNonZero::new(max_u32).unwrap();
356
357 Ok(BackoffInterval { min_ms, max_ms })
358 }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
367pub(crate) enum DurationParseError {
368 Empty,
369 InvalidNumber(String),
370 UnknownUnit(String),
371 TruncatesToZero(String),
373}
374
375impl fmt::Display for DurationParseError {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 match self {
378 Self::Empty => write!(f, "empty duration string"),
379 Self::InvalidNumber(s) => write!(f, "invalid number: {s}"),
380 Self::UnknownUnit(s) => write!(f, "unknown unit: {s}"),
381 Self::TruncatesToZero(s) => {
382 write!(f, "duration '{s}' truncates to 0ms")
383 }
384 }
385 }
386}
387
388fn parse_duration_ms(s: &str) -> Result<u64, DurationParseError> {
408 let s = s.trim();
409 if s.is_empty() {
410 return Err(DurationParseError::Empty);
411 }
412
413 if s == "0" {
415 return Ok(0);
416 }
417
418 let num_end = s
420 .find(|c: char| !c.is_ascii_digit() && c != '.' && c != '-' && c != '+')
421 .unwrap_or(s.len());
422
423 let num_str = s[..num_end].trim();
424 let unit_str = s[num_end..].trim();
425
426 if num_str.is_empty() {
427 return Err(DurationParseError::InvalidNumber(s.to_string()));
428 }
429 if unit_str.is_empty() {
430 return Err(DurationParseError::UnknownUnit(
431 "missing unit suffix".to_string(),
432 ));
433 }
434
435 let value: f64 = num_str
436 .parse()
437 .map_err(|_| DurationParseError::InvalidNumber(num_str.to_string()))?;
438
439 let factor_ns: f64 = match unit_str {
442 "nanoseconds" | "nanosecond" | "nanos" | "nano" | "nsecs" | "nsec" | "ns" => 1.0,
443 "microseconds" | "microsecond" | "micros" | "micro" | "usecs" | "usec" | "us"
444 | "\u{00B5}s" | "\u{00B5}secs" | "\u{00B5}sec" | "\u{03BC}s" | "\u{03BC}secs"
445 | "\u{03BC}sec" => 1_000.0,
446 "milliseconds" | "millisecond" | "millis" | "milli" | "msecs" | "msec" | "ms" => {
447 1_000_000.0
448 }
449 "seconds" | "second" | "secs" | "sec" | "s" => 1_000_000_000.0,
450 "minutes" | "minute" | "mins" | "min" | "m" => 60.0 * 1_000_000_000.0,
451 "hours" | "hour" | "hrs" | "hr" | "h" => 3_600.0 * 1_000_000_000.0,
452 "days" | "day" | "d" => 86_400.0 * 1_000_000_000.0,
453 _ => return Err(DurationParseError::UnknownUnit(unit_str.to_string())),
454 };
455
456 let total_ns = value * factor_ns;
457 let ms = (total_ns / 1_000_000.0).round() as u64;
458
459 if ms == 0 && value != 0.0 {
461 return Err(DurationParseError::TruncatesToZero(s.to_string()));
462 }
463
464 Ok(ms)
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
476 fn parse_milliseconds() {
477 assert_eq!(parse_duration_ms("10ms").unwrap(), 10);
478 assert_eq!(parse_duration_ms("100ms").unwrap(), 100);
479 assert_eq!(parse_duration_ms("1ms").unwrap(), 1);
480 assert_eq!(parse_duration_ms("0ms").unwrap(), 0);
481 }
482
483 #[test]
484 fn parse_seconds() {
485 assert_eq!(parse_duration_ms("1s").unwrap(), 1_000);
486 assert_eq!(parse_duration_ms("10s").unwrap(), 10_000);
487 assert_eq!(parse_duration_ms("1sec").unwrap(), 1_000);
488 assert_eq!(parse_duration_ms("2secs").unwrap(), 2_000);
489 assert_eq!(parse_duration_ms("1second").unwrap(), 1_000);
490 assert_eq!(parse_duration_ms("3seconds").unwrap(), 3_000);
491 }
492
493 #[test]
494 fn parse_fractional() {
495 assert_eq!(parse_duration_ms("0.5s").unwrap(), 500);
496 assert_eq!(parse_duration_ms("1.5s").unwrap(), 1_500);
497 assert_eq!(parse_duration_ms("0.1s").unwrap(), 100);
498 assert_eq!(parse_duration_ms("2.5ms").unwrap(), 3); }
500
501 #[test]
502 fn parse_minutes() {
503 assert_eq!(parse_duration_ms("1min").unwrap(), 60_000);
504 assert_eq!(parse_duration_ms("2mins").unwrap(), 120_000);
505 assert_eq!(parse_duration_ms("1m").unwrap(), 60_000);
506 assert_eq!(parse_duration_ms("1minute").unwrap(), 60_000);
507 assert_eq!(parse_duration_ms("3minutes").unwrap(), 180_000);
508 }
509
510 #[test]
511 fn parse_hours() {
512 assert_eq!(parse_duration_ms("1h").unwrap(), 3_600_000);
513 assert_eq!(parse_duration_ms("1hr").unwrap(), 3_600_000);
514 assert_eq!(parse_duration_ms("2hrs").unwrap(), 7_200_000);
515 assert_eq!(parse_duration_ms("1hour").unwrap(), 3_600_000);
516 assert_eq!(parse_duration_ms("3hours").unwrap(), 10_800_000);
517 }
518
519 #[test]
520 fn parse_days() {
521 assert_eq!(parse_duration_ms("1d").unwrap(), 86_400_000);
522 assert_eq!(parse_duration_ms("1day").unwrap(), 86_400_000);
523 assert_eq!(parse_duration_ms("2days").unwrap(), 172_800_000);
524 }
525
526 #[test]
527 fn parse_microseconds() {
528 assert_eq!(parse_duration_ms("1000us").unwrap(), 1);
529 assert_eq!(parse_duration_ms("500us").unwrap(), 1); assert_eq!(parse_duration_ms("1000\u{03BC}s").unwrap(), 1); assert_eq!(parse_duration_ms("1000\u{00B5}s").unwrap(), 1); assert_eq!(parse_duration_ms("1000usec").unwrap(), 1);
533 assert_eq!(parse_duration_ms("1000usecs").unwrap(), 1);
534 assert_eq!(parse_duration_ms("1000micro").unwrap(), 1);
535 assert_eq!(parse_duration_ms("1000micros").unwrap(), 1);
536 assert_eq!(parse_duration_ms("1000microsecond").unwrap(), 1);
537 assert_eq!(parse_duration_ms("1000microseconds").unwrap(), 1);
538 }
539
540 #[test]
541 fn parse_nanoseconds() {
542 assert_eq!(parse_duration_ms("1000000ns").unwrap(), 1);
543 assert_eq!(parse_duration_ms("1000000nsec").unwrap(), 1);
544 assert_eq!(parse_duration_ms("1000000nsecs").unwrap(), 1);
545 assert_eq!(parse_duration_ms("1000000nano").unwrap(), 1);
546 assert_eq!(parse_duration_ms("1000000nanos").unwrap(), 1);
547 assert_eq!(parse_duration_ms("1000000nanosecond").unwrap(), 1);
548 assert_eq!(parse_duration_ms("1000000nanoseconds").unwrap(), 1);
549 }
550
551 #[test]
552 fn parse_millisecond_aliases() {
553 assert_eq!(parse_duration_ms("10msec").unwrap(), 10);
554 assert_eq!(parse_duration_ms("10msecs").unwrap(), 10);
555 assert_eq!(parse_duration_ms("10milli").unwrap(), 10);
556 assert_eq!(parse_duration_ms("10millis").unwrap(), 10);
557 assert_eq!(parse_duration_ms("10millisecond").unwrap(), 10);
558 assert_eq!(parse_duration_ms("10milliseconds").unwrap(), 10);
559 }
560
561 #[test]
562 fn parse_with_spaces() {
563 assert_eq!(parse_duration_ms("10 ms").unwrap(), 10);
564 assert_eq!(parse_duration_ms(" 1 s ").unwrap(), 1_000);
565 }
566
567 #[test]
568 fn parse_bare_zero() {
569 assert_eq!(parse_duration_ms("0").unwrap(), 0);
570 }
571
572 #[test]
573 fn parse_truncates_to_zero() {
574 assert!(matches!(
575 parse_duration_ms("1ns"),
576 Err(DurationParseError::TruncatesToZero(_))
577 ));
578 }
579
580 #[test]
581 fn parse_unknown_unit() {
582 assert!(matches!(
583 parse_duration_ms("10xyz"),
584 Err(DurationParseError::UnknownUnit(_))
585 ));
586 }
587
588 #[test]
589 fn parse_missing_unit() {
590 assert!(matches!(
591 parse_duration_ms("42"),
592 Err(DurationParseError::UnknownUnit(_))
593 ));
594 }
595
596 #[test]
597 fn parse_empty() {
598 assert!(matches!(
599 parse_duration_ms(""),
600 Err(DurationParseError::Empty)
601 ));
602 }
603
604 #[test]
609 fn parse_basic_range() {
610 let b: BackoffInterval = "10ms..1s".parse().unwrap();
611 assert_eq!(b.min_ms.get(), 10);
612 assert_eq!(b.max_ms.get(), 1_000);
613 }
614
615 #[test]
616 fn parse_fractional_range() {
617 let b: BackoffInterval = "0.5s..1.5s".parse().unwrap();
618 assert_eq!(b.min_ms.get(), 500);
619 assert_eq!(b.max_ms.get(), 1_500);
620 }
621
622 #[test]
623 fn parse_with_spaces_around() {
624 let b: BackoffInterval = " 10ms .. 1s ".parse().unwrap();
625 assert_eq!(b.min_ms.get(), 10);
626 assert_eq!(b.max_ms.get(), 1_000);
627 }
628
629 #[test]
630 fn parse_same_min_max() {
631 let b: BackoffInterval = "100ms..100ms".parse().unwrap();
632 assert_eq!(b.min_ms.get(), 100);
633 assert_eq!(b.max_ms.get(), 100);
634 }
635
636 #[test]
637 fn parse_large_values() {
638 let b: BackoffInterval = "1h..3d".parse().unwrap();
639 assert_eq!(b.min_ms.get(), 3_600_000);
640 assert_eq!(b.max_ms.get(), 259_200_000);
641 }
642
643 #[test]
644 fn parse_mixed_units() {
645 let b: BackoffInterval = "100ms..1min".parse().unwrap();
646 assert_eq!(b.min_ms.get(), 100);
647 assert_eq!(b.max_ms.get(), 60_000);
648 }
649
650 #[test]
651 fn err_empty() {
652 assert_eq!(
653 "".parse::<BackoffInterval>().unwrap_err(),
654 ParseError::Empty
655 );
656 }
657
658 #[test]
659 fn err_missing_separator() {
660 assert_eq!(
661 "10ms".parse::<BackoffInterval>().unwrap_err(),
662 ParseError::MissingRangeSeparator
663 );
664 }
665
666 #[test]
667 fn err_zero_min() {
668 assert_eq!(
669 "0ms..1s".parse::<BackoffInterval>().unwrap_err(),
670 ParseError::ZeroMin
671 );
672 }
673
674 #[test]
675 fn err_min_exceeds_max() {
676 assert!(matches!(
677 "10s..1s".parse::<BackoffInterval>(),
678 Err(ParseError::MinExceedsMax { .. })
679 ));
680 }
681
682 #[test]
683 fn err_invalid_min() {
684 assert!(matches!(
685 "abc..1s".parse::<BackoffInterval>(),
686 Err(ParseError::InvalidMin(_))
687 ));
688 }
689
690 #[test]
691 fn err_invalid_max() {
692 assert!(matches!(
693 "10ms..abc".parse::<BackoffInterval>(),
694 Err(ParseError::InvalidMax(_))
695 ));
696 }
697
698 #[test]
703 fn display_basic() {
704 let b: BackoffInterval = "10ms..1min".parse().unwrap();
705 assert_eq!(b.to_string(), "10ms..1m");
706 }
707
708 #[test]
709 fn display_sub_second() {
710 let b: BackoffInterval = "500ms..1500ms".parse().unwrap();
711 assert_eq!(b.to_string(), "500ms..1500ms");
712 }
713
714 #[test]
715 fn display_round_trip() {
716 for original in &["10ms..60s", "250ms..1m", "1s..1h", "100ms..1d"] {
717 let b: BackoffInterval = original.parse().unwrap();
718 let displayed = b.to_string();
719 let reparsed: BackoffInterval = displayed.parse().unwrap();
720 assert_eq!(b, reparsed, "round-trip failed for {original}");
721 }
722 }
723
724 #[test]
729 fn into_timeout_config() {
730 let b: BackoffInterval = "50ms..30s".parse().unwrap();
731 let cfg: TimeoutConfig = b.into();
732 assert_eq!(cfg.backoff.min_ms.get(), 50);
733 assert_eq!(cfg.backoff.max_ms.get(), 30_000);
734 assert_eq!(cfg.quantile, 0.9999);
736 assert_eq!(cfg.safety_factor, 2.0);
737 }
738
739 #[cfg(feature = "serde")]
744 mod serde_tests {
745 use super::*;
746
747 #[test]
748 fn serialize_to_string() {
749 let b: BackoffInterval = "250ms..1m".parse().unwrap();
750 let json = serde_json::to_string(&b).unwrap();
751 assert_eq!(json, r#""250ms..1m""#);
752 }
753
754 #[test]
755 fn deserialize_from_string() {
756 let b: BackoffInterval = serde_json::from_str(r#""10ms..60s""#).unwrap();
757 assert_eq!(b.min_ms.get(), 10);
758 assert_eq!(b.max_ms.get(), 60_000);
759 }
760
761 #[test]
762 fn serde_round_trip() {
763 for s in &["10ms..60s", "250ms..1m", "1s..1h", "100ms..1d"] {
764 let original: BackoffInterval = s.parse().unwrap();
765 let json = serde_json::to_string(&original).unwrap();
766 let restored: BackoffInterval = serde_json::from_str(&json).unwrap();
767 assert_eq!(original, restored, "round-trip failed for {s}");
768 }
769 }
770
771 #[test]
772 fn deserialize_error_propagated() {
773 let err = serde_json::from_str::<BackoffInterval>(r#""not-a-range""#);
774 assert!(err.is_err());
775 }
776 }
777
778 #[cfg(feature = "schemars")]
783 mod schemars_tests {
784 use schemars::JsonSchema;
785
786 use super::*;
787
788 fn backoff_schema() -> schemars::Schema {
789 let mut generator = schemars::SchemaGenerator::default();
790 BackoffInterval::json_schema(&mut generator)
791 }
792
793 fn timeout_schema() -> schemars::Schema {
794 let mut generator = schemars::SchemaGenerator::default();
795 TimeoutConfig::json_schema(&mut generator)
796 }
797
798 #[test]
799 fn backoff_interval_is_string_type() {
800 let schema = backoff_schema();
801 let obj = schema.as_object().unwrap();
802 assert_eq!(obj["type"], "string");
803 }
804
805 #[test]
806 fn backoff_interval_has_pattern() {
807 let schema = backoff_schema();
808 let obj = schema.as_object().unwrap();
809 assert!(obj.contains_key("pattern"), "schema should have a pattern");
810 assert!(
812 obj["pattern"].as_str().unwrap().contains(r"\.\."),
813 "pattern should include the '..' separator"
814 );
815 }
816
817 #[test]
818 fn backoff_interval_has_examples() {
819 let schema = backoff_schema();
820 let obj = schema.as_object().unwrap();
821 assert!(obj.contains_key("examples"), "schema should have examples");
822 }
823
824 #[test]
825 fn timeout_config_schema_matches_backoff_interval() {
826 assert_eq!(
828 backoff_schema().as_object().unwrap()["type"],
829 timeout_schema().as_object().unwrap()["type"],
830 );
831 assert_eq!(
832 backoff_schema().as_object().unwrap()["pattern"],
833 timeout_schema().as_object().unwrap()["pattern"],
834 );
835 }
836
837 #[test]
838 fn backoff_interval_schema_name() {
839 assert_eq!(BackoffInterval::schema_name(), "BackoffInterval");
840 }
841
842 #[test]
843 fn timeout_config_schema_name() {
844 assert_eq!(TimeoutConfig::schema_name(), "TimeoutConfig");
845 }
846
847 #[test]
848 fn both_are_inline() {
849 assert!(BackoffInterval::inline_schema());
850 assert!(TimeoutConfig::inline_schema());
851 }
852 }
853}