harn_vm/triggers/
flow_control.rs1use 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 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}