Skip to main content

go_http/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::fmt;
4use std::io;
5
6use crate::parse::ParseError;
7
8/// Top-level error type for go-http.
9#[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        // Variants without an inner cause return None.
89        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}