Skip to main content

rustical_dav/
error.rs

1use axum::body::Body;
2use http::StatusCode;
3use rustical_xml::XmlError;
4use thiserror::Error;
5use tracing::error;
6
7#[derive(Debug, Error)]
8pub enum Error {
9    #[error("Bad request: {0}")]
10    BadRequest(String),
11
12    #[error("Unauthorized")]
13    Unauthorized,
14
15    #[error("prop is read-only")]
16    PropReadOnly,
17
18    #[error(transparent)]
19    XmlError(#[from] rustical_xml::XmlError),
20
21    #[error("Precondition Failed")]
22    PreconditionFailed,
23
24    #[error("Forbidden")]
25    Forbidden,
26}
27
28impl Error {
29    #[must_use]
30    pub const fn status_code(&self) -> StatusCode {
31        match self {
32            Self::BadRequest(_) => StatusCode::BAD_REQUEST,
33            Self::Unauthorized => StatusCode::UNAUTHORIZED,
34            Self::XmlError(error) => match &error {
35                XmlError::InvalidTag(..)
36                | XmlError::MissingField(_)
37                | XmlError::UnsupportedEvent(_)
38                | XmlError::InvalidVariant(_)
39                | XmlError::InvalidFieldName(_, _)
40                | XmlError::InvalidValue(_) => StatusCode::UNPROCESSABLE_ENTITY,
41                _ => StatusCode::BAD_REQUEST,
42            },
43            Self::PropReadOnly => StatusCode::CONFLICT,
44            // The correct status code for a failed precondition is not PreconditionFailed but
45            // Forbidden (or Conflict):
46            // https://datatracker.ietf.org/doc/html/rfc4791#section-1.3
47            Self::PreconditionFailed | Self::Forbidden => StatusCode::FORBIDDEN,
48        }
49    }
50}
51
52impl axum::response::IntoResponse for Error {
53    fn into_response(self) -> axum::response::Response {
54        if matches!(self.status_code(), StatusCode::INTERNAL_SERVER_ERROR) {
55            error!("{self}");
56        }
57
58        let mut resp = axum::response::Response::builder().status(self.status_code());
59        if matches!(&self, &Self::Unauthorized) {
60            resp.headers_mut()
61                .expect("This must always work")
62                .insert("WWW-Authenticate", "Basic".parse().unwrap());
63        }
64
65        resp.body(Body::new(self.to_string()))
66            .expect("This should always work")
67    }
68}