1use facet::Facet;
2use serde::{Deserialize, Serialize};
3use thiserror::Error as ThisError;
4
5#[derive(Facet, Serialize, Deserialize, PartialEq, Eq, Clone, ThisError, Debug)]
23#[repr(C)]
24pub enum HttpError {
25 #[error("URL parse error: {0}")]
28 Url(String),
29 #[error("IO error: {0}")]
31 Io(String),
32 #[error("Timeout")]
34 Timeout,
35
36 #[error("HTTP error {code}: {message}")]
38 #[serde(skip)]
39 #[facet(skip)]
40 Http {
41 code: u16,
42 message: String,
43 body: Option<Vec<u8>>,
44 },
45 #[error("JSON serialization error: {0}")]
46 #[serde(skip)]
47 #[facet(skip)]
48 Json(String),
49}
50
51#[cfg(feature = "http-types")]
52impl From<http_types::Error> for HttpError {
53 fn from(e: http_types::Error) -> Self {
54 Self::Http {
55 code: e.status().into(),
56 message: e.to_string(),
57 body: None,
58 }
59 }
60}
61
62impl From<std::io::Error> for HttpError {
63 fn from(e: std::io::Error) -> Self {
64 Self::Io(e.to_string())
65 }
66}
67
68impl From<serde_json::Error> for HttpError {
69 fn from(e: serde_json::Error) -> Self {
70 Self::Json(e.to_string())
71 }
72}
73
74impl From<url::ParseError> for HttpError {
75 fn from(e: url::ParseError) -> Self {
76 Self::Url(e.to_string())
77 }
78}
79
80impl From<serde_qs::Error> for HttpError {
81 fn from(e: serde_qs::Error) -> Self {
82 Self::Json(e.to_string())
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_error_display() {
92 let error = HttpError::Http {
93 code: 400,
94 message: "Bad Request".to_string(),
95 body: None,
96 };
97 assert_eq!(error.to_string(), "HTTP error 400: Bad Request");
98 }
99
100 #[test]
101 fn http_code_is_plain_u16() {
102 let error = HttpError::Http {
104 code: 404u16,
105 message: "Not Found".to_string(),
106 body: None,
107 };
108 assert_eq!(error.to_string(), "HTTP error 404: Not Found");
109 }
110
111 #[test]
112 fn io_error_converts_to_io_variant() {
113 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
114 let http_err = HttpError::from(io_err);
115 assert!(matches!(http_err, HttpError::Io(_)));
116 assert_eq!(http_err.to_string(), "IO error: file not found");
117 }
118
119 #[test]
120 fn serde_json_error_converts_to_json_variant() {
121 let json_err: serde_json::Error =
122 serde_json::from_str::<serde_json::Value>("{bad}").unwrap_err();
123 let http_err = HttpError::from(json_err);
124 assert!(matches!(http_err, HttpError::Json(_)));
125 }
126
127 #[test]
128 fn url_parse_error_converts_to_url_variant() {
129 let url_err = url::Url::parse("not a url").unwrap_err();
130 let http_err = HttpError::from(url_err);
131 assert!(matches!(http_err, HttpError::Url(_)));
132 }
133
134 #[test]
135 fn serde_qs_error_converts_to_json_variant() {
136 let qs_err: serde_qs::Error =
137 serde_qs::from_str::<std::collections::HashMap<String, String>>("%bad%").unwrap_err();
138 let http_err = HttpError::from(qs_err);
139 assert!(matches!(http_err, HttpError::Json(_)));
140 }
141}