Skip to main content

base_http1/
error.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use base_scheduler::{Cancelled, OperationError};
5use std::{fmt, io};
6
7/// An HTTP/1 failure. Variants wrapping a cause describe this layer and report the cause as their source.
8#[derive(Debug)]
9pub enum Error {
10    Io(io::Error),
11    Cancelled,
12    Invalid(&'static str),
13    Limit(&'static str),
14    Truncated,
15    LengthMismatch,
16    Body(Box<dyn std::error::Error + Send + Sync>),
17}
18
19impl fmt::Display for Error {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::Io(_) => f.write_str("HTTP/1 connection I/O failed"),
23            Self::Cancelled => f.write_str("HTTP exchange cancelled"),
24            Self::Invalid(reason) => write!(f, "invalid HTTP/1 message: {reason}"),
25            Self::Limit(reason) => write!(f, "HTTP/1 limit exceeded: {reason}"),
26            Self::Truncated => f.write_str("truncated HTTP/1 message"),
27            Self::LengthMismatch => f.write_str("body does not match its declared length"),
28            Self::Body(_) => f.write_str("HTTP/1 body failed"),
29        }
30    }
31}
32impl std::error::Error for Error {
33    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
34        match self {
35            Self::Io(error) => Some(error),
36            Self::Body(error) => Some(error.as_ref()),
37            _ => None,
38        }
39    }
40}
41impl From<io::Error> for Error {
42    fn from(error: io::Error) -> Self {
43        Self::Io(error)
44    }
45}
46impl From<Cancelled> for Error {
47    fn from(_: Cancelled) -> Self {
48        Self::Cancelled
49    }
50}
51impl From<OperationError> for Error {
52    fn from(error: OperationError) -> Self {
53        match error {
54            OperationError::Cancelled(_) => Self::Cancelled,
55            OperationError::Io(error) => Self::Io(error),
56        }
57    }
58}
59impl From<base_http::FieldError> for Error {
60    fn from(error: base_http::FieldError) -> Self {
61        Self::Body(Box::new(error))
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use std::error::Error as _;
69
70    #[test]
71    fn wrapping_variants_describe_their_own_layer() {
72        let error = Error::Io(io::Error::from(io::ErrorKind::ConnectionRefused));
73        assert_eq!(error.to_string(), "HTTP/1 connection I/O failed");
74        assert_eq!(error.source().unwrap().to_string(), "connection refused");
75
76        let error = Error::from(base_http::FieldError::InvalidTrailer("host".to_owned()));
77        assert_eq!(error.to_string(), "HTTP/1 body failed");
78        assert!(error.source().is_some());
79    }
80
81    #[test]
82    fn leaf_variants_describe_the_failure_without_a_source() {
83        for (error, message) in [
84            (Error::Cancelled, "HTTP exchange cancelled"),
85            (
86                Error::Invalid("missing host"),
87                "invalid HTTP/1 message: missing host",
88            ),
89            (
90                Error::Limit("message head"),
91                "HTTP/1 limit exceeded: message head",
92            ),
93            (Error::Truncated, "truncated HTTP/1 message"),
94            (
95                Error::LengthMismatch,
96                "body does not match its declared length",
97            ),
98        ] {
99            assert_eq!(error.to_string(), message);
100            assert!(error.source().is_none());
101        }
102    }
103}