Skip to main content

ferro_notifications/
notification.rs

1//! Core notification trait.
2
3use crate::channel::Channel;
4use crate::channels::{
5    DatabaseMessage, InAppMessage, MailMessage, PushMessage, SlackMessage, SmsMessage,
6    WhatsAppMessage,
7};
8
9/// A notification that can be sent through multiple channels.
10///
11/// Notifications encapsulate a message that should be delivered to users
12/// through one or more channels (mail, database, slack, etc.).
13///
14/// # Example
15///
16/// ```rust
17/// use ferro_notifications::{Notification, Channel, MailMessage, DatabaseMessage};
18///
19/// struct OrderShipped {
20///     order_id: i64,
21///     tracking_number: String,
22/// }
23///
24/// impl Notification for OrderShipped {
25///     fn via(&self) -> Vec<Channel> {
26///         vec![Channel::Mail, Channel::Database]
27///     }
28///
29///     fn to_mail(&self) -> Option<MailMessage> {
30///         Some(MailMessage::new()
31///             .subject("Your order has shipped!")
32///             .body(format!("Tracking: {}", self.tracking_number)))
33///     }
34///
35///     fn to_database(&self) -> Option<DatabaseMessage> {
36///         Some(DatabaseMessage::new("order_shipped")
37///             .data("order_id", self.order_id)
38///             .data("tracking", &self.tracking_number))
39///     }
40/// }
41/// ```
42pub trait Notification: Send + Sync {
43    /// The channels this notification should be sent through.
44    fn via(&self) -> Vec<Channel>;
45
46    /// Convert the notification to a mail message.
47    fn to_mail(&self) -> Option<MailMessage> {
48        None
49    }
50
51    /// Convert the notification to a database message.
52    fn to_database(&self) -> Option<DatabaseMessage> {
53        None
54    }
55
56    /// Convert the notification to a Slack message.
57    fn to_slack(&self) -> Option<SlackMessage> {
58        None
59    }
60
61    /// Convert the notification to a WhatsApp message (per CONTEXT.md D-02).
62    fn to_whatsapp(&self) -> Option<WhatsAppMessage> {
63        None
64    }
65
66    /// Convert the notification to an in-app SSE message (per CONTEXT.md D-02).
67    fn to_in_app(&self) -> Option<InAppMessage> {
68        None
69    }
70
71    /// Convert the notification to an SMS message (placeholder per ARCH-FINDING-03; no adapter in this phase).
72    fn to_sms(&self) -> Option<SmsMessage> {
73        None
74    }
75
76    /// Convert the notification to a push notification (placeholder per ARCH-FINDING-03; no adapter in this phase).
77    fn to_push(&self) -> Option<PushMessage> {
78        None
79    }
80
81    /// Get the notification type name for logging.
82    fn notification_type(&self) -> &'static str {
83        std::any::type_name::<Self>()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    struct TestNotification;
92
93    impl Notification for TestNotification {
94        fn via(&self) -> Vec<Channel> {
95            vec![Channel::Mail, Channel::Database]
96        }
97
98        fn to_mail(&self) -> Option<MailMessage> {
99            Some(MailMessage::new().subject("Test").body("Test body"))
100        }
101    }
102
103    #[test]
104    fn test_notification_via() {
105        let notification = TestNotification;
106        let channels = notification.via();
107        assert_eq!(channels.len(), 2);
108        assert!(channels.contains(&Channel::Mail));
109        assert!(channels.contains(&Channel::Database));
110    }
111
112    #[test]
113    fn test_notification_to_mail() {
114        let notification = TestNotification;
115        let mail = notification.to_mail();
116        assert!(mail.is_some());
117        let mail = mail.unwrap();
118        assert_eq!(mail.subject, "Test");
119    }
120
121    #[test]
122    fn test_notification_to_database_default() {
123        let notification = TestNotification;
124        assert!(notification.to_database().is_none());
125    }
126
127    #[test]
128    fn test_notification_to_whatsapp_default() {
129        let notification = TestNotification;
130        assert!(notification.to_whatsapp().is_none());
131    }
132
133    #[test]
134    fn test_notification_to_in_app_default() {
135        let notification = TestNotification;
136        assert!(notification.to_in_app().is_none());
137    }
138
139    #[test]
140    fn test_notification_to_sms_default() {
141        let notification = TestNotification;
142        assert!(notification.to_sms().is_none());
143    }
144
145    #[test]
146    fn test_notification_to_push_default() {
147        let notification = TestNotification;
148        assert!(notification.to_push().is_none());
149    }
150}