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