http_file/
error.rs

1use core::fmt;
2
3use std::{error, io};
4
5use http::{
6    header::{ALLOW, CONTENT_RANGE},
7    request::Parts,
8    HeaderValue, Request, Response, StatusCode,
9};
10
11use super::buf::buf_write_header;
12
13/// high level error types for serving file.
14/// see [into_response_from] and [into_response] for way of converting error to [Response] type.
15///
16/// [into_response_from]: ServeError::into_response_from
17/// [into_response]: ServeError::into_response
18#[derive(Debug)]
19pub enum ServeError {
20    /// request method is not allowed. only GET/HEAD methods are allowed.
21    MethodNotAllowed,
22    /// requested file path is invalid.
23    InvalidPath,
24    /// requested file has not been modified.
25    NotModified,
26    /// requested file has been modified before given precondition time.
27    PreconditionFailed,
28    /// requested file range is not satisfied. u64 is the max range of file.
29    RangeNotSatisfied(u64),
30    /// can not find requested file.
31    NotFound,
32    /// I/O error from file system.
33    Io(io::Error),
34}
35
36impl ServeError {
37    /// produce a response from error. an existing request is received for inherent it's extension
38    /// data and reuse it's heap allocation.
39    ///
40    /// # Examples
41    /// ```rust
42    /// # use http::Request;
43    /// # use http_file::ServeError;
44    /// #[derive(Clone)]
45    /// struct User;
46    ///
47    /// let mut req = Request::new(());
48    /// req.extensions_mut().insert(User); // data type were inserted into request extension.
49    ///
50    /// let mut res = ServeError::NotFound.into_response_from(req);
51    ///
52    /// res.extensions_mut().remove::<User>().unwrap(); // data type is moved to response.
53    /// ```
54    pub fn into_response_from<Ext>(self, req: Request<Ext>) -> Response<()> {
55        let (
56            Parts {
57                mut headers,
58                extensions,
59                ..
60            },
61            _,
62        ) = req.into_parts();
63        headers.clear();
64
65        let mut res = Response::new(());
66        *res.headers_mut() = headers;
67        *res.extensions_mut() = extensions;
68
69        self._into_response(res)
70    }
71
72    /// produce a response from error.
73    #[inline]
74    pub fn into_response(self) -> Response<()> {
75        self._into_response(Response::new(()))
76    }
77
78    fn _into_response(self, mut res: Response<()>) -> Response<()> {
79        match self {
80            Self::MethodNotAllowed => {
81                *res.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
82                res.headers_mut().insert(ALLOW, HeaderValue::from_static("GET,HEAD"));
83            }
84            Self::InvalidPath => *res.status_mut() = StatusCode::BAD_REQUEST,
85            Self::NotModified => *res.status_mut() = StatusCode::NOT_MODIFIED,
86            Self::PreconditionFailed => *res.status_mut() = StatusCode::PRECONDITION_FAILED,
87            Self::RangeNotSatisfied(size) => {
88                *res.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
89                let val = buf_write_header!(0, "bytes */{size}");
90                res.headers_mut().insert(CONTENT_RANGE, val);
91            }
92            Self::NotFound => *res.status_mut() = StatusCode::NOT_FOUND,
93            Self::Io(_) => *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR,
94        }
95        res
96    }
97}
98
99impl fmt::Display for ServeError {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        match *self {
102            Self::MethodNotAllowed => f.write_str("request method not allowed"),
103            Self::InvalidPath => f.write_str("file path is not valid"),
104            Self::NotModified => f.write_str("file has not been modified"),
105            Self::PreconditionFailed => f.write_str("precondition failed. file has been modified"),
106            Self::RangeNotSatisfied(size) => write!(f, "range is out of bound. max range of file is {size}"),
107            Self::NotFound => f.write_str("file can not be found"),
108            Self::Io(ref e) => fmt::Display::fmt(e, f),
109        }
110    }
111}
112
113impl error::Error for ServeError {}
114
115impl From<io::Error> for ServeError {
116    fn from(e: io::Error) -> Self {
117        match e.kind() {
118            io::ErrorKind::NotFound => Self::NotFound,
119            io::ErrorKind::PermissionDenied => Self::InvalidPath,
120            _ => Self::Io(e),
121        }
122    }
123}
124
125#[cfg(test)]
126mod test {
127    use super::*;
128
129    #[test]
130    fn response() {
131        assert_eq!(
132            ServeError::RangeNotSatisfied(128)
133                .into_response()
134                .headers_mut()
135                .remove(CONTENT_RANGE)
136                .unwrap(),
137            HeaderValue::from_static("bytes */128")
138        )
139    }
140}