Skip to main content

ferro_notifications/
error.rs

1//! Error types for the notification system.
2
3use thiserror::Error;
4
5/// Errors that can occur during notification dispatch.
6#[derive(Error, Debug)]
7pub enum Error {
8    /// Failed to send email notification.
9    #[error("mail error: {0}")]
10    Mail(String),
11
12    /// Failed to send Slack notification.
13    #[error("slack error: {0}")]
14    Slack(String),
15
16    /// Failed to store database notification.
17    #[error("database error: {0}")]
18    Database(String),
19
20    /// Failed to send WhatsApp notification (per CONTEXT.md D-05).
21    #[error("whatsapp error: {0}")]
22    WhatsApp(#[from] ferro_whatsapp::Error),
23
24    /// Failed to publish an in-app broadcast (per RESEARCH.md §InApp Adapter — ferro_broadcast::Error mapping).
25    #[error("broadcast error: {0}")]
26    Broadcast(String),
27
28    /// Mail attachment exceeded the 25 MB framework cap (per CONTEXT.md D-11).
29    #[error("attachment '{filename}' too large: {size} bytes (limit {limit} bytes)")]
30    AttachmentTooLarge {
31        /// Original filename of the rejected attachment.
32        filename: String,
33        /// Size of the rejected attachment in bytes.
34        size: usize,
35        /// Configured limit in bytes (currently 25 * 1024 * 1024).
36        limit: usize,
37    },
38
39    /// Channel not configured or available.
40    #[error("channel not available: {0}")]
41    ChannelNotAvailable(String),
42
43    /// Serialization error.
44    #[error("serialization error: {0}")]
45    Serialization(#[from] serde_json::Error),
46
47    /// Generic notification error.
48    #[error("{0}")]
49    Other(String),
50}
51
52impl Error {
53    /// Create a mail error.
54    pub fn mail(msg: impl Into<String>) -> Self {
55        Self::Mail(msg.into())
56    }
57
58    /// Create a slack error.
59    pub fn slack(msg: impl Into<String>) -> Self {
60        Self::Slack(msg.into())
61    }
62
63    /// Create a database error.
64    pub fn database(msg: impl Into<String>) -> Self {
65        Self::Database(msg.into())
66    }
67
68    /// Create a broadcast error.
69    pub fn broadcast(msg: impl Into<String>) -> Self {
70        Self::Broadcast(msg.into())
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_error_mail_helper() {
80        let e = Error::mail("smtp connect failed");
81        assert_eq!(e.to_string(), "mail error: smtp connect failed");
82    }
83
84    #[test]
85    fn test_error_attachment_too_large_displays_correctly() {
86        let e = Error::AttachmentTooLarge {
87            filename: "report.pdf".into(),
88            size: 30 * 1024 * 1024,
89            limit: 25 * 1024 * 1024,
90        };
91        assert_eq!(
92            e.to_string(),
93            "attachment 'report.pdf' too large: 31457280 bytes (limit 26214400 bytes)"
94        );
95    }
96
97    #[test]
98    fn test_error_whatsapp_from_impl() {
99        let wa_err = ferro_whatsapp::Error::RateLimit;
100        let n_err: Error = wa_err.into();
101        assert!(matches!(
102            n_err,
103            Error::WhatsApp(ferro_whatsapp::Error::RateLimit)
104        ));
105        assert!(n_err.to_string().contains("whatsapp error"));
106        assert!(n_err.to_string().contains("rate limit exceeded"));
107    }
108
109    #[test]
110    fn test_error_broadcast_helper() {
111        let e = Error::broadcast("channel disconnected");
112        assert_eq!(e.to_string(), "broadcast error: channel disconnected");
113    }
114}