1pub type Result<T> = std::result::Result<T, GhostError>;
22
23#[derive(thiserror::Error, Debug)]
29pub enum GhostError {
30 #[error("Ghost API error ({error_type}): {message}")]
35 Api {
36 message: String,
38 error_type: String,
40 context: Option<String>,
42 },
43
44 #[error("HTTP error: {0}")]
49 Http(#[from] reqwest::Error),
50
51 #[error("Serialization error: {0}")]
56 Json(#[from] serde_json::Error),
57
58 #[error("Authentication error: {0}")]
63 Auth(String),
64}
65
66impl GhostError {
67 pub fn api(
81 message: impl Into<String>,
82 error_type: impl Into<String>,
83 context: Option<String>,
84 ) -> Self {
85 Self::Api {
86 message: message.into(),
87 error_type: error_type.into(),
88 context,
89 }
90 }
91
92 pub fn auth(message: impl Into<String>) -> Self {
102 Self::Auth(message.into())
103 }
104
105 pub fn is_api_error(&self) -> bool {
116 matches!(self, Self::Api { .. })
117 }
118
119 pub fn is_http_error(&self) -> bool {
121 matches!(self, Self::Http(_))
122 }
123
124 pub fn is_json_error(&self) -> bool {
126 matches!(self, Self::Json(_))
127 }
128
129 pub fn is_auth_error(&self) -> bool {
131 matches!(self, Self::Auth(_))
132 }
133
134 pub fn api_error_type(&self) -> Option<&str> {
147 match self {
148 Self::Api { error_type, .. } => Some(error_type),
149 _ => None,
150 }
151 }
152
153 pub fn api_message(&self) -> Option<&str> {
157 match self {
158 Self::Api { message, .. } => Some(message),
159 _ => None,
160 }
161 }
162
163 pub fn api_context(&self) -> Option<&str> {
167 match self {
168 Self::Api { context, .. } => context.as_deref(),
169 _ => None,
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_api_error_creation() {
180 let error = GhostError::api("Resource not found", "NotFoundError", None);
181 assert!(error.is_api_error());
182 assert_eq!(error.api_error_type(), Some("NotFoundError"));
183 assert_eq!(error.api_message(), Some("Resource not found"));
184 assert_eq!(error.api_context(), None);
185 }
186
187 #[test]
188 fn test_api_error_with_context() {
189 let error = GhostError::api(
190 "Validation failed",
191 "ValidationError",
192 Some("Title is required".to_string()),
193 );
194 assert!(error.is_api_error());
195 assert_eq!(error.api_error_type(), Some("ValidationError"));
196 assert_eq!(error.api_message(), Some("Validation failed"));
197 assert_eq!(error.api_context(), Some("Title is required"));
198 }
199
200 #[test]
201 fn test_auth_error_creation() {
202 let error = GhostError::auth("Invalid API key");
203 assert!(error.is_auth_error());
204 assert!(!error.is_api_error());
205 assert_eq!(error.api_error_type(), None);
206 }
207
208 #[test]
209 fn test_http_error_from_trait() {
210 fn _assert_from_impl(e: reqwest::Error) -> GhostError {
221 e.into()
222 }
223 }
224
225 #[test]
226 fn test_json_error_conversion() {
227 let json_error = serde_json::from_str::<serde_json::Value>("not valid json").unwrap_err();
228 let error: GhostError = json_error.into();
229 assert!(error.is_json_error());
230 assert!(!error.is_api_error());
231 }
232
233 #[test]
234 fn test_error_display() {
235 let error = GhostError::api("Not found", "NotFoundError", None);
236 let display = format!("{}", error);
237 assert!(display.contains("NotFoundError"));
238 assert!(display.contains("Not found"));
239 }
240
241 #[test]
242 fn test_error_display_with_context() {
243 let error = GhostError::api(
244 "Validation failed",
245 "ValidationError",
246 Some("Title required".to_string()),
247 );
248 let display = format!("{}", error);
249 assert!(display.contains("ValidationError"));
250 assert!(display.contains("Validation failed"));
251 }
252
253 #[test]
254 fn test_auth_error_display() {
255 let error = GhostError::auth("JWT generation failed");
256 let display = format!("{}", error);
257 assert!(display.contains("Authentication error"));
258 assert!(display.contains("JWT generation failed"));
259 }
260
261 #[test]
262 fn test_result_type_alias() {
263 fn returns_result() -> Result<String> {
264 Ok("success".to_string())
265 }
266
267 let result = returns_result();
268 assert!(result.is_ok());
269 assert_eq!(result.unwrap(), "success");
270 }
271
272 #[test]
273 fn test_result_with_error() {
274 fn returns_error() -> Result<String> {
275 Err(GhostError::auth("Failed"))
276 }
277
278 let result = returns_error();
279 assert!(result.is_err());
280 let error = result.unwrap_err();
281 assert!(error.is_auth_error());
282 }
283
284 #[test]
285 fn test_error_type_checks() {
286 let api_error = GhostError::api("Test", "TestError", None);
287 assert!(api_error.is_api_error());
288 assert!(!api_error.is_http_error());
289 assert!(!api_error.is_json_error());
290 assert!(!api_error.is_auth_error());
291
292 let auth_error = GhostError::auth("Test");
293 assert!(!auth_error.is_api_error());
294 assert!(!auth_error.is_http_error());
295 assert!(!auth_error.is_json_error());
296 assert!(auth_error.is_auth_error());
297 }
298
299 #[test]
300 fn test_api_error_accessors() {
301 let error = GhostError::api("Test message", "TestType", Some("Test context".to_string()));
302
303 assert_eq!(error.api_message(), Some("Test message"));
304 assert_eq!(error.api_error_type(), Some("TestType"));
305 assert_eq!(error.api_context(), Some("Test context"));
306 }
307
308 #[test]
309 fn test_non_api_error_accessors_return_none() {
310 let error = GhostError::auth("Test");
311
312 assert_eq!(error.api_message(), None);
313 assert_eq!(error.api_error_type(), None);
314 assert_eq!(error.api_context(), None);
315 }
316
317 #[test]
318 fn test_error_is_send_and_sync() {
319 fn assert_send<T: Send>() {}
320 fn assert_sync<T: Sync>() {}
321
322 assert_send::<GhostError>();
323 assert_sync::<GhostError>();
324 }
325
326 #[test]
327 fn test_error_debug() {
328 let error = GhostError::api("Debug test", "DebugError", None);
329 let debug = format!("{:?}", error);
330 assert!(debug.contains("Api"));
331 assert!(debug.contains("Debug test"));
332 assert!(debug.contains("DebugError"));
333 }
334}