Skip to main content

mailsis_utils/handlers/
file_storage.rs

1//! Filesystem-backed message handler.
2//!
3//! Provides [`FileStorageHandler`], a [`MessageHandler`](crate::MessageHandler)
4//! implementation that delegates to a [`FileStorageEngine`](crate::FileStorageEngine)
5//! for storing emails as `.eml` files on disk.
6
7use std::{path::PathBuf, sync::Arc};
8
9use tracing::{debug, error, info};
10
11use crate::{
12    handler::{HandlerError, HandlerFuture, MessageHandler},
13    EmailMessage, FileStorageEngine, StorageEngine,
14};
15
16/// Message handler that stores emails using a [`FileStorageEngine`].
17pub struct FileStorageHandler {
18    engine: Arc<FileStorageEngine>,
19}
20
21impl FileStorageHandler {
22    /// Creates a new [`FileStorageHandler`] with the given base path and metadata flag.
23    pub fn new(base_path: PathBuf, metadata: bool) -> Self {
24        info!(
25            path = %base_path.display(),
26            metadata = metadata,
27            "File storage handler initialized"
28        );
29        let engine = if metadata {
30            FileStorageEngine::new(base_path)
31        } else {
32            FileStorageEngine::without_metadata(base_path)
33        };
34        Self {
35            engine: Arc::new(engine),
36        }
37    }
38
39    /// Returns a reference to the underlying storage engine.
40    pub fn engine(&self) -> &Arc<FileStorageEngine> {
41        &self.engine
42    }
43}
44
45impl MessageHandler for FileStorageHandler {
46    fn handle<'a>(&'a self, message: &'a EmailMessage) -> HandlerFuture<'a> {
47        Box::pin(async move {
48            debug!(
49                message_id = %message.message_id,
50                to = %message.to,
51                "Storing email to filesystem"
52            );
53            self.engine.store(message).await.map_err(|e| {
54                error!(
55                    message_id = %message.message_id,
56                    error = %e,
57                    "Failed to store email to filesystem"
58                );
59                HandlerError::Storage(e.to_string())
60            })?;
61            info!(
62                message_id = %message.message_id,
63                from = %message.from,
64                to = %message.to,
65                "Stored email to filesystem"
66            );
67            Ok(())
68        })
69    }
70
71    fn name(&self) -> &str {
72        "file_storage"
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use tempfile::TempDir;
79
80    use super::*;
81
82    #[tokio::test]
83    async fn test_file_storage_handler() {
84        let temp_dir = TempDir::new().unwrap();
85        let handler = FileStorageHandler::new(temp_dir.path().to_path_buf(), false);
86
87        let message = EmailMessage::from_raw("sender@example.com", "rcpt@example.com", "Hello");
88        let result = handler.handle(&message).await;
89        assert!(result.is_ok());
90
91        // Verify the file was stored
92        let messages = handler.engine.list("rcpt@example.com").await.unwrap();
93        assert_eq!(messages.len(), 1);
94    }
95
96    #[test]
97    fn test_file_storage_handler_name() {
98        let temp_dir = TempDir::new().unwrap();
99        let handler = FileStorageHandler::new(temp_dir.path().to_path_buf(), false);
100        assert_eq!(handler.name(), "file_storage");
101    }
102
103    #[test]
104    fn test_file_storage_handler_engine_accessor() {
105        let temp_dir = TempDir::new().unwrap();
106        let handler = FileStorageHandler::new(temp_dir.path().to_path_buf(), false);
107        let _engine = handler.engine();
108    }
109
110    #[tokio::test]
111    async fn test_file_storage_handler_multiple_messages() {
112        let temp_dir = TempDir::new().unwrap();
113        let handler = FileStorageHandler::new(temp_dir.path().to_path_buf(), false);
114
115        let msg1 = EmailMessage::from_raw("sender@example.com", "rcpt@example.com", "Message 1");
116        let msg2 = EmailMessage::from_raw("sender@example.com", "rcpt@example.com", "Message 2");
117
118        handler.handle(&msg1).await.unwrap();
119        handler.handle(&msg2).await.unwrap();
120
121        let messages = handler.engine.list("rcpt@example.com").await.unwrap();
122        assert_eq!(messages.len(), 2);
123    }
124
125    #[tokio::test]
126    async fn test_file_storage_handler_with_mime_message() {
127        let temp_dir = TempDir::new().unwrap();
128        let handler = FileStorageHandler::new(temp_dir.path().to_path_buf(), false);
129
130        let message = EmailMessage::from_raw(
131            "sender@example.com",
132            "rcpt@example.com",
133            "Subject: Test\r\nFrom: sender@example.com\r\n\r\nBody content",
134        );
135
136        handler.handle(&message).await.unwrap();
137
138        let content = handler
139            .engine
140            .retrieve("rcpt@example.com", &message.message_id)
141            .await
142            .unwrap();
143        assert!(content.contains("Subject: Test"));
144        assert!(content.contains("Body content"));
145    }
146}