solti_model/domain/policy/jitter.rs
1//! # Jitter policy
2//!
3//! [`JitterPolicy`] selects how retry delay randomness is applied.
4
5use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7
8use crate::error::{ModelError, ModelResult};
9
10/// Jitter applied to a backoff delay.
11///
12/// | Variant | Delay range |
13/// |----------------|------------------------------------------|
14/// | `None` | `base` |
15/// | `Full` | `[0, base]` |
16/// | `Equal` | `[base / 2, base]` |
17/// | `Decorrelated` | `[first, min(base * 3, max)]` |
18///
19/// The execution layer applies the selected policy.
20///
21/// ## Example
22///
23/// ```
24/// use solti_model::JitterPolicy;
25///
26/// assert_eq!("equal".parse::<JitterPolicy>().unwrap(), JitterPolicy::Equal);
27/// assert_eq!("".parse::<JitterPolicy>().unwrap(), JitterPolicy::Full);
28/// ```
29#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
30#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
31#[serde(rename_all = "camelCase")]
32#[non_exhaustive]
33pub enum JitterPolicy {
34 /// Full jitter: delay is uniformly sampled from `[0, base]`.
35 #[default]
36 Full,
37 /// No randomness applied. Backoff durations remain fixed.
38 None,
39 /// Equal jitter samples from `[base / 2, base]`.
40 Equal,
41 /// Memoryless randomized band: delay is sampled uniformly from
42 /// `[first, min(base * 3, max)]`.
43 Decorrelated,
44}
45
46impl FromStr for JitterPolicy {
47 type Err = ModelError;
48 fn from_str(s: &str) -> ModelResult<Self> {
49 let s = s.trim();
50 if s.is_empty() || s.eq_ignore_ascii_case("full") || s.eq_ignore_ascii_case("default") {
51 Ok(JitterPolicy::Full)
52 } else if s.eq_ignore_ascii_case("none") {
53 Ok(JitterPolicy::None)
54 } else if s.eq_ignore_ascii_case("equal") {
55 Ok(JitterPolicy::Equal)
56 } else if s.eq_ignore_ascii_case("decorrelated") {
57 Ok(JitterPolicy::Decorrelated)
58 } else {
59 Err(ModelError::UnknownJitter(s.to_string()))
60 }
61 }
62}