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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct Rule {
41 pub id: RuleId,
42 pub name: String,
43 pub enabled: bool,
44 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 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}