1use axum::{
2 body::Body,
3 response::{IntoResponse, Response},
4};
5use headers::{ContentType, HeaderMapExt};
6use http::StatusCode;
7use rustical_xml::{XmlSerialize, XmlSerializeRoot};
8use tracing::error;
9
10#[derive(Debug, thiserror::Error, XmlSerialize)]
11pub enum Precondition {
12 #[error("valid-calendar-data")]
13 #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
14 ValidCalendarData,
15 #[error("calendar-timezone error: {0}")]
16 #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
17 CalendarTimezone(&'static str),
18}
19
20impl IntoResponse for Precondition {
21 fn into_response(self) -> axum::response::Response {
22 let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
23 let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
24
25 let error = rustical_dav::xml::ErrorElement(&self);
26 if error.serialize_root(&mut writer).is_err() {
27 return (
29 StatusCode::INTERNAL_SERVER_ERROR,
30 "IO error when serialising output",
31 )
32 .into_response();
33 }
34 let mut res = Response::builder().status(StatusCode::FORBIDDEN);
35 res.headers_mut().unwrap().typed_insert(ContentType::xml());
36 res.body(Body::from(output)).unwrap()
37 }
38}
39
40#[derive(Debug, thiserror::Error)]
41pub enum Error {
42 #[error("Unauthorized")]
43 Unauthorized,
44
45 #[error("Not Found")]
46 NotFound,
47
48 #[error("Not implemented")]
49 NotImplemented,
50
51 #[error(transparent)]
52 StoreError(#[from] rustical_store::Error),
53
54 #[error(transparent)]
55 ChronoParseError(#[from] chrono::ParseError),
56
57 #[error(transparent)]
58 DavError(#[from] rustical_dav::Error),
59
60 #[error(transparent)]
61 XmlDecodeError(#[from] rustical_xml::XmlError),
62
63 #[error(transparent)]
64 PreconditionFailed(Precondition),
65}
66
67impl Error {
68 #[must_use]
69 pub fn status_code(&self) -> StatusCode {
70 match self {
71 Self::StoreError(err) => match err {
72 rustical_store::Error::NotFound => StatusCode::NOT_FOUND,
73 rustical_store::Error::AlreadyExists => StatusCode::CONFLICT,
74 rustical_store::Error::ReadOnly => StatusCode::FORBIDDEN,
75 _ => StatusCode::INTERNAL_SERVER_ERROR,
76 },
77 Self::DavError(err) => StatusCode::try_from(err.status_code().as_u16())
78 .expect("Just converting between versions"),
79 Self::Unauthorized => StatusCode::UNAUTHORIZED,
80 Self::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
81 Self::ChronoParseError(_) | Self::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
82 Self::NotFound => StatusCode::NOT_FOUND,
83 Self::PreconditionFailed(_err) => StatusCode::FORBIDDEN,
87 }
88 }
89}
90
91impl IntoResponse for Error {
92 fn into_response(self) -> axum::response::Response {
93 if let Self::PreconditionFailed(precondition) = self {
94 return precondition.into_response();
95 }
96 if matches!(self.status_code(), StatusCode::INTERNAL_SERVER_ERROR) {
97 error!("{self}");
98 }
99 (self.status_code(), self.to_string()).into_response()
100 }
101}