1use async_trait::async_trait;
4use rusmes_proto::{Mail, MailState};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::time::Duration;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum MailetAction {
12 Continue,
14 ChangeState(MailState),
16 Drop,
18 Defer(Duration),
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct MailetConfig {
25 pub name: String,
27 pub params: HashMap<String, String>,
29}
30
31impl MailetConfig {
32 pub fn new(name: impl Into<String>) -> Self {
34 Self {
35 name: name.into(),
36 params: HashMap::new(),
37 }
38 }
39
40 pub fn with_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
42 self.params.insert(key.into(), value.into());
43 self
44 }
45
46 pub fn get_param(&self, key: &str) -> Option<&str> {
48 self.params.get(key).map(|s| s.as_str())
49 }
50
51 pub fn require_param(&self, key: &str) -> anyhow::Result<&str> {
53 self.get_param(key).ok_or_else(|| {
54 anyhow::anyhow!("Required parameter '{}' not found in mailet config", key)
55 })
56 }
57}
58
59#[async_trait]
61pub trait Mailet: Send + Sync {
62 async fn init(&mut self, config: MailetConfig) -> anyhow::Result<()>;
64
65 async fn service(&self, mail: &mut Mail) -> anyhow::Result<MailetAction>;
67
68 async fn destroy(&mut self) -> anyhow::Result<()> {
70 Ok(())
71 }
72
73 fn name(&self) -> &str;
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_mailet_config() {
83 let config = MailetConfig::new("TestMailet")
84 .with_param("key1", "value1")
85 .with_param("key2", "value2");
86
87 assert_eq!(config.name, "TestMailet");
88 assert_eq!(config.get_param("key1"), Some("value1"));
89 assert_eq!(config.get_param("key2"), Some("value2"));
90 assert_eq!(config.get_param("nonexistent"), None);
91 }
92
93 #[test]
94 fn test_mailet_action_equality() {
95 assert_eq!(MailetAction::Continue, MailetAction::Continue);
96 assert_eq!(MailetAction::Drop, MailetAction::Drop);
97 assert_ne!(MailetAction::Continue, MailetAction::Drop);
98 }
99}