Skip to main content

rusmes_core/
mailet.rs

1//! Mailet trait and types
2
3use async_trait::async_trait;
4use rusmes_proto::{Mail, MailState};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::time::Duration;
8
9/// Actions a mailet can take after processing a mail
10#[derive(Debug, Clone, PartialEq)]
11pub enum MailetAction {
12    /// Continue to next mailet in the chain
13    Continue,
14    /// Change mail state and move to different processor
15    ChangeState(MailState),
16    /// Drop the mail (set state to Ghost)
17    Drop,
18    /// Defer processing (requeue with delay)
19    Defer(Duration),
20}
21
22/// Mailet configuration
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct MailetConfig {
25    /// Mailet name
26    pub name: String,
27    /// Configuration parameters
28    pub params: HashMap<String, String>,
29}
30
31impl MailetConfig {
32    /// Create a new mailet config
33    pub fn new(name: impl Into<String>) -> Self {
34        Self {
35            name: name.into(),
36            params: HashMap::new(),
37        }
38    }
39
40    /// Add a parameter
41    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    /// Get a parameter value
47    pub fn get_param(&self, key: &str) -> Option<&str> {
48        self.params.get(key).map(|s| s.as_str())
49    }
50
51    /// Get a required parameter
52    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/// Core mailet trait - message processing unit
60#[async_trait]
61pub trait Mailet: Send + Sync {
62    /// Initialize mailet with configuration
63    async fn init(&mut self, config: MailetConfig) -> anyhow::Result<()>;
64
65    /// Process a mail message
66    async fn service(&self, mail: &mut Mail) -> anyhow::Result<MailetAction>;
67
68    /// Cleanup on shutdown
69    async fn destroy(&mut self) -> anyhow::Result<()> {
70        Ok(())
71    }
72
73    /// Mailet name for logging/metrics
74    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}