Skip to main content

mxr_rules/
lib.rs

1pub mod action;
2pub mod condition;
3pub mod engine;
4pub mod history;
5pub mod shell_hook;
6
7pub use action::{RuleAction, SnoozeDuration};
8pub use condition::{Conditions, FieldCondition, MessageView, StringMatch};
9pub use engine::{DryRunMatch, DryRunResult, EvaluationResult, RuleEngine};
10pub use history::{RuleExecutionLog, RuleMatchEntry};
11
12use chrono::{DateTime, Utc};
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16/// Unique identifier for a rule.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct RuleId(pub String);
19
20impl RuleId {
21    pub fn new() -> Self {
22        Self(Uuid::now_v7().to_string())
23    }
24}
25
26impl Default for RuleId {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl std::fmt::Display for RuleId {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "{}", self.0)
35    }
36}
37
38/// A declarative mail rule.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct Rule {
41    pub id: RuleId,
42    pub name: String,
43    pub enabled: bool,
44    /// Lower number = runs first.
45    pub priority: i32,
46    pub conditions: Conditions,
47    pub actions: Vec<RuleAction>,
48    pub created_at: DateTime<Utc>,
49    pub updated_at: DateTime<Utc>,
50}
51
52#[cfg(test)]
53pub(crate) mod tests {
54    use crate::condition::MessageView;
55    use chrono::{DateTime, Utc};
56
57    /// Test message implementation for condition evaluation.
58    pub(crate) struct TestMessage {
59        pub from: String,
60        pub to: Vec<String>,
61        pub subject: String,
62        pub labels: Vec<String>,
63        pub has_attachment: bool,
64        pub size: u64,
65        pub date: DateTime<Utc>,
66        pub is_unread: bool,
67        pub is_starred: bool,
68        pub has_unsub: bool,
69        pub body: Option<String>,
70    }
71
72    impl MessageView for TestMessage {
73        fn sender_email(&self) -> &str {
74            &self.from
75        }
76        fn to_emails(&self) -> &[String] {
77            &self.to
78        }
79        fn subject(&self) -> &str {
80            &self.subject
81        }
82        fn labels(&self) -> &[String] {
83            &self.labels
84        }
85        fn has_attachment(&self) -> bool {
86            self.has_attachment
87        }
88        fn size_bytes(&self) -> u64 {
89            self.size
90        }
91        fn date(&self) -> DateTime<Utc> {
92            self.date
93        }
94        fn is_unread(&self) -> bool {
95            self.is_unread
96        }
97        fn is_starred(&self) -> bool {
98            self.is_starred
99        }
100        fn has_unsubscribe(&self) -> bool {
101            self.has_unsub
102        }
103        fn body_text(&self) -> Option<&str> {
104            self.body.as_deref()
105        }
106    }
107
108    pub(crate) fn newsletter_msg() -> TestMessage {
109        TestMessage {
110            from: "newsletter@substack.com".into(),
111            to: vec!["user@example.com".into()],
112            subject: "This Week in Rust #580".into(),
113            labels: vec!["INBOX".into(), "newsletters".into()],
114            has_attachment: false,
115            size: 15000,
116            date: Utc::now(),
117            is_unread: false,
118            is_starred: false,
119            has_unsub: true,
120            body: Some("Here's your weekly Rust digest...".into()),
121        }
122    }
123
124    pub(crate) fn work_email_msg() -> TestMessage {
125        TestMessage {
126            from: "boss@company.com".into(),
127            to: vec!["user@example.com".into()],
128            subject: "Q1 Review".into(),
129            labels: vec!["INBOX".into(), "work".into()],
130            has_attachment: true,
131            size: 52000,
132            date: Utc::now(),
133            is_unread: true,
134            is_starred: true,
135            has_unsub: false,
136            body: Some("Please review the attached Q1 report.".into()),
137        }
138    }
139}