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