Skip to main content

deaddrop_core/
rules.rs

1//! Intentionally limited automation. Not a general-purpose language.
2
3use crate::{DdError, ErrorCode, Result};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Rule {
7    pub name: String,
8    pub when: String,
9    pub filters: Vec<(String, String)>,
10    pub action: String,
11}
12
13pub fn parse_rules(src: &str) -> Result<Vec<Rule>> {
14    let mut rules = Vec::new();
15    let mut cur: Option<Rule> = None;
16    for raw in src.lines() {
17        let line = raw.trim();
18        if line.is_empty() || line.starts_with('#') || line == "}" {
19            if line == "}"
20                && let Some(r) = cur.take()
21            {
22                rules.push(r);
23            }
24            continue;
25        }
26        if let Some(rest) = line.strip_prefix("rule ") {
27            let name = rest.trim().trim_end_matches('{').trim().to_string();
28            cur = Some(Rule {
29                name,
30                when: String::new(),
31                filters: Vec::new(),
32                action: String::new(),
33            });
34            continue;
35        }
36        let Some(rule) = cur.as_mut() else {
37            return Err(DdError::protocol(
38                ErrorCode::Ddp1001InvalidFrame,
39                "rule statement outside block",
40            ));
41        };
42        if let Some(w) = line.strip_prefix("when ") {
43            rule.when = w.trim().to_string();
44        } else if let Some(i) = line.strip_prefix("if ") {
45            let i = i.trim();
46            if let Some((k, v)) = i.split_once("==") {
47                rule.filters
48                    .push((k.trim().to_string(), v.trim().trim_matches('"').to_string()));
49            }
50        } else if let Some(t) = line.strip_prefix("then ") {
51            rule.action = t.trim().to_string();
52        }
53    }
54    if let Some(r) = cur.take() {
55        rules.push(r);
56    }
57    Ok(rules)
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn parse_pin_rule() {
66        let src = r#"
67rule auto-pin-releases {
68    when drop.received
69    if drop.channel == "software/releases"
70    if drop.publisher.trust == "verified"
71    then drop.pin
72}
73"#;
74        let r = parse_rules(src).unwrap();
75        assert_eq!(r.len(), 1);
76        assert_eq!(r[0].name, "auto-pin-releases");
77        assert_eq!(r[0].action, "drop.pin");
78        assert_eq!(r[0].filters.len(), 2);
79    }
80}