metabase_api_rs/core/
error.rs

1//! Error types for the metabase-api-rs library
2//!
3//! This module defines the error types used throughout the library,
4//! following SOLID principles and providing clear error messages.
5
6use thiserror::Error;
7
8/// The main error type for metabase-api-rs operations
9#[derive(Debug, Error)]
10pub enum Error {
11    /// HTTP request failed with status code
12    #[error("HTTP request failed with status {status}: {message}")]
13    Http { status: u16, message: String },
14
15    /// JSON parsing failed
16    #[error("JSON parsing failed: {0}")]
17    Json(String),
18
19    /// Serialization/Deserialization error
20    #[error("Serialization error: {0}")]
21    Serialization(String),
22
23    /// Authentication failed
24    #[error("Authentication failed: {0}")]
25    Authentication(String),
26
27    /// Validation error
28    #[error("Validation error: {0}")]
29    Validation(String),
30
31    /// Configuration error
32    #[error("Configuration error: {0}")]
33    Config(String),
34
35    /// Session error
36    #[error("Session error: {0}")]
37    Session(String),
38
39    /// Invalid parameter provided
40    #[error("Invalid parameter: {0}")]
41    InvalidParameter(String),
42
43    /// Resource not found
44    #[error("Resource not found: {0}")]
45    NotFound(String),
46
47    /// Rate limit exceeded
48    #[error("Rate limited")]
49    RateLimited { retry_after: Option<u32> },
50
51    /// Server error
52    #[error("Server error: {0}")]
53    Server(String),
54
55    /// Request timeout
56    #[error("Request timeout")]
57    Timeout,
58
59    /// Network error
60    #[error("Network error: {0}")]
61    Network(String),
62
63    /// Not implemented error
64    #[error("Not implemented: {0}")]
65    NotImplemented(String),
66
67    /// Unknown error
68    #[error("Unknown error: {0}")]
69    Unknown(String),
70}
71
72/// A type alias for Results that use our Error type
73pub type Result<T> = std::result::Result<T, Error>;
74
75// From implementations for error conversion
76impl From<serde_json::Error> for Error {
77    fn from(err: serde_json::Error) -> Self {
78        Error::Json(err.to_string())
79    }
80}
81
82impl From<reqwest::Error> for Error {
83    fn from(err: reqwest::Error) -> Self {
84        if err.is_timeout() {
85            Error::Timeout
86        } else if err.is_connect() {
87            Error::Network(format!("Connection failed: {}", err))
88        } else if err.is_status() {
89            if let Some(status) = err.status() {
90                match status.as_u16() {
91                    401 | 403 => Error::Authentication(format!("Authentication error: {}", status)),
92                    404 => Error::NotFound(format!("Resource not found: {}", status)),
93                    429 => Error::RateLimited { retry_after: None },
94                    500..=599 => Error::Server(format!("Server error: {}", status)),
95                    code => Error::Http {
96                        status: code,
97                        message: format!("HTTP error: {}", status),
98                    },
99                }
100            } else {
101                Error::Http {
102                    status: 0,
103                    message: err.to_string(),
104                }
105            }
106        } else {
107            Error::Http {
108                status: 0,
109                message: err.to_string(),
110            }
111        }
112    }
113}
114
115impl From<std::io::Error> for Error {
116    fn from(err: std::io::Error) -> Self {
117        Error::Network(format!("IO error: {}", err))
118    }
119}
120
121// まず失敗するテストを書く(TDD Red Phase)
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_error_http_variant() {
128        // Arrange
129        let status = 400;
130        let msg = "Bad Request".to_string();
131
132        // Act
133        let error = Error::Http {
134            status,
135            message: msg.clone(),
136        };
137
138        // Assert
139        assert_eq!(
140            error.to_string(),
141            format!("HTTP request failed with status {}: {}", status, msg)
142        );
143    }
144
145    #[test]
146    fn test_error_json_variant() {
147        // Arrange
148        let msg = "Invalid JSON structure".to_string();
149
150        // Act
151        let error = Error::Json(msg.clone());
152
153        // Assert
154        assert_eq!(error.to_string(), format!("JSON parsing failed: {}", msg));
155    }
156
157    #[test]
158    fn test_error_auth_variant() {
159        // Arrange
160        let msg = "Invalid credentials".to_string();
161
162        // Act
163        let error = Error::Authentication(msg.clone());
164
165        // Assert
166        assert_eq!(error.to_string(), format!("Authentication failed: {}", msg));
167    }
168
169    #[test]
170    fn test_error_not_found_variant() {
171        // Arrange
172        let msg = "Card with id 123".to_string();
173
174        // Act
175        let error = Error::NotFound(msg.clone());
176
177        // Assert
178        assert_eq!(error.to_string(), format!("Resource not found: {}", msg));
179    }
180
181    #[test]
182    fn test_error_rate_limited() {
183        // Act
184        let error = Error::RateLimited { retry_after: None };
185
186        // Assert
187        assert_eq!(error.to_string(), "Rate limited");
188    }
189
190    #[test]
191    fn test_error_from_reqwest_error() {
192        // このテストは実際のreqwest::Errorを作るのが難しいので、
193        // モックや実際のHTTPエラーで後でテストする
194        // 今はコンパイルエラーになることを確認
195
196        // let reqwest_err = create_mock_reqwest_error();
197        // let error: Error = reqwest_err.into();
198        // match error {
199        //     Error::Http(_) | Error::Network(_) => (),
200        //     _ => panic!("Expected Http or Network error"),
201        // }
202    }
203
204    #[test]
205    fn test_error_from_serde_json_error() {
206        // Arrange
207        let json_err = serde_json::from_str::<String>("invalid json").unwrap_err();
208
209        // Act
210        let error: Error = json_err.into();
211
212        // Assert
213        match error {
214            Error::Json(_) => (),
215            _ => panic!("Expected Json error"),
216        }
217    }
218
219    #[test]
220    fn test_result_type_alias() {
221        // Arrange
222        fn test_function() -> Result<String> {
223            Ok("success".to_string())
224        }
225
226        // Act
227        let result = test_function();
228
229        // Assert
230        assert!(result.is_ok());
231        assert_eq!(result.unwrap(), "success");
232    }
233
234    #[test]
235    fn test_error_is_send_sync() {
236        fn assert_send_sync<T: Send + Sync>() {}
237        assert_send_sync::<Error>();
238    }
239}