1#[derive(thiserror::Error, Debug)]
5pub enum HttpError {
6 #[error("Reqwest failure: {0}")]
8 RequestError(#[from] reqwest::Error),
9
10 #[error("Invalid URL: {0}")]
12 UrlParseError(#[from] url::ParseError),
13
14 #[error("JSON parsing failed: {0}")]
16 JsonError(#[from] serde_json::Error),
17
18 #[error("IO error: {0}")]
20 IOError(#[from] std::io::Error),
21
22 #[error("{}", HttpError::format_http_error(status, message))]
24 HttpStatus {
25 status: u16,
27 message: String,
29 },
30
31 #[error("API responded with success=false: {message}")]
33 ApiError {
34 message: String,
36 },
37
38 #[error("TLS error: {0}")]
40 TLSError(String),
41
42 #[error("Missing 'response' field in API response")]
44 MissingResponseField,
45
46 #[error("Missing 'type' field in API response")]
48 MissingTypeField,
49
50 #[error("Missing 'data' field in API response")]
52 MissingDataField,
53
54 #[error("Type mismatch: expected '{expected}', got '{actual}'")]
56 ResponseTypeMismatch {
57 expected: String,
59 actual: String,
61 },
62}
63impl HttpError {
64 fn status_text(status: u16) -> &'static str {
65 match status {
66 200 => "OK",
67 400 => "Bad Request",
68 401 => "Unauthorized",
69 403 => "Forbidden",
70 404 => "Not Found",
71 405 => "Method Not Allowed",
72 408 => "Not Acceptable",
73 429 => "Too Many Requests",
74 500 => "Internal Server Error",
75 503 => "Service Unavailable",
76 504 => "Gateway Timeout",
77 _ => "Unknown",
78 }
79 }
80
81 #[allow(clippy::trivially_copy_pass_by_ref)]
83 fn format_http_error(status: &u16, message: &str) -> String {
84 if message.trim().is_empty() {
85 format!("HTTP {status} {}", Self::status_text(*status))
86 } else {
87 format!("HTTP {status}: {message}")
88 }
89 }
90}
91
92pub type HttpResult<T> = Result<T, HttpError>;