Skip to main content

base_http_content/
read_error.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use base_http::Status;
5use base_web_content::Rejection;
6use std::{error::Error, fmt};
7
8/// Why a request's content was not read, with any failures to remove what was stored of its uploads.
9///
10/// Neither its `Display` nor its `Debug` output includes submitted content, filenames, or media types, so it is safe to log, although the body and store errors it holds are their own types', with their own guarantees.
11#[derive(Debug)]
12#[non_exhaustive]
13pub struct ReadError<B, S> {
14    /// What went wrong.
15    pub kind: ReadErrorKind<B, S>,
16    /// The failures to remove what was stored of uploads, while cleaning up after `kind`. Empty when cleanup succeeded or nothing was stored.
17    pub cleanup: Vec<S>,
18}
19
20/// The kind of [`ReadError`], where `B` is the body's error and `S` the [`UploadStore`](crate::UploadStore)'s.
21#[derive(Debug)]
22#[non_exhaustive]
23pub enum ReadErrorKind<B, S> {
24    /// The request has more than one `Content-Type` field, or one that is not a valid media type. The body was not read. 400 Bad Request.
25    InvalidMediaType,
26    /// The request's length exceeds the largest body the declaration could need in its format. The body was not read. 413 Content Too Large.
27    ContentTooLarge {
28        /// The request's length, from its body or its `Content-Length`.
29        length: u64,
30        /// The largest body the declaration could need, [`Content::body_size_limit`](base_web_content::Content::body_size_limit).
31        limit: u64,
32    },
33    /// The content was refused: a fatal error, whose own status is 400, 413, or 415, or violations, which are 422 Unprocessable Content.
34    Rejected(Rejection),
35    /// Reading the request's body failed, including when its read was cancelled. 400 Bad Request, although after cancellation, such as when the server is shutting down, there is usually no one to respond to.
36    Body(B),
37    /// Storing an upload failed, including when a store operation was cancelled. 500 Internal Server Error.
38    Store(S),
39}
40
41impl<B, S> ReadError<B, S> {
42    pub(crate) fn new(kind: ReadErrorKind<B, S>) -> Self {
43        Self {
44            kind,
45            cleanup: Vec::new(),
46        }
47    }
48
49    /// The status a server should respond with.
50    pub fn status(&self) -> Status {
51        match &self.kind {
52            ReadErrorKind::InvalidMediaType | ReadErrorKind::Body(_) => Status::BAD_REQUEST,
53            ReadErrorKind::ContentTooLarge { .. } => Status::CONTENT_TOO_LARGE,
54            ReadErrorKind::Rejected(Rejection::Error(error)) => {
55                Status::new(error.status()).unwrap_or(Status::BAD_REQUEST)
56            }
57            ReadErrorKind::Rejected(Rejection::Violations(_)) => Status::UNPROCESSABLE_CONTENT,
58            ReadErrorKind::Store(_) => Status::INTERNAL_SERVER_ERROR,
59        }
60    }
61}
62
63impl<B, S> fmt::Display for ReadError<B, S> {
64    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65        let described = match &self.kind {
66            ReadErrorKind::InvalidMediaType => {
67                formatter.write_str("request has an invalid Content-Type")
68            }
69            ReadErrorKind::ContentTooLarge { length, limit } => write!(
70                formatter,
71                "request length {length} exceeds the largest body its declaration accepts, {limit}"
72            ),
73            ReadErrorKind::Rejected(rejection) => rejection.fmt(formatter),
74            ReadErrorKind::Body(_) => formatter.write_str("failed to read the request body"),
75            ReadErrorKind::Store(_) => formatter.write_str("failed to store an upload"),
76        };
77        described.and_then(|()| match self.cleanup.len() {
78            0 => Ok(()),
79            1 => formatter.write_str(", and 1 failure to remove a stored upload"),
80            count => write!(formatter, ", and {count} failures to remove stored uploads"),
81        })
82    }
83}
84
85/// The body or store error, for `Body` and `Store`. Cleanup failures are in [`ReadError::cleanup`].
86impl<B: Error + 'static, S: Error + 'static> Error for ReadError<B, S> {
87    fn source(&self) -> Option<&(dyn Error + 'static)> {
88        match &self.kind {
89            ReadErrorKind::Body(error) => Some(error),
90            ReadErrorKind::Store(error) => Some(error),
91            _ => None,
92        }
93    }
94}