1use std::fmt;
4use std::io;
5
6use crate::parse::ParseError;
7
8#[derive(Debug)]
10pub enum HttpError {
11 Io(io::Error),
12 Parse(ParseError),
13 InvalidUrl(String),
14 Timeout,
15 TooManyRedirects,
16 Redirect(String),
19 Proxy(String),
21 BodyRead,
22 Mime(String),
23 Tls(String),
24}
25
26impl fmt::Display for HttpError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 Self::Io(e) => write!(f, "io error: {e}"),
30 Self::Parse(e) => write!(f, "parse error: {e}"),
31 Self::InvalidUrl(s) => write!(f, "invalid URL: {s}"),
32 Self::Timeout => write!(f, "request timed out"),
33 Self::TooManyRedirects => write!(f, "too many redirects"),
34 Self::Redirect(s) => write!(f, "redirect blocked: {s}"),
35 Self::Proxy(s) => write!(f, "proxy error: {s}"),
36 Self::BodyRead => write!(f, "error reading body"),
37 Self::Mime(s) => write!(f, "mime error: {s}"),
38 Self::Tls(s) => write!(f, "TLS error: {s}"),
39 }
40 }
41}
42
43impl std::error::Error for HttpError {
44 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
45 match self {
46 Self::Io(e) => Some(e),
47 Self::Parse(e) => Some(e),
48 _ => None,
49 }
50 }
51}
52
53impl From<io::Error> for HttpError {
54 fn from(e: io::Error) -> Self {
55 Self::Io(e)
56 }
57}
58
59impl From<ParseError> for HttpError {
60 fn from(e: ParseError) -> Self {
61 Self::Parse(e)
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::error::Error;
69
70 #[test]
71 fn display_for_each_variant() {
72 let io_err = HttpError::Io(io::Error::other("boom"));
73 assert!(io_err.to_string().contains("io error"));
74 assert!(io_err.to_string().contains("boom"));
75
76 let parse = HttpError::Parse(ParseError::BadRequestLine);
77 assert!(parse.to_string().contains("parse error"));
78
79 assert!(HttpError::InvalidUrl("bad".into()).to_string().contains("invalid URL: bad"));
80 assert_eq!(HttpError::Timeout.to_string(), "request timed out");
81 assert_eq!(HttpError::TooManyRedirects.to_string(), "too many redirects");
82 assert_eq!(HttpError::BodyRead.to_string(), "error reading body");
83 assert!(HttpError::Mime("m".into()).to_string().contains("mime error: m"));
84 assert!(HttpError::Tls("t".into()).to_string().contains("TLS error: t"));
85 }
86
87 #[test]
88 fn source_is_set_for_wrapped_errors() {
89 let io_err = HttpError::Io(io::Error::other("x"));
90 assert!(io_err.source().is_some());
91
92 let parse = HttpError::Parse(ParseError::UnexpectedEof);
93 assert!(parse.source().is_some());
94
95 assert!(HttpError::Timeout.source().is_none());
97 assert!(HttpError::BodyRead.source().is_none());
98 }
99
100 #[test]
101 fn from_conversions() {
102 let from_io: HttpError = io::Error::new(io::ErrorKind::NotFound, "nf").into();
103 assert!(matches!(from_io, HttpError::Io(_)));
104
105 let from_parse: HttpError = ParseError::BadStatusLine.into();
106 assert!(matches!(from_parse, HttpError::Parse(_)));
107 }
108}