Skip to main content

chorus_core/
error.rs

1use thiserror::Error;
2
3/// Errors that can occur during Chorus operations.
4#[derive(Debug, Error)]
5pub enum ChorusError {
6    /// A specific provider returned an error.
7    #[error("provider error ({provider}): {message}")]
8    Provider { provider: String, message: String },
9
10    /// All configured providers failed to deliver the message.
11    #[error("all providers failed")]
12    AllProvidersFailed,
13
14    /// Input validation failed (e.g., missing required field).
15    #[error("validation error: {0}")]
16    Validation(String),
17
18    /// The requested template slug was not found.
19    #[error("template not found: {0}")]
20    TemplateNotFound(String),
21
22    /// Account quota has been exceeded.
23    #[error("quota exceeded: {0}")]
24    QuotaExceeded(String),
25
26    /// The provided API key is invalid.
27    #[error("invalid api key")]
28    InvalidApiKey,
29
30    /// Request was rate limited. Retry after the specified duration.
31    #[error("rate limited")]
32    RateLimited { retry_after_secs: u64 },
33
34    /// An unexpected internal error occurred.
35    #[error("internal error: {0}")]
36    Internal(#[from] anyhow::Error),
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn provider_error_display() {
45        let err = ChorusError::Provider {
46            provider: "twilio".into(),
47            message: "timeout".into(),
48        };
49        assert_eq!(err.to_string(), "provider error (twilio): timeout");
50    }
51
52    #[test]
53    fn all_providers_failed_display() {
54        let err = ChorusError::AllProvidersFailed;
55        assert_eq!(err.to_string(), "all providers failed");
56    }
57
58    #[test]
59    fn validation_error_display() {
60        let err = ChorusError::Validation("missing phone number".into());
61        assert_eq!(err.to_string(), "validation error: missing phone number");
62    }
63
64    #[test]
65    fn template_not_found_display() {
66        let err = ChorusError::TemplateNotFound("welcome".into());
67        assert_eq!(err.to_string(), "template not found: welcome");
68    }
69
70    #[test]
71    fn quota_exceeded_display() {
72        let err = ChorusError::QuotaExceeded("monthly limit reached".into());
73        assert_eq!(err.to_string(), "quota exceeded: monthly limit reached");
74    }
75
76    #[test]
77    fn invalid_api_key_display() {
78        let err = ChorusError::InvalidApiKey;
79        assert_eq!(err.to_string(), "invalid api key");
80    }
81
82    #[test]
83    fn rate_limited_display() {
84        let err = ChorusError::RateLimited {
85            retry_after_secs: 30,
86        };
87        assert_eq!(err.to_string(), "rate limited");
88    }
89
90    #[test]
91    fn internal_error_from_anyhow() {
92        let anyhow_err = anyhow::anyhow!("something broke");
93        let err = ChorusError::from(anyhow_err);
94        assert!(err.to_string().contains("something broke"));
95    }
96}