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