Skip to main content

ferro_notifications/
notification.rs

1//! Core notification trait.
2
3use crate::channel::Channel;
4use crate::channels::{DatabaseMessage, MailMessage, SlackMessage};
5
6/// A notification that can be sent through multiple channels.
7///
8/// Notifications encapsulate a message that should be delivered to users
9/// through one or more channels (mail, database, slack, etc.).
10///
11/// # Example
12///
13/// ```rust
14/// use ferro_notifications::{Notification, Channel, MailMessage, DatabaseMessage};
15///
16/// struct OrderShipped {
17///     order_id: i64,
18///     tracking_number: String,
19/// }
20///
21/// impl Notification for OrderShipped {
22///     fn via(&self) -> Vec<Channel> {
23///         vec![Channel::Mail, Channel::Database]
24///     }
25///
26///     fn to_mail(&self) -> Option<MailMessage> {
27///         Some(MailMessage::new()
28///             .subject("Your order has shipped!")
29///             .body(format!("Tracking: {}", self.tracking_number)))
30///     }
31///
32///     fn to_database(&self) -> Option<DatabaseMessage> {
33///         Some(DatabaseMessage::new("order_shipped")
34///             .data("order_id", self.order_id)
35///             .data("tracking", &self.tracking_number))
36///     }
37/// }
38/// ```
39pub trait Notification: Send + Sync {
40    /// The channels this notification should be sent through.
41    fn via(&self) -> Vec<Channel>;
42
43    /// Convert the notification to a mail message.
44    fn to_mail(&self) -> Option<MailMessage> {
45        None
46    }
47
48    /// Convert the notification to a database message.
49    fn to_database(&self) -> Option<DatabaseMessage> {
50        None
51    }
52
53    /// Convert the notification to a Slack message.
54    fn to_slack(&self) -> Option<SlackMessage> {
55        None
56    }
57
58    /// Get the notification type name for logging.
59    fn notification_type(&self) -> &'static str {
60        std::any::type_name::<Self>()
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    struct TestNotification;
69
70    impl Notification for TestNotification {
71        fn via(&self) -> Vec<Channel> {
72            vec![Channel::Mail, Channel::Database]
73        }
74
75        fn to_mail(&self) -> Option<MailMessage> {
76            Some(MailMessage::new().subject("Test").body("Test body"))
77        }
78    }
79
80    #[test]
81    fn test_notification_via() {
82        let notification = TestNotification;
83        let channels = notification.via();
84        assert_eq!(channels.len(), 2);
85        assert!(channels.contains(&Channel::Mail));
86        assert!(channels.contains(&Channel::Database));
87    }
88
89    #[test]
90    fn test_notification_to_mail() {
91        let notification = TestNotification;
92        let mail = notification.to_mail();
93        assert!(mail.is_some());
94        let mail = mail.unwrap();
95        assert_eq!(mail.subject, "Test");
96    }
97
98    #[test]
99    fn test_notification_to_database_default() {
100        let notification = TestNotification;
101        assert!(notification.to_database().is_none());
102    }
103}