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    /// A redirect policy (`Client.check_redirect`) aborted the request.
17    /// Port of the error returned by Go's `CheckRedirect`.
18    Redirect(String),
19    /// A proxy failure (e.g. a failed `CONNECT` tunnel, or a bad proxy URL).
20    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        // Variants without an inner cause return None.
96        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}