Skip to main content

harn_vm/triggers/
flow_control.rs

1use std::time::Duration;
2
3#[derive(Clone)]
4pub struct TriggerExpressionSpec {
5    pub raw: String,
6    pub callable: crate::value::VmCallable,
7}
8
9impl std::fmt::Debug for TriggerExpressionSpec {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        f.debug_struct("TriggerExpressionSpec")
12            .field("raw", &self.raw)
13            .finish()
14    }
15}
16
17#[derive(Clone, Debug)]
18pub struct TriggerConcurrencyConfig {
19    pub key: Option<TriggerExpressionSpec>,
20    pub max: u32,
21}
22
23#[derive(Clone, Debug)]
24pub struct TriggerThrottleConfig {
25    pub key: Option<TriggerExpressionSpec>,
26    pub period: Duration,
27    pub max: u32,
28}
29
30#[derive(Clone, Debug)]
31pub struct TriggerRateLimitConfig {
32    pub key: Option<TriggerExpressionSpec>,
33    pub period: Duration,
34    pub max: u32,
35}
36
37#[derive(Clone, Debug)]
38pub struct TriggerDebounceConfig {
39    pub key: TriggerExpressionSpec,
40    pub period: Duration,
41}
42
43#[derive(Clone, Debug)]
44pub struct TriggerSingletonConfig {
45    pub key: Option<TriggerExpressionSpec>,
46}
47
48#[derive(Clone, Debug)]
49pub struct TriggerBatchConfig {
50    pub key: Option<TriggerExpressionSpec>,
51    pub size: u32,
52    pub timeout: Duration,
53}
54
55#[derive(Clone, Debug)]
56pub struct TriggerPriorityOrderConfig {
57    pub key: TriggerExpressionSpec,
58    pub order: Vec<String>,
59}
60
61#[derive(Clone, Debug, Default)]
62pub struct TriggerFlowControlConfig {
63    pub concurrency: Option<TriggerConcurrencyConfig>,
64    pub throttle: Option<TriggerThrottleConfig>,
65    pub rate_limit: Option<TriggerRateLimitConfig>,
66    pub debounce: Option<TriggerDebounceConfig>,
67    pub singleton: Option<TriggerSingletonConfig>,
68    pub batch: Option<TriggerBatchConfig>,
69    pub priority: Option<TriggerPriorityOrderConfig>,
70}
71
72pub fn parse_flow_control_duration(raw: &str) -> Result<Duration, String> {
73    let Some((amount, unit)) = crate::duration_parse::split_amount_unit(raw) else {
74        return Err(format!("invalid duration '{raw}': expected <int><unit>"));
75    };
76    if unit.is_empty() {
77        return Err(format!("invalid duration '{raw}': missing unit suffix"));
78    }
79    let value = amount
80        .parse::<u64>()
81        .map_err(|_| format!("invalid duration '{raw}': expected integer prefix"))?;
82    if value == 0 {
83        return Err(format!(
84            "invalid duration '{raw}': duration must be positive"
85        ));
86    }
87    // Seconds-resolution units only (no ms): s/m/h/d/w. The shared table is
88    // in milliseconds, so divide to whole seconds.
89    let secs_factor = match unit.as_str() {
90        "s" | "m" | "h" | "d" | "w" => {
91            crate::duration_parse::unit_to_millis(&unit).unwrap() / 1_000
92        }
93        other => {
94            return Err(format!(
95                "invalid duration '{raw}': unsupported unit '{other}', expected s/m/h/d/w"
96            ))
97        }
98    };
99    Ok(Duration::from_secs(value.saturating_mul(secs_factor)))
100}