hopr_transport_probe/
config.rs1use serde::{Deserialize, Serialize};
2use validator::Validate;
3
4fn validate_interval_ge_timeout(config: &ProbeConfig) -> Result<(), validator::ValidationError> {
5 if config.interval < config.timeout {
6 let mut err = validator::ValidationError::new("interval_less_than_timeout");
7 err.message = Some(
8 format!(
9 "probe interval ({:?}) must be >= timeout ({:?}) to prevent overlapping rounds",
10 config.interval, config.timeout
11 )
12 .into(),
13 );
14 return Err(err);
15 }
16 Ok(())
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, smart_default::SmartDefault, Validate, Serialize, Deserialize)]
21#[serde(deny_unknown_fields)]
22#[validate(schema(function = "validate_interval_ge_timeout"))]
23pub struct ProbeConfig {
24 #[default(default_max_probe_timeout())]
26 #[serde(default = "default_max_probe_timeout", with = "humantime_serde")]
27 pub timeout: std::time::Duration,
28
29 #[validate(range(min = 1))]
31 #[default(default_max_parallel_probes())]
32 #[serde(default = "default_max_parallel_probes")]
33 pub max_parallel_probes: usize,
34
35 #[serde(default = "default_probing_interval", with = "humantime_serde")]
40 #[default(default_probing_interval())]
41 pub interval: std::time::Duration,
42
43 #[serde(default = "default_recheck_threshold", with = "humantime_serde")]
45 #[default(default_recheck_threshold())]
46 pub recheck_threshold: std::time::Duration,
47}
48
49const DEFAULT_MAX_PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
51
52const DEFAULT_MAX_PARALLEL_PROBES: usize = 50;
54
55const DEFAULT_REPEATED_PROBING_DELAY: std::time::Duration = std::time::Duration::from_secs(5);
57
58const DEFAULT_PROBE_RECHECK_THRESHOLD: std::time::Duration = std::time::Duration::from_secs(60);
60
61#[inline]
62const fn default_max_probe_timeout() -> std::time::Duration {
63 DEFAULT_MAX_PROBE_TIMEOUT
64}
65
66#[inline]
67const fn default_max_parallel_probes() -> usize {
68 DEFAULT_MAX_PARALLEL_PROBES
69}
70
71#[inline]
72const fn default_probing_interval() -> std::time::Duration {
73 DEFAULT_REPEATED_PROBING_DELAY
74}
75
76#[inline]
77const fn default_recheck_threshold() -> std::time::Duration {
78 DEFAULT_PROBE_RECHECK_THRESHOLD
79}
80
81#[cfg(test)]
82mod tests {
83 use anyhow::Context;
84 use validator::Validate;
85
86 use super::*;
87
88 #[test]
89 fn probe_config_default_is_valid() -> anyhow::Result<()> {
90 let cfg = ProbeConfig::default();
91 cfg.validate().context("default ProbeConfig should be valid")?;
92 assert!(cfg.interval >= cfg.timeout);
93 insta::assert_yaml_snapshot!(cfg);
94 Ok(())
95 }
96
97 #[test]
98 fn probe_config_zero_parallel_probes_is_rejected() -> anyhow::Result<()> {
99 let cfg = ProbeConfig {
100 max_parallel_probes: 0,
101 ..Default::default()
102 };
103 let err = cfg.validate().err().context("expected validation error")?;
104 anyhow::ensure!(
105 err.field_errors().contains_key("max_parallel_probes"),
106 "expected max_parallel_probes field error, got: {err}"
107 );
108 Ok(())
109 }
110
111 #[test]
112 fn probe_config_one_parallel_probe_is_valid() -> anyhow::Result<()> {
113 let cfg = ProbeConfig {
114 max_parallel_probes: 1,
115 ..Default::default()
116 };
117 cfg.validate().context("single parallel probe should be valid")?;
118 Ok(())
119 }
120
121 #[test]
122 fn interval_less_than_timeout_should_be_rejected() {
123 let config = ProbeConfig {
124 timeout: std::time::Duration::from_secs(10),
125 interval: std::time::Duration::from_secs(5),
126 ..Default::default()
127 };
128 assert!(config.validate().is_err(), "interval < timeout must be rejected");
129 }
130
131 #[test]
132 fn interval_equal_to_timeout_should_be_accepted() {
133 let config = ProbeConfig {
134 timeout: std::time::Duration::from_secs(5),
135 interval: std::time::Duration::from_secs(5),
136 ..Default::default()
137 };
138 assert!(config.validate().is_ok());
139 }
140}