use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct TrackingOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub opens: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_replies: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
impl TrackingOptions {
pub fn builder() -> TrackingOptionsBuilder {
TrackingOptionsBuilder::default()
}
pub fn all_enabled() -> Self {
Self {
opens: Some(true),
links: Some(true),
thread_replies: Some(true),
label: None,
}
}
pub fn all_disabled() -> Self {
Self {
opens: Some(false),
links: Some(false),
thread_replies: Some(false),
label: None,
}
}
}
#[derive(Debug, Default)]
pub struct TrackingOptionsBuilder {
opens: Option<bool>,
links: Option<bool>,
thread_replies: Option<bool>,
label: Option<String>,
}
impl TrackingOptionsBuilder {
pub fn opens(mut self, enabled: bool) -> Self {
self.opens = Some(enabled);
self
}
pub fn links(mut self, enabled: bool) -> Self {
self.links = Some(enabled);
self
}
pub fn thread_replies(mut self, enabled: bool) -> Self {
self.thread_replies = Some(enabled);
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn build(self) -> TrackingOptions {
TrackingOptions {
opens: self.opens,
links: self.links,
thread_replies: self.thread_replies,
label: self.label,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MessageOpened {
pub message_id: String,
pub timestamp: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub ip_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LinkClicked {
pub message_id: String,
pub link: String,
pub timestamp: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub ip_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ThreadReplied {
pub message_id: String,
pub thread_id: String,
pub reply_message_id: String,
pub timestamp: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracking_options_builder() {
let options = TrackingOptions::builder()
.opens(true)
.links(true)
.thread_replies(false)
.label("campaign-2024")
.build();
assert_eq!(options.opens, Some(true));
assert_eq!(options.links, Some(true));
assert_eq!(options.thread_replies, Some(false));
assert_eq!(options.label, Some("campaign-2024".to_string()));
}
#[test]
fn test_tracking_options_all_enabled() {
let options = TrackingOptions::all_enabled();
assert_eq!(options.opens, Some(true));
assert_eq!(options.links, Some(true));
assert_eq!(options.thread_replies, Some(true));
assert_eq!(options.label, None);
}
#[test]
fn test_tracking_options_all_disabled() {
let options = TrackingOptions::all_disabled();
assert_eq!(options.opens, Some(false));
assert_eq!(options.links, Some(false));
assert_eq!(options.thread_replies, Some(false));
}
#[test]
fn test_tracking_options_serialization() {
let options = TrackingOptions::builder().opens(true).links(false).build();
let json = serde_json::to_string(&options).unwrap();
let deserialized: TrackingOptions = serde_json::from_str(&json).unwrap();
assert_eq!(options, deserialized);
}
#[test]
fn test_message_opened_serialization() {
let opened = MessageOpened {
message_id: "msg_123".to_string(),
timestamp: 1234567890,
ip_address: Some("192.168.1.1".to_string()),
user_agent: Some("Mozilla/5.0".to_string()),
count: Some(1),
};
let json = serde_json::to_string(&opened).unwrap();
let deserialized: MessageOpened = serde_json::from_str(&json).unwrap();
assert_eq!(opened, deserialized);
}
#[test]
fn test_link_clicked_serialization() {
let clicked = LinkClicked {
message_id: "msg_123".to_string(),
link: "https://example.com".to_string(),
timestamp: 1234567890,
ip_address: Some("192.168.1.1".to_string()),
user_agent: Some("Mozilla/5.0".to_string()),
count: Some(2),
};
let json = serde_json::to_string(&clicked).unwrap();
let deserialized: LinkClicked = serde_json::from_str(&json).unwrap();
assert_eq!(clicked, deserialized);
}
#[test]
fn test_thread_replied_serialization() {
let replied = ThreadReplied {
message_id: "msg_123".to_string(),
thread_id: "thread_456".to_string(),
reply_message_id: "msg_789".to_string(),
timestamp: 1234567890,
from: Some("user@example.com".to_string()),
};
let json = serde_json::to_string(&replied).unwrap();
let deserialized: ThreadReplied = serde_json::from_str(&json).unwrap();
assert_eq!(replied, deserialized);
}
}