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