use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailMessage {
pub from: String,
pub to: Vec<String>,
#[serde(default)]
pub cc: Vec<String>,
#[serde(default)]
pub bcc: Vec<String>,
pub subject: String,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub body_html: Option<String>,
#[serde(default)]
pub attachments: Vec<EmailAttachment>,
#[serde(default)]
pub date: Option<String>,
#[serde(default)]
pub uid: Option<u32>,
#[serde(default)]
pub message_id: Option<String>,
#[serde(default)]
pub flags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailAttachment {
pub filename: String,
pub content_type: String,
#[serde(with = "base64_bytes")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailFolder {
pub name: String,
pub total_messages: u32,
pub unread: u32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EmailSearchQuery {
#[serde(default)]
pub from: Option<String>,
#[serde(default)]
pub to: Option<String>,
#[serde(default)]
pub subject: Option<String>,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub since: Option<String>,
#[serde(default)]
pub before: Option<String>,
#[serde(default)]
pub flags: Vec<String>,
}
mod base64_bytes {
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&STANDARD.encode(data))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = String::deserialize(deserializer)?;
STANDARD.decode(s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_email_message_serde_roundtrip() {
let msg = EmailMessage {
from: "alice@example.com".to_string(),
to: vec!["bob@example.com".to_string()],
cc: vec![],
bcc: vec![],
subject: "Hello".to_string(),
body: Some("Hi Bob!".to_string()),
body_html: None,
attachments: vec![],
date: Some("2025-01-01T00:00:00Z".to_string()),
uid: Some(42),
message_id: Some("<msg@example.com>".to_string()),
flags: vec!["\\Seen".to_string()],
};
let json = serde_json::to_string(&msg).unwrap();
let deserialized: EmailMessage = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.from, "alice@example.com");
assert_eq!(deserialized.subject, "Hello");
assert_eq!(deserialized.uid, Some(42));
}
#[test]
fn test_email_attachment_serde_roundtrip() {
let att = EmailAttachment {
filename: "test.txt".to_string(),
content_type: "text/plain".to_string(),
data: b"hello world".to_vec(),
};
let json = serde_json::to_string(&att).unwrap();
let deserialized: EmailAttachment = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.filename, "test.txt");
assert_eq!(deserialized.data, b"hello world");
}
#[test]
fn test_email_folder_serde_roundtrip() {
let folder = EmailFolder {
name: "INBOX".to_string(),
total_messages: 100,
unread: 5,
};
let json = serde_json::to_string(&folder).unwrap();
let deserialized: EmailFolder = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "INBOX");
assert_eq!(deserialized.total_messages, 100);
}
#[test]
fn test_email_search_query_serde_roundtrip() {
let query = EmailSearchQuery {
from: Some("alice@example.com".to_string()),
subject: Some("Hello".to_string()),
..Default::default()
};
let json = serde_json::to_string(&query).unwrap();
let deserialized: EmailSearchQuery = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.from, Some("alice@example.com".to_string()));
}
}