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