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")]
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 let Err(err) = error.serialize_root(&mut writer) {
27 return rustical_dav::Error::from(err).into_response();
28 }
29 let mut res = Response::builder().status(StatusCode::FORBIDDEN);
30 res.headers_mut().unwrap().typed_insert(ContentType::xml());
31 res.body(Body::from(output)).unwrap()
32 }
33}
34
35#[derive(Debug, thiserror::Error)]
36pub enum Error {
37 #[error("Unauthorized")]
38 Unauthorized,
39
40 #[error("Not Found")]
41 NotFound,
42
43 #[error("Not implemented")]
44 NotImplemented,
45
46 #[error(transparent)]
47 StoreError(#[from] rustical_store::Error),
48
49 #[error(transparent)]
50 ChronoParseError(#[from] chrono::ParseError),
51
52 #[error(transparent)]
53 DavError(#[from] rustical_dav::Error),
54
55 #[error(transparent)]
56 XmlDecodeError(#[from] rustical_xml::XmlError),
57
58 #[error(transparent)]
59 PreconditionFailed(Precondition),
60}
61
62impl Error {
63 #[must_use]
64 pub fn status_code(&self) -> StatusCode {
65 match self {
66 Self::StoreError(err) => match err {
67 rustical_store::Error::NotFound => StatusCode::NOT_FOUND,
68 rustical_store::Error::AlreadyExists => StatusCode::CONFLICT,
69 rustical_store::Error::ReadOnly => StatusCode::FORBIDDEN,
70 _ => StatusCode::INTERNAL_SERVER_ERROR,
71 },
72 Self::DavError(err) => StatusCode::try_from(err.status_code().as_u16())
73 .expect("Just converting between versions"),
74 Self::Unauthorized => StatusCode::UNAUTHORIZED,
75 Self::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
76 Self::ChronoParseError(_) | Self::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
77 Self::NotFound => StatusCode::NOT_FOUND,
78 Self::PreconditionFailed(_err) => StatusCode::FORBIDDEN,
82 }
83 }
84}
85
86impl IntoResponse for Error {
87 fn into_response(self) -> axum::response::Response {
88 if let Self::PreconditionFailed(precondition) = self {
89 return precondition.into_response();
90 }
91 if matches!(self.status_code(), StatusCode::INTERNAL_SERVER_ERROR) {
92 error!("{self}");
93 }
94 (self.status_code(), self.to_string()).into_response()
95 }
96}