Skip to main content

waka_api/
error.rs

1//! API error types for `waka-api`.
2
3/// All errors that can be returned by the `WakaTime` API client.
4#[derive(Debug, thiserror::Error)]
5#[non_exhaustive]
6pub enum ApiError {
7    /// The API key is invalid or was not provided.
8    /// Maps to HTTP 401.
9    #[error("Unauthorized: invalid or missing API key")]
10    Unauthorized,
11
12    /// The API rate limit has been exceeded.
13    /// Maps to HTTP 429.
14    ///
15    /// `retry_after` contains the number of seconds to wait before retrying,
16    /// if the server included a `Retry-After` header.
17    #[error("Rate limited — retry after {retry_after:?} seconds")]
18    RateLimit {
19        /// Seconds to wait before retrying, if known.
20        retry_after: Option<u64>,
21    },
22
23    /// The requested resource was not found.
24    /// Maps to HTTP 404.
25    #[error("Not found")]
26    NotFound,
27
28    /// An unexpected server-side error occurred.
29    /// Maps to HTTP 5xx.
30    #[error("Server error (HTTP {status})")]
31    ServerError {
32        /// The raw HTTP status code returned by the server.
33        status: u16,
34    },
35
36    /// A network-level error occurred (connection refused, timeout, DNS, etc.).
37    #[error("Network error: {0}")]
38    NetworkError(#[from] reqwest::Error),
39
40    /// The server returned a response that could not be deserialized.
41    #[error("Failed to parse API response: {0}")]
42    ParseError(String),
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn unauthorized_display() {
51        let err = ApiError::Unauthorized;
52        assert_eq!(err.to_string(), "Unauthorized: invalid or missing API key");
53    }
54
55    #[test]
56    fn rate_limit_with_retry_after() {
57        let err = ApiError::RateLimit {
58            retry_after: Some(60),
59        };
60        assert!(err.to_string().contains("60"));
61    }
62
63    #[test]
64    fn rate_limit_without_retry_after() {
65        let err = ApiError::RateLimit { retry_after: None };
66        assert!(err.to_string().contains("None"));
67    }
68
69    #[test]
70    fn not_found_display() {
71        let err = ApiError::NotFound;
72        assert_eq!(err.to_string(), "Not found");
73    }
74
75    #[test]
76    fn server_error_display() {
77        let err = ApiError::ServerError { status: 503 };
78        assert!(err.to_string().contains("503"));
79    }
80
81    #[test]
82    fn parse_error_display() {
83        let err = ApiError::ParseError("missing field `id`".to_owned());
84        assert!(err.to_string().contains("missing field `id`"));
85    }
86}