fraiseql_error/notification.rs
1use std::time::Duration;
2
3/// Errors that occur during notification delivery (email, SMS, push, etc.).
4#[derive(Debug, thiserror::Error)]
5pub enum NotificationError {
6 /// The notification provider is misconfigured (e.g. missing API key or
7 /// invalid sender address).
8 #[error("Configuration error: {message}")]
9 Configuration {
10 /// Description of the configuration problem.
11 message: String,
12 },
13
14 /// The notification provider returned an unexpected error response.
15 #[error("Provider error: {provider} - {message}")]
16 Provider {
17 /// Name of the notification provider (e.g. `"sendgrid"`, `"twilio"`).
18 provider: String,
19 /// Error message from the provider (kept server-side; not forwarded to clients).
20 message: String,
21 },
22
23 /// The notification provider is temporarily unreachable or returning
24 /// 5xx responses.
25 #[error("Provider unavailable: {provider}")]
26 ProviderUnavailable {
27 /// Name of the provider that is unavailable.
28 provider: String,
29 /// How long to wait before retrying, if the provider indicated a backoff.
30 retry_after: Option<Duration>,
31 },
32
33 /// The notification request contained invalid data (e.g. a malformed
34 /// recipient address or an empty message body).
35 #[error("Invalid input: {message}")]
36 InvalidInput {
37 /// Description of what was invalid.
38 message: String,
39 },
40
41 /// An error occurred while rendering the notification template.
42 #[error("Template error: {message}")]
43 Template {
44 /// Description of the template rendering failure.
45 message: String,
46 },
47
48 /// The notification provider has rate-limited the sending account.
49 #[error("Rate limited by provider: retry after {seconds} seconds")]
50 ProviderRateLimited {
51 /// Name of the provider that applied the rate limit.
52 provider: String,
53 /// Number of seconds to wait before retrying.
54 seconds: u64,
55 },
56
57 /// The circuit breaker for this provider is open because too many recent
58 /// requests have failed.
59 ///
60 /// Requests will not be forwarded to the provider until `retry_after` has
61 /// elapsed, giving the provider time to recover.
62 #[error("Circuit breaker open for provider: {provider}")]
63 CircuitOpen {
64 /// Name of the provider whose circuit is open.
65 provider: String,
66 /// How long to wait before the circuit transitions to half-open.
67 retry_after: Duration,
68 },
69
70 /// The notification delivery attempt did not complete within the allowed
71 /// time budget.
72 #[error("Timeout sending notification")]
73 Timeout,
74}
75
76impl NotificationError {
77 /// Returns a short, stable error code string suitable for API responses and
78 /// structured logging.
79 pub const fn error_code(&self) -> &'static str {
80 match self {
81 Self::Configuration { .. } => "notification_config_error",
82 Self::Provider { .. } => "notification_provider_error",
83 Self::ProviderUnavailable { .. } => "notification_provider_unavailable",
84 Self::InvalidInput { .. } => "notification_invalid_input",
85 Self::Template { .. } => "notification_template_error",
86 Self::ProviderRateLimited { .. } => "notification_rate_limited",
87 Self::CircuitOpen { .. } => "notification_circuit_open",
88 Self::Timeout => "notification_timeout",
89 }
90 }
91}