1use std::fmt::Debug;
2
3use super::auth::AuthenticationError;
4use super::response::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 #[cfg(feature = "redaction")]
200 #[display("Redaction error: {message}")]
201 #[from(skip)]
202 RedactionError {
203 message: String,
205 },
206
207 #[display("Expected output type '{expected}' but got '{actual}'")]
211 #[from(skip)]
212 UnexpectedOutputType {
213 expected: String,
215 actual: String,
217 },
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::client::response::output::Output;
224
225 #[test]
226 fn test_api_client_error_is_send_and_sync() {
227 fn assert_send<T: Send>() {}
228 fn assert_sync<T: Sync>() {}
229
230 assert_send::<ApiClientError>();
231 assert_sync::<ApiClientError>();
232 }
233
234 #[test]
236 fn test_call_result_required_error() {
237 let error = ApiClientError::CallResultRequired;
238 assert_eq!(error.to_string(), "Invalid state: expected a call result");
239 }
240
241 #[test]
242 fn test_invalid_base_path_error() {
243 let error = ApiClientError::InvalidBasePath {
244 error: "contains invalid characters".to_string(),
245 };
246 assert_eq!(
247 error.to_string(),
248 "Invalid base path: contains invalid characters"
249 );
250 }
251
252 #[test]
253 fn test_json_error() {
254 let json_error = serde_json::from_str::<serde_json::Value>("{ invalid json").unwrap_err();
256 let error = ApiClientError::JsonError {
257 path: "/api/users".to_string(),
258 error: json_error,
259 body: "{ invalid json }".to_string(),
260 };
261
262 let error_str = error.to_string();
263 assert!(error_str.contains("Failed to deserialize JSON at '/api/users'"));
264 assert!(error_str.contains("{ invalid json }"));
265 }
266
267 #[test]
268 fn test_unsupported_json_output_error() {
269 let output = Output::Bytes(vec![0xFF, 0xFE, 0xFD]);
270 let error = ApiClientError::UnsupportedJsonOutput {
271 output,
272 name: "User",
273 };
274
275 let error_str = error.to_string();
276 assert!(error_str.contains("Unsupported output for User as JSON"));
277 assert!(error_str.contains("Bytes"));
278 }
279
280 #[test]
281 fn test_unsupported_text_output_error() {
282 let output = Output::Bytes(vec![0xFF, 0xFE, 0xFD]);
283 let error = ApiClientError::UnsupportedTextOutput { output };
284
285 let error_str = error.to_string();
286 assert!(error_str.contains("Unsupported output for text"));
287 assert!(error_str.contains("Bytes"));
288 }
289
290 #[test]
291 fn test_unsupported_bytes_output_error() {
292 let output = Output::Empty;
293 let error = ApiClientError::UnsupportedBytesOutput { output };
294
295 let error_str = error.to_string();
296 assert!(error_str.contains("Unsupported output for bytes"));
297 assert!(error_str.contains("Empty"));
298 }
299
300 #[test]
301 fn test_path_unresolved_error() {
302 let error = ApiClientError::PathUnresolved {
303 path: "/users/{id}/posts/{post_id}".to_string(),
304 missings: vec!["id".to_string(), "post_id".to_string()],
305 };
306
307 let error_str = error.to_string();
308 assert!(
309 error_str.contains("Path '/users/{id}/posts/{post_id}' is missing required arguments")
310 );
311 assert!(error_str.contains("id"));
312 assert!(error_str.contains("post_id"));
313 }
314
315 #[test]
316 fn test_unsupported_query_parameter_value_error() {
317 let value = serde_json::json!({"nested": {"object": "not supported"}});
318 let error = ApiClientError::UnsupportedQueryParameterValue {
319 value: value.clone(),
320 };
321
322 let error_str = error.to_string();
323 assert!(error_str.contains("Unsupported query parameter value"));
324 assert!(error_str.contains("objects are not supported"));
325 }
326
327 #[test]
328 fn test_unsupported_parameter_value_error() {
329 let value = serde_json::json!({"complex": "object"});
330 let error = ApiClientError::UnsupportedParameterValue {
331 message: "nested objects not allowed".to_string(),
332 value: value.clone(),
333 };
334
335 let error_str = error.to_string();
336 assert!(error_str.contains("Unsupported parameter value: nested objects not allowed"));
337 assert!(error_str.contains("complex"));
338 }
339
340 #[test]
341 fn test_missing_operation_error() {
342 let error = ApiClientError::MissingOperation {
343 id: "get-users-by-id".to_string(),
344 };
345
346 assert_eq!(error.to_string(), "Missing operation: get-users-by-id");
347 }
348
349 #[test]
350 fn test_server_failure_error() {
351 let error = ApiClientError::ServerFailure {
352 raw_body: "Internal Server Error: Database connection failed".to_string(),
353 };
354
355 let error_str = error.to_string();
356 assert!(error_str.contains("Server error (500)"));
357 assert!(error_str.contains("Database connection failed"));
358 }
359
360 #[test]
361 fn test_serialization_error() {
362 let error = ApiClientError::SerializationError {
363 message: "Cannot serialize circular reference".to_string(),
364 };
365
366 assert_eq!(
367 error.to_string(),
368 "Serialization error: Cannot serialize circular reference"
369 );
370 }
371
372 #[test]
373 fn test_unexpected_status_code_error() {
374 let error = ApiClientError::UnexpectedStatusCode {
375 status_code: 418,
376 body: "I'm a teapot".to_string(),
377 };
378
379 let error_str = error.to_string();
380 assert!(error_str.contains("Unexpected status code 418"));
381 assert!(error_str.contains("I'm a teapot"));
382 }
383
384 #[test]
386 fn test_from_reqwest_error() {
387 let url_error = url::ParseError::InvalidPort;
389 let api_error: ApiClientError = url_error.into();
390
391 match api_error {
392 ApiClientError::UrlError(_) => {} _ => panic!("Should convert to UrlError"),
394 }
395 }
396
397 #[test]
398 fn test_from_url_parse_error() {
399 let url_error = url::ParseError::InvalidPort;
400 let api_error: ApiClientError = url_error.into();
401
402 match api_error {
403 ApiClientError::UrlError(url::ParseError::InvalidPort) => {} _ => panic!("Should convert to UrlError"),
405 }
406 }
407
408 #[test]
409 fn test_from_json_error() {
410 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
411 let api_error: ApiClientError = json_error.into();
412
413 match api_error {
414 ApiClientError::JsonValueError(_) => {} _ => panic!("Should convert to JsonValueError"),
416 }
417 }
418
419 #[test]
420 fn test_from_http_error() {
421 let invalid_header = http::HeaderName::from_bytes(b"invalid\0header").unwrap_err();
423 let api_error: ApiClientError = invalid_header.into();
424
425 match api_error {
426 ApiClientError::InvalidHeaderName(_) => {} _ => panic!("Should convert to InvalidHeaderName"),
428 }
429 }
430
431 #[test]
432 fn test_from_invalid_header_name() {
433 let header_error = http::HeaderName::from_bytes(b"invalid\0header").unwrap_err();
434 let api_error: ApiClientError = header_error.into();
435
436 match api_error {
437 ApiClientError::InvalidHeaderName(_) => {} _ => panic!("Should convert to InvalidHeaderName"),
439 }
440 }
441
442 #[test]
443 fn test_from_invalid_header_value() {
444 let header_error = http::HeaderValue::from_bytes(&[0x00]).unwrap_err();
446 let api_error: ApiClientError = header_error.into();
447
448 match api_error {
449 ApiClientError::InvalidHeaderValue(_) => {} _ => panic!("Should convert to InvalidHeaderValue"),
451 }
452 }
453
454 #[test]
455 fn test_from_authentication_error() {
456 let auth_error = AuthenticationError::InvalidBearerToken {
457 message: "contains null byte".to_string(),
458 };
459 let api_error: ApiClientError = auth_error.into();
460
461 match api_error {
462 ApiClientError::AuthenticationError(_) => {} _ => panic!("Should convert to AuthenticationError"),
464 }
465 }
466
467 #[test]
469 fn test_error_debug_implementation() {
470 let error = ApiClientError::CallResultRequired;
471 let debug_str = format!("{error:?}");
472 assert!(debug_str.contains("CallResultRequired"));
473
474 let error = ApiClientError::InvalidBasePath {
475 error: "test".to_string(),
476 };
477 let debug_str = format!("{error:?}");
478 assert!(debug_str.contains("InvalidBasePath"));
479 assert!(debug_str.contains("test"));
480 }
481
482 #[test]
484 fn test_error_trait_implementation() {
485 use std::error::Error;
486
487 let error = ApiClientError::CallResultRequired;
488 assert!(error.source().is_none());
489
490 let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
491 let error = ApiClientError::JsonValueError(json_error);
492 assert!(error.source().is_some());
493 }
494
495 #[test]
497 fn test_error_equality() {
498 let error1 = ApiClientError::CallResultRequired;
499 let error2 = ApiClientError::CallResultRequired;
500
501 assert_eq!(error1.to_string(), error2.to_string());
503
504 let error1 = ApiClientError::InvalidBasePath {
505 error: "same error".to_string(),
506 };
507 let error2 = ApiClientError::InvalidBasePath {
508 error: "same error".to_string(),
509 };
510 assert_eq!(error1.to_string(), error2.to_string());
511 }
512
513 #[test]
515 fn test_error_context_preservation() {
516 let path = "/complex/path/{id}";
517 let missings = vec!["id".to_string(), "user_id".to_string()];
518 let error = ApiClientError::PathUnresolved {
519 path: path.to_string(),
520 missings: missings.clone(),
521 };
522
523 let error_string = error.to_string();
524 assert!(error_string.contains(path));
525 for missing in &missings {
526 assert!(error_string.contains(missing));
527 }
528 }
529
530 #[test]
532 fn test_json_error_with_large_body() {
533 let large_body = "x".repeat(2000);
534 let json_error = serde_json::from_str::<serde_json::Value>("{ invalid").unwrap_err();
535 let error = ApiClientError::JsonError {
536 path: "/api/data".to_string(),
537 error: json_error,
538 body: large_body.clone(),
539 };
540
541 let error_str = error.to_string();
542 assert!(error_str.contains("/api/data"));
543 assert!(error_str.contains(&large_body));
544 }
545
546 #[test]
547 fn test_status_code_error_edge_cases() {
548 let error = ApiClientError::UnexpectedStatusCode {
550 status_code: 999, body: "unknown status".to_string(),
552 };
553 assert!(error.to_string().contains("999"));
554
555 let error = ApiClientError::UnexpectedStatusCode {
556 status_code: 0, body: "".to_string(),
558 };
559 assert!(error.to_string().contains("0"));
560 }
561
562 #[test]
564 fn test_output_errors_with_all_output_types() {
565 let text_output = Output::Text("some text".to_string());
567 let error = ApiClientError::UnsupportedBytesOutput {
568 output: text_output,
569 };
570 assert!(error.to_string().contains("Text"));
571
572 let json_output =
574 Output::Json(serde_json::to_string(&serde_json::json!({"key": "value"})).unwrap());
575 let error = ApiClientError::UnsupportedTextOutput {
576 output: json_output,
577 };
578 assert!(error.to_string().contains("Json"));
579
580 let empty_output = Output::Empty;
582 let error = ApiClientError::UnsupportedJsonOutput {
583 output: empty_output,
584 name: "TestType",
585 };
586 assert!(error.to_string().contains("Empty"));
587 assert!(error.to_string().contains("TestType"));
588 }
589}