lmrc_cloudflare/
error.rs

1//! Error types for the Cloudflare client.
2
3use std::fmt;
4
5/// Result type alias for Cloudflare operations.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// The main error type for Cloudflare client operations.
9#[derive(Debug)]
10pub enum Error {
11    /// HTTP request failed
12    Http(reqwest::Error),
13
14    /// Cloudflare API returned an error response
15    Api(ApiError),
16
17    /// Failed to serialize/deserialize JSON
18    Json(serde_json::Error),
19
20    /// Resource not found (zone, record, etc.)
21    NotFound(String),
22
23    /// Invalid input or configuration
24    InvalidInput(String),
25
26    /// Authentication failed
27    Unauthorized(String),
28
29    /// Rate limited by Cloudflare API
30    RateLimited { retry_after: Option<u64> },
31}
32
33/// Detailed API error from Cloudflare
34#[derive(Debug, Clone)]
35pub struct ApiError {
36    /// HTTP status code
37    pub status: u16,
38
39    /// Error code from Cloudflare
40    pub code: Option<i32>,
41
42    /// Error message
43    pub message: String,
44
45    /// Full response body for debugging
46    pub body: String,
47}
48
49impl fmt::Display for Error {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Error::Http(e) => write!(f, "HTTP request failed: {}", e),
53            Error::Api(e) => write!(f, "Cloudflare API error: {}", e.message),
54            Error::Json(e) => write!(f, "JSON serialization error: {}", e),
55            Error::NotFound(msg) => write!(f, "Resource not found: {}", msg),
56            Error::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
57            Error::Unauthorized(msg) => write!(f, "Authentication failed: {}", msg),
58            Error::RateLimited { retry_after } => {
59                if let Some(seconds) = retry_after {
60                    write!(f, "Rate limited, retry after {} seconds", seconds)
61                } else {
62                    write!(f, "Rate limited by Cloudflare API")
63                }
64            }
65        }
66    }
67}
68
69impl std::error::Error for Error {
70    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
71        match self {
72            Error::Http(e) => Some(e),
73            Error::Json(e) => Some(e),
74            _ => None,
75        }
76    }
77}
78
79impl From<reqwest::Error> for Error {
80    fn from(err: reqwest::Error) -> Self {
81        Error::Http(err)
82    }
83}
84
85impl From<serde_json::Error> for Error {
86    fn from(err: serde_json::Error) -> Self {
87        Error::Json(err)
88    }
89}
90
91impl ApiError {
92    /// Create a new API error
93    pub fn new(status: u16, message: String, body: String) -> Self {
94        Self {
95            status,
96            code: None,
97            message,
98            body,
99        }
100    }
101
102    /// Create from Cloudflare API response
103    pub(crate) fn from_response(status: u16, body: &str) -> Self {
104        // Try to parse error details from response
105        if let Ok(json) = serde_json::from_str::<serde_json::Value>(body)
106            && let Some(errors) = json["errors"].as_array()
107            && let Some(first_error) = errors.first()
108        {
109            return Self {
110                status,
111                code: first_error["code"].as_i64().map(|c| c as i32),
112                message: first_error["message"]
113                    .as_str()
114                    .unwrap_or("Unknown error")
115                    .to_string(),
116                body: body.to_string(),
117            };
118        }
119
120        Self {
121            status,
122            code: None,
123            message: format!("HTTP {}", status),
124            body: body.to_string(),
125        }
126    }
127}