Skip to main content

base_http/body/
error.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use base_scheduler::Cancelled;
5use std::{any::Any, error, fmt, sync::Arc};
6
7/// Failures shared by writable bodies and their producers. `Failed` reports the producer's error as its source, so callers can downcast it.
8#[derive(Clone, Debug)]
9pub enum BodyError {
10    Cancelled,
11    Closed,
12    Abandoned,
13    InvalidFrame,
14    ChunkTooLarge { length: usize, limit: usize },
15    Failed(Arc<dyn error::Error + Send + Sync>),
16}
17
18impl BodyError {
19    /// Wrap a producer failure. A `BodyError` is returned unchanged, so a failure relayed through several bodies is reported once.
20    pub fn new<E: error::Error + Send + Sync + 'static>(error: E) -> Self {
21        let mut error = Some(error);
22        if let Some(body) = (&mut error as &mut dyn Any).downcast_mut::<Option<Self>>() {
23            return body.take().unwrap();
24        }
25        Self::Failed(Arc::new(error.unwrap()))
26    }
27}
28
29impl From<Cancelled> for BodyError {
30    fn from(_: Cancelled) -> Self {
31        Self::Cancelled
32    }
33}
34
35impl From<std::io::Error> for BodyError {
36    fn from(error: std::io::Error) -> Self {
37        Self::new(error)
38    }
39}
40
41impl fmt::Display for BodyError {
42    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::Cancelled => formatter.write_str("body operation cancelled"),
45            Self::Closed => formatter.write_str("body output is closed"),
46            Self::Abandoned => formatter.write_str("body abandoned before completion"),
47            Self::InvalidFrame => {
48                formatter.write_str("body data or headers after trailing headers")
49            }
50            Self::ChunkTooLarge { length, limit } => write!(
51                formatter,
52                "body frame has {length} bytes, exceeding queue limit {limit}"
53            ),
54            Self::Failed(_) => formatter.write_str("body failed"),
55        }
56    }
57}
58
59impl error::Error for BodyError {
60    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
61        match self {
62            Self::Failed(error) => Some(error.as_ref()),
63            _ => None,
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use std::{error::Error as _, io};
72
73    #[test]
74    fn failed_reports_the_producer_error_as_its_source() {
75        let error = BodyError::new(io::Error::from(io::ErrorKind::TimedOut));
76        assert_eq!(error.to_string(), "body failed");
77        assert_eq!(error.source().unwrap().to_string(), "timed out");
78    }
79
80    #[test]
81    fn wrapping_a_body_error_returns_it_unchanged() {
82        let error = BodyError::new(BodyError::new(io::Error::other("producer failed")));
83        assert_eq!(error.to_string(), "body failed");
84        assert_eq!(error.source().unwrap().to_string(), "producer failed");
85        assert_eq!(
86            BodyError::new(BodyError::Closed).to_string(),
87            "body output is closed"
88        );
89    }
90}