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}