Skip to main content

yesser_todo_errors/
api_error.rs

1use std::fmt::Display;
2
3use thiserror::Error;
4
5use crate::server_error::ServerError;
6
7#[derive(Debug, Error)]
8pub enum ApiError {
9    ServerError(ServerError),
10    HTTPError(u16),
11    #[cfg(feature = "client")]
12    RequestError(ureq::Error),
13}
14
15#[cfg(feature = "client")]
16impl From<ureq::Error> for ApiError {
17    fn from(value: ureq::Error) -> Self {
18        ApiError::RequestError(value)
19    }
20}
21
22impl Display for ApiError {
23    /// Formats an `ApiError` as a human-readable message.
24    ///
25    /// - `ApiError::HTTPError(status)` is displayed as `"Server returned HTTP error code {status}"`.
26    /// - `ApiError::RequestError(_)` is displayed as `"Failed to connect to server"`.
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// use yesser_todo_errors::api_error::ApiError;
32    /// // `ApiError` is defined in the current crate module where this formatter lives.
33    /// let err = ApiError::HTTPError(400);
34    /// assert_eq!(format!("{}", err), format!("Server returned HTTP error code {}", 400));
35    /// ```
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            Self::ServerError(err) => err.fmt(f),
39            Self::HTTPError(status_code) => write!(f, "Server returned HTTP error code {status_code}"),
40            #[cfg(feature = "client")]
41            Self::RequestError(_) => write!(f, "Failed to connect to server"),
42        }
43    }
44}
45
46#[cfg(feature = "client")]
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn test_api_error_is_error_trait() {
53        fn assert_error<T: std::error::Error>() {}
54        assert_error::<ApiError>();
55    }
56
57    #[test]
58    fn test_api_error_debug() {
59        let err = ApiError::HTTPError(400);
60        let debug_str = format!("{:?}", err);
61        assert!(debug_str.contains("HTTPError"));
62        assert!(debug_str.contains("400"));
63    }
64
65    #[test]
66    fn test_http_error_various_status_codes() {
67        let test_cases = vec![
68            (200, "Server returned HTTP error code 200"),
69            (201, "Server returned HTTP error code 201"),
70            (403, "Server returned HTTP error code 403"),
71            (503, "Server returned HTTP error code 503"),
72        ];
73
74        for (status, expected) in test_cases {
75            let err = ApiError::HTTPError(status);
76            assert_eq!(format!("{}", err), expected);
77        }
78    }
79
80    #[test]
81    fn test_server_error_display_not_found() {
82        use crate::server_error::TaskSelector;
83        let err = ApiError::ServerError(ServerError::NotFound(TaskSelector::Name("test task".into())));
84        assert_eq!(format!("{}", err), "Task test task not found!");
85    }
86
87    #[test]
88    fn test_server_error_display_conflict() {
89        use crate::server_error::TaskSelector;
90        let err = ApiError::ServerError(ServerError::Conflict(TaskSelector::Index(1)));
91        assert_eq!(format!("{}", err), "Task of index 1 already exists!");
92    }
93
94    #[test]
95    fn test_server_error_display_io_error() {
96        let err = ApiError::ServerError(ServerError::IOError("database connection failed".into()));
97        assert_eq!(format!("{}", err), "IO error: database connection failed");
98    }
99}