1use std::fmt::Debug;
2
3use super::auth::AuthenticationError;
4use super::output::Output;
5
6#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
11pub enum ApiClientError {
12 ReqwestError(reqwest::Error),
16
17 UrlError(url::ParseError),
21
22 HeadersError(headers::Error),
26
27 HttpError(http::Error),
31
32 InvalidHeaderName(http::header::InvalidHeaderName),
36
37 InvalidHeaderValue(http::header::InvalidHeaderValue),
41
42 JsonValueError(serde_json::Error),
46
47 QuerySerializationError(serde_urlencoded::ser::Error),
51
52 AuthenticationError(AuthenticationError),
56
57 #[display("Invalid state: expected a call result")]
61 CallResultRequired,
62
63 #[display("Invalid base path: {error}")]
67 InvalidBasePath {
68 error: String,
70 },
71
72 #[display("Failed to deserialize JSON at '{path}': {error}\n{body}")]
76 #[from(skip)]
77 JsonError {
78 path: String,
80 error: serde_json::Error,
82 body: String,
84 },
85
86 #[display("Unsupported output for {name} as JSON:\n{output:?}")]
90 #[from(skip)]
91 UnsupportedJsonOutput {
92 output: Output,
94 name: &'static str,
96 },
97
98 #[display("Unsupported output for text:\n{output:?}")]
102 #[from(skip)]
103 UnsupportedTextOutput {
104 output: Output,
106 },
107
108 #[display("Unsupported output for bytes:\n{output:?}")]
112 #[from(skip)]
113 UnsupportedBytesOutput {
114 output: Output,
116 },
117
118 #[display("Path '{path}' is missing required arguments: {missings:?}")]
122 #[from(skip)]
123 PathUnresolved {
124 path: String,
126 missings: Vec<String>,
128 },
129
130 #[display(
134 "Unsupported query parameter value: objects are not supported for query parameters. Got: {value}"
135 )]
136 #[from(skip)]
137 UnsupportedQueryParameterValue {
138 value: serde_json::Value,
140 },
141
142 #[display("Unsupported parameter value: {message}. Got: {value}")]
146 #[from(skip)]
147 UnsupportedParameterValue {
148 message: String,
150 value: serde_json::Value,
152 },
153
154 #[display("Missing operation: {id}")]
158 #[from(skip)]
159 MissingOperation {
160 id: String,
162 },
163
164 #[display("Server error (500) with response body: {raw_body}")]
168 #[from(skip)]
169 ServerFailure {
170 raw_body: String,
172 },
173
174 #[display("Serialization error: {message}")]
178 #[from(skip)]
179 SerializationError {
180 message: String,
182 },
183
184 #[display("Unexpected status code {status_code}: {body}")]
188 #[from(skip)]
189 UnexpectedStatusCode {
190 status_code: u16,
192 body: String,
194 },
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::client::output::Output;
201
202 #[test]
203 fn test_api_client_error_is_send_and_sync() {
204 fn assert_send<T: Send>() {}
205 fn assert_sync<T: Sync>() {}
206
207 assert_send::<ApiClientError>();
208 assert_sync::<ApiClientError>();
209 }
210
211 #[test]
213 fn test_call_result_required_error() {
214 let error = ApiClientError::CallResultRequired;
215 assert_eq!(error.to_string(), "Invalid state: expected a call result");
216 }
217
218 #[test]
219 fn test_invalid_base_path_error() {
220 let error = ApiClientError::InvalidBasePath {
221 error: "contains invalid characters".to_string(),
222 };
223 assert_eq!(
224 error.to_string(),
225 "Invalid base path: contains invalid characters"
226 );
227 }
228
229 #[test]
230 fn test_json_error() {
231 let json_error = serde_json::from_str::<serde_json::Value>("{ invalid json").unwrap_err();
233 let error = ApiClientError::JsonError {
234 path: "/api/users".to_string(),
235 error: json_error,
236 body: "{ invalid json }".to_string(),
237 };
238
239 let error_str = error.to_string();
240 assert!(error_str.contains("Failed to deserialize JSON at '/api/users'"));
241 assert!(error_str.contains("{ invalid json }"));
242 }
243
244 #[test]
245 fn test_unsupported_json_output_error() {
246 let output = Output::Bytes(vec![0xFF, 0xFE, 0xFD]);
247 let error = ApiClientError::UnsupportedJsonOutput {
248 output,
249 name: "User",
250 };
251
252 let error_str = error.to_string();
253 assert!(error_str.contains("Unsupported output for User as JSON"));
254 assert!(error_str.contains("Bytes"));
255 }
256
257 #[test]
258 fn test_unsupported_text_output_error() {
259 let output = Output::Bytes(vec![0xFF, 0xFE, 0xFD]);
260 let error = ApiClientError::UnsupportedTextOutput { output };
261
262 let error_str = error.to_string();
263 assert!(error_str.contains("Unsupported output for text"));
264 assert!(error_str.contains("Bytes"));
265 }
266
267 #[test]
268 fn test_unsupported_bytes_output_error() {
269 let output = Output::Empty;
270 let error = ApiClientError::UnsupportedBytesOutput { output };
271
272 let error_str = error.to_string();
273 assert!(error_str.contains("Unsupported output for bytes"));
274 assert!(error_str.contains("Empty"));
275 }
276
277 #[test]
278 fn test_path_unresolved_error() {
279 let error = ApiClientError::PathUnresolved {
280 path: "/users/{id}/posts/{post_id}".to_string(),
281 missings: vec!["id".to_string(), "post_id".to_string()],
282 };
283
284 let error_str = error.to_string();
285 assert!(
286 error_str.contains("Path '/users/{id}/posts/{post_id}' is missing required arguments")
287 );
288 assert!(error_str.contains("id"));
289 assert!(error_str.contains("post_id"));
290 }
291
292 #[test]
293 fn test_unsupported_query_parameter_value_error() {
294 let value = serde_json::json!({"nested": {"object": "not supported"}});
295 let error = ApiClientError::UnsupportedQueryParameterValue {
296 value: value.clone(),
297 };
298
299 let error_str = error.to_string();
300 assert!(error_str.contains("Unsupported query parameter value"));
301 assert!(error_str.contains("objects are not supported"));
302 }
303
304 #[test]
305 fn test_unsupported_parameter_value_error() {
306 let value = serde_json::json!({"complex": "object"});
307 let error = ApiClientError::UnsupportedParameterValue {
308 message: "nested objects not allowed".to_string(),
309 value: value.clone(),
310 };
311
312 let error_str = error.to_string();
313 assert!(error_str.contains("Unsupported parameter value: nested objects not allowed"));
314 assert!(error_str.contains("complex"));
315 }
316
317 #[test]
318 fn test_missing_operation_error() {
319 let error = ApiClientError::MissingOperation {
320 id: "get-users-by-id".to_string(),
321 };
322
323 assert_eq!(error.to_string(), "Missing operation: get-users-by-id");
324 }
325
326 #[test]
327 fn test_server_failure_error() {
328 let error = ApiClientError::ServerFailure {
329 raw_body: "Internal Server Error: Database connection failed".to_string(),
330 };
331
332 let error_str = error.to_string();
333 assert!(error_str.contains("Server error (500)"));
334 assert!(error_str.contains("Database connection failed"));
335 }
336
337 #[test]
338 fn test_serialization_error() {
339 let error = ApiClientError::SerializationError {
340 message: "Cannot serialize circular reference".to_string(),
341 };
342
343 assert_eq!(
344 error.to_string(),
345 "Serialization error: Cannot serialize circular reference"
346 );
347 }
348
349 #[test]
350 fn test_unexpected_status_code_error() {
351 let error = ApiClientError::UnexpectedStatusCode {
352 status_code: 418,
353 body: "I'm a teapot".to_string(),
354 };
355
356 let error_str = error.to_string();
357 assert!(error_str.contains("Unexpected status code 418"));
358 assert!(error_str.contains("I'm a teapot"));
359 }
360
361 #[test]
363 fn test_from_reqwest_error() {
364 let url_error = url::ParseError::InvalidPort;
366 let api_error: ApiClientError = url_error.into();
367
368 match api_error {
369 ApiClientError::UrlError(_) => {} _ => panic!("Should convert to UrlError"),
371 }
372 }
373
374 #[test]
375 fn test_from_url_parse_error() {
376 let url_error = url::ParseError::InvalidPort;
377 let api_error: ApiClientError = url_error.into();
378
379 match api_error {
380 ApiClientError::UrlError(url::ParseError::InvalidPort) => {} _ => panic!("Should convert to UrlError"),
382 }
383 }
384
385 #[test]
386 fn test_from_json_error() {
387 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
388 let api_error: ApiClientError = json_error.into();
389
390 match api_error {
391 ApiClientError::JsonValueError(_) => {} _ => panic!("Should convert to JsonValueError"),
393 }
394 }
395
396 #[test]
397 fn test_from_http_error() {
398 let invalid_header = http::HeaderName::from_bytes(b"invalid\0header").unwrap_err();
400 let api_error: ApiClientError = invalid_header.into();
401
402 match api_error {
403 ApiClientError::InvalidHeaderName(_) => {} _ => panic!("Should convert to InvalidHeaderName"),
405 }
406 }
407
408 #[test]
409 fn test_from_invalid_header_name() {
410 let header_error = http::HeaderName::from_bytes(b"invalid\0header").unwrap_err();
411 let api_error: ApiClientError = header_error.into();
412
413 match api_error {
414 ApiClientError::InvalidHeaderName(_) => {} _ => panic!("Should convert to InvalidHeaderName"),
416 }
417 }
418
419 #[test]
420 fn test_from_invalid_header_value() {
421 let header_error = http::HeaderValue::from_bytes(&[0x00]).unwrap_err();
423 let api_error: ApiClientError = header_error.into();
424
425 match api_error {
426 ApiClientError::InvalidHeaderValue(_) => {} _ => panic!("Should convert to InvalidHeaderValue"),
428 }
429 }
430
431 #[test]
432 fn test_from_authentication_error() {
433 let auth_error = AuthenticationError::InvalidBearerToken {
434 message: "contains null byte".to_string(),
435 };
436 let api_error: ApiClientError = auth_error.into();
437
438 match api_error {
439 ApiClientError::AuthenticationError(_) => {} _ => panic!("Should convert to AuthenticationError"),
441 }
442 }
443
444 #[test]
446 fn test_error_debug_implementation() {
447 let error = ApiClientError::CallResultRequired;
448 let debug_str = format!("{error:?}");
449 assert!(debug_str.contains("CallResultRequired"));
450
451 let error = ApiClientError::InvalidBasePath {
452 error: "test".to_string(),
453 };
454 let debug_str = format!("{error:?}");
455 assert!(debug_str.contains("InvalidBasePath"));
456 assert!(debug_str.contains("test"));
457 }
458
459 #[test]
461 fn test_error_trait_implementation() {
462 use std::error::Error;
463
464 let error = ApiClientError::CallResultRequired;
465 assert!(error.source().is_none());
466
467 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
468 let error = ApiClientError::JsonValueError(json_error);
469 assert!(error.source().is_some());
470 }
471
472 #[test]
474 fn test_error_equality() {
475 let error1 = ApiClientError::CallResultRequired;
476 let error2 = ApiClientError::CallResultRequired;
477
478 assert_eq!(error1.to_string(), error2.to_string());
480
481 let error1 = ApiClientError::InvalidBasePath {
482 error: "same error".to_string(),
483 };
484 let error2 = ApiClientError::InvalidBasePath {
485 error: "same error".to_string(),
486 };
487 assert_eq!(error1.to_string(), error2.to_string());
488 }
489
490 #[test]
492 fn test_error_context_preservation() {
493 let path = "/complex/path/{id}";
494 let missings = vec!["id".to_string(), "user_id".to_string()];
495 let error = ApiClientError::PathUnresolved {
496 path: path.to_string(),
497 missings: missings.clone(),
498 };
499
500 let error_string = error.to_string();
501 assert!(error_string.contains(path));
502 for missing in &missings {
503 assert!(error_string.contains(missing));
504 }
505 }
506
507 #[test]
509 fn test_json_error_with_large_body() {
510 let large_body = "x".repeat(2000);
511 let json_error = serde_json::from_str::<serde_json::Value>("{ invalid").unwrap_err();
512 let error = ApiClientError::JsonError {
513 path: "/api/data".to_string(),
514 error: json_error,
515 body: large_body.clone(),
516 };
517
518 let error_str = error.to_string();
519 assert!(error_str.contains("/api/data"));
520 assert!(error_str.contains(&large_body));
521 }
522
523 #[test]
524 fn test_status_code_error_edge_cases() {
525 let error = ApiClientError::UnexpectedStatusCode {
527 status_code: 999, body: "unknown status".to_string(),
529 };
530 assert!(error.to_string().contains("999"));
531
532 let error = ApiClientError::UnexpectedStatusCode {
533 status_code: 0, body: "".to_string(),
535 };
536 assert!(error.to_string().contains("0"));
537 }
538
539 #[test]
541 fn test_output_errors_with_all_output_types() {
542 let text_output = Output::Text("some text".to_string());
544 let error = ApiClientError::UnsupportedBytesOutput {
545 output: text_output,
546 };
547 assert!(error.to_string().contains("Text"));
548
549 let json_output =
551 Output::Json(serde_json::to_string(&serde_json::json!({"key": "value"})).unwrap());
552 let error = ApiClientError::UnsupportedTextOutput {
553 output: json_output,
554 };
555 assert!(error.to_string().contains("Json"));
556
557 let empty_output = Output::Empty;
559 let error = ApiClientError::UnsupportedJsonOutput {
560 output: empty_output,
561 name: "TestType",
562 };
563 assert!(error.to_string().contains("Empty"));
564 assert!(error.to_string().contains("TestType"));
565 }
566}