datadog_api/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum Error {
5    #[error("HTTP request failed: {0}")]
6    HttpError(#[from] reqwest::Error),
7
8    #[error("Middleware error: {0}")]
9    MiddlewareError(#[from] reqwest_middleware::Error),
10
11    #[error("JSON serialization/deserialization error: {0}")]
12    JsonError(#[from] serde_json::Error),
13
14    #[error("API error: {status} - {message}")]
15    ApiError { status: u16, message: String },
16
17    #[error("Configuration error: {0}")]
18    ConfigError(String),
19
20    #[error("I/O error: {0}")]
21    IoError(#[from] std::io::Error),
22
23    #[error("Invalid response format: {0}")]
24    InvalidResponse(String),
25}
26
27impl Error {
28    /// Returns true if this is a client error (4xx status code)
29    #[must_use]
30    pub fn is_client_error(&self) -> bool {
31        matches!(self, Error::ApiError { status, .. } if (400..500).contains(status))
32    }
33
34    /// Returns true if this is a server error (5xx status code)
35    #[must_use]
36    pub fn is_server_error(&self) -> bool {
37        matches!(self, Error::ApiError { status, .. } if (500..600).contains(status))
38    }
39
40    /// Returns true if this is a not found error (404)
41    #[must_use]
42    pub fn is_not_found(&self) -> bool {
43        matches!(self, Error::ApiError { status: 404, .. })
44    }
45
46    /// Returns true if this is an authentication error (401)
47    #[must_use]
48    pub fn is_unauthorized(&self) -> bool {
49        matches!(self, Error::ApiError { status: 401, .. })
50    }
51
52    /// Returns true if this is a forbidden error (403)
53    #[must_use]
54    pub fn is_forbidden(&self) -> bool {
55        matches!(self, Error::ApiError { status: 403, .. })
56    }
57
58    /// Returns true if this is a rate limit error (429)
59    #[must_use]
60    pub fn is_rate_limited(&self) -> bool {
61        matches!(self, Error::ApiError { status: 429, .. })
62    }
63
64    /// Returns the HTTP status code if this is an API error
65    #[must_use]
66    pub fn status_code(&self) -> Option<u16> {
67        match self {
68            Error::ApiError { status, .. } => Some(*status),
69            _ => None,
70        }
71    }
72
73    /// Returns true if this error is likely transient and retrying might succeed
74    #[must_use]
75    pub fn is_retryable(&self) -> bool {
76        match self {
77            Error::ApiError { status, .. } => {
78                // 429 (rate limit), 500, 502, 503, 504 are retryable
79                *status == 429 || (500..=504).contains(status)
80            }
81            Error::HttpError(e) => e.is_connect() || e.is_timeout(),
82            Error::MiddlewareError(_) => true,
83            _ => false,
84        }
85    }
86}
87
88pub type Result<T> = std::result::Result<T, Error>;
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_config_error_display() {
96        let error = Error::ConfigError("Invalid configuration".to_string());
97        let display = format!("{}", error);
98        assert!(display.contains("Configuration error"));
99        assert!(display.contains("Invalid configuration"));
100    }
101
102    #[test]
103    fn test_api_error_display() {
104        let error = Error::ApiError {
105            status: 404,
106            message: "Not found".to_string(),
107        };
108        let display = format!("{}", error);
109        assert!(display.contains("API error"));
110        assert!(display.contains("404"));
111        assert!(display.contains("Not found"));
112    }
113
114    #[test]
115    fn test_json_error() {
116        let json_error = serde_json::from_str::<serde_json::Value>("invalid json");
117        let error = Error::JsonError(json_error.unwrap_err());
118        let display = format!("{}", error);
119        assert!(display.contains("JSON serialization"));
120    }
121
122    #[test]
123    fn test_invalid_response_error() {
124        let error = Error::InvalidResponse("Bad format".to_string());
125        let display = format!("{}", error);
126        assert!(display.contains("Invalid response format"));
127        assert!(display.contains("Bad format"));
128    }
129
130    #[test]
131    fn test_error_debug() {
132        let error = Error::ConfigError("test".to_string());
133        let debug = format!("{:?}", error);
134        assert!(debug.contains("ConfigError"));
135    }
136
137    #[test]
138    fn test_error_is_send_sync() {
139        fn assert_send_sync<T: Send + Sync>() {}
140        assert_send_sync::<Error>();
141    }
142
143    #[test]
144    fn test_api_error_with_different_status_codes() {
145        let codes = vec![400, 401, 403, 404, 500, 502, 503];
146        for code in codes {
147            let error = Error::ApiError {
148                status: code,
149                message: format!("Error {}", code),
150            };
151            let display = format!("{}", error);
152            assert!(display.contains(&code.to_string()));
153        }
154    }
155
156    #[test]
157    fn test_is_client_error() {
158        let error_400 = Error::ApiError {
159            status: 400,
160            message: "Bad Request".into(),
161        };
162        let error_404 = Error::ApiError {
163            status: 404,
164            message: "Not Found".into(),
165        };
166        let error_500 = Error::ApiError {
167            status: 500,
168            message: "Server Error".into(),
169        };
170
171        assert!(error_400.is_client_error());
172        assert!(error_404.is_client_error());
173        assert!(!error_500.is_client_error());
174    }
175
176    #[test]
177    fn test_is_server_error() {
178        let error_400 = Error::ApiError {
179            status: 400,
180            message: "Bad Request".into(),
181        };
182        let error_500 = Error::ApiError {
183            status: 500,
184            message: "Server Error".into(),
185        };
186        let error_503 = Error::ApiError {
187            status: 503,
188            message: "Service Unavailable".into(),
189        };
190
191        assert!(!error_400.is_server_error());
192        assert!(error_500.is_server_error());
193        assert!(error_503.is_server_error());
194    }
195
196    #[test]
197    fn test_specific_error_checks() {
198        let not_found = Error::ApiError {
199            status: 404,
200            message: "Not Found".into(),
201        };
202        let unauthorized = Error::ApiError {
203            status: 401,
204            message: "Unauthorized".into(),
205        };
206        let forbidden = Error::ApiError {
207            status: 403,
208            message: "Forbidden".into(),
209        };
210        let rate_limited = Error::ApiError {
211            status: 429,
212            message: "Too Many Requests".into(),
213        };
214
215        assert!(not_found.is_not_found());
216        assert!(unauthorized.is_unauthorized());
217        assert!(forbidden.is_forbidden());
218        assert!(rate_limited.is_rate_limited());
219
220        assert!(!not_found.is_unauthorized());
221        assert!(!unauthorized.is_not_found());
222    }
223
224    #[test]
225    fn test_status_code() {
226        let api_error = Error::ApiError {
227            status: 404,
228            message: "Not Found".into(),
229        };
230        let config_error = Error::ConfigError("test".into());
231
232        assert_eq!(api_error.status_code(), Some(404));
233        assert_eq!(config_error.status_code(), None);
234    }
235
236    #[test]
237    fn test_is_retryable() {
238        let rate_limited = Error::ApiError {
239            status: 429,
240            message: "Rate Limited".into(),
241        };
242        let server_error = Error::ApiError {
243            status: 500,
244            message: "Server Error".into(),
245        };
246        let bad_gateway = Error::ApiError {
247            status: 502,
248            message: "Bad Gateway".into(),
249        };
250        let not_found = Error::ApiError {
251            status: 404,
252            message: "Not Found".into(),
253        };
254        let bad_request = Error::ApiError {
255            status: 400,
256            message: "Bad Request".into(),
257        };
258
259        assert!(rate_limited.is_retryable());
260        assert!(server_error.is_retryable());
261        assert!(bad_gateway.is_retryable());
262        assert!(!not_found.is_retryable());
263        assert!(!bad_request.is_retryable());
264    }
265}