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}