ferro_notifications/
notifiable.rs1use crate::channel::Channel;
4use crate::channels::DatabaseMessage;
5use crate::dispatcher::NotificationDispatcher;
6use crate::notification::Notification;
7use crate::Error;
8use async_trait::async_trait;
9
10#[async_trait]
38pub trait Notifiable: Send + Sync {
39 fn route_notification_for(&self, channel: Channel) -> Option<String>;
45
46 fn notifiable_id(&self) -> String {
49 "unknown".to_string()
50 }
51
52 fn notifiable_type(&self) -> &'static str {
54 std::any::type_name::<Self>()
55 }
56
57 async fn notify<N: Notification + 'static>(&self, notification: N) -> Result<(), Error> {
62 NotificationDispatcher::send(self, notification).await
63 }
64}
65
66#[derive(Debug)]
68pub struct ChannelResult {
69 pub channel: Channel,
71 pub success: bool,
73 pub error: Option<String>,
75}
76
77impl ChannelResult {
78 pub fn success(channel: Channel) -> Self {
80 Self {
81 channel,
82 success: true,
83 error: None,
84 }
85 }
86
87 pub fn failure(channel: Channel, error: impl Into<String>) -> Self {
89 Self {
90 channel,
91 success: false,
92 error: Some(error.into()),
93 }
94 }
95}
96
97#[async_trait]
99pub trait DatabaseNotificationStore: Send + Sync {
100 async fn store(
102 &self,
103 notifiable_id: &str,
104 notifiable_type: &str,
105 notification_type: &str,
106 message: &DatabaseMessage,
107 ) -> Result<(), Error>;
108
109 async fn mark_as_read(&self, notification_id: &str) -> Result<(), Error>;
111
112 async fn unread(&self, notifiable_id: &str) -> Result<Vec<StoredNotification>, Error>;
114}
115
116#[derive(Debug, Clone)]
118pub struct StoredNotification {
119 pub id: String,
121 pub notifiable_id: String,
123 pub notifiable_type: String,
125 pub notification_type: String,
127 pub data: String,
129 pub read_at: Option<String>,
131 pub created_at: String,
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 struct TestUser {
140 id: i64,
141 email: String,
142 }
143
144 impl Notifiable for TestUser {
145 fn route_notification_for(&self, channel: Channel) -> Option<String> {
146 match channel {
147 Channel::Mail => Some(self.email.clone()),
148 Channel::Database => Some(self.id.to_string()),
149 _ => None,
150 }
151 }
152
153 fn notifiable_id(&self) -> String {
154 self.id.to_string()
155 }
156 }
157
158 #[test]
159 fn test_route_notification_for() {
160 let user = TestUser {
161 id: 42,
162 email: "test@example.com".to_string(),
163 };
164
165 assert_eq!(
166 user.route_notification_for(Channel::Mail),
167 Some("test@example.com".to_string())
168 );
169 assert_eq!(
170 user.route_notification_for(Channel::Database),
171 Some("42".to_string())
172 );
173 assert_eq!(user.route_notification_for(Channel::Slack), None);
174 }
175
176 #[test]
177 fn test_channel_result() {
178 let success = ChannelResult::success(Channel::Mail);
179 assert!(success.success);
180 assert!(success.error.is_none());
181
182 let failure = ChannelResult::failure(Channel::Slack, "Connection failed");
183 assert!(!failure.success);
184 assert_eq!(failure.error, Some("Connection failed".to_string()));
185 }
186}