ddns_a/webhook/error.rs
1//! Error types for HTTP and webhook operations.
2
3use thiserror::Error;
4
5/// Error type for HTTP operations.
6///
7/// Describes what went wrong without dictating recovery strategy.
8/// These errors are typically retryable at the caller's discretion.
9#[derive(Debug, Error)]
10pub enum HttpError {
11 /// Network connection failed.
12 ///
13 /// This includes DNS resolution failures, connection refused,
14 /// and other network-level errors.
15 #[error("Connection error: {0}")]
16 Connection(#[source] Box<dyn std::error::Error + Send + Sync>),
17
18 /// Request timed out.
19 ///
20 /// The server did not respond within the configured timeout period.
21 #[error("Request timed out")]
22 Timeout,
23
24 /// The provided URL is invalid.
25 ///
26 /// This typically indicates a configuration error rather than
27 /// a transient failure.
28 #[error("Invalid URL: {0}")]
29 InvalidUrl(String),
30}
31
32/// Error type for operations that may be retried.
33///
34/// Wraps both network-level failures and HTTP status errors that
35/// indicate transient conditions (server overload, rate limiting, etc.).
36///
37/// **Note**: Despite the name, not all variants are inherently retryable.
38/// Use [`IsRetryable::is_retryable()`] to determine if a specific error
39/// should be retried.
40#[derive(Debug, Error)]
41pub enum RetryableError {
42 /// Network-level error during the HTTP request.
43 #[error(transparent)]
44 Http(#[from] HttpError),
45
46 /// Server returned a non-success (non-2xx) status code.
47 ///
48 /// **Important**: This variant is not always retryable. Whether to retry
49 /// depends on the specific status code:
50 /// - 5xx (server errors): typically retryable
51 /// - 429 (Too Many Requests): retryable
52 /// - 408 (Request Timeout): retryable
53 /// - Other 4xx (client errors): not retryable (configuration/request issue)
54 ///
55 /// Use [`IsRetryable::is_retryable()`] to check.
56 #[error("HTTP {}: {}", status.as_u16(), body.as_deref().unwrap_or("<no body>"))]
57 NonSuccessStatus {
58 /// HTTP status code
59 status: http::StatusCode,
60 /// Optional response body for diagnostics
61 body: Option<String>,
62 },
63
64 /// Template rendering failed.
65 ///
66 /// The body template could not be rendered with the provided data.
67 #[error("Template error: {0}")]
68 Template(String),
69}
70
71/// High-level error type for webhook operations.
72///
73/// Distinguishes between errors that can be retried and terminal failures
74/// where all retry attempts have been exhausted.
75#[derive(Debug, Error)]
76pub enum WebhookError {
77 /// A single attempt failed but may be retried.
78 #[error(transparent)]
79 Retryable(#[from] RetryableError),
80
81 /// All retry attempts have been exhausted.
82 #[error("Failed after {attempts} attempts")]
83 MaxRetriesExceeded {
84 /// Number of attempts made before giving up
85 attempts: u32,
86 /// The last error encountered
87 #[source]
88 last_error: RetryableError,
89 },
90}