rustical_dav/resource/
axum_service.rs

1use super::methods::{axum_route_propfind, axum_route_proppatch};
2use crate::resource::{
3    ResourceService,
4    axum_methods::AxumMethods,
5    methods::{axum_route_copy, axum_route_move},
6};
7use axum::{
8    body::Body,
9    extract::FromRequestParts,
10    handler::Handler,
11    http::{Request, Response},
12    response::IntoResponse,
13};
14use futures_util::future::BoxFuture;
15use headers::HeaderMapExt;
16use http::{HeaderValue, StatusCode};
17use std::convert::Infallible;
18use tower::Service;
19
20#[derive(Clone)]
21pub struct AxumService<RS: ResourceService + AxumMethods> {
22    resource_service: RS,
23}
24
25impl<RS: ResourceService + AxumMethods> AxumService<RS> {
26    pub fn new(resource_service: RS) -> Self {
27        Self { resource_service }
28    }
29}
30
31impl<RS: ResourceService + AxumMethods + Clone + Send + Sync> Service<Request<Body>>
32    for AxumService<RS>
33where
34    RS::Error: IntoResponse + Send + Sync + 'static,
35    RS::Principal: FromRequestParts<RS>,
36{
37    type Error = Infallible;
38    type Response = Response<Body>;
39    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
40
41    #[inline]
42    fn poll_ready(
43        &mut self,
44        _cx: &mut std::task::Context<'_>,
45    ) -> std::task::Poll<Result<(), Self::Error>> {
46        Ok(()).into()
47    }
48
49    #[inline]
50    fn call(&mut self, req: Request<Body>) -> Self::Future {
51        use crate::resource::methods::axum_route_delete;
52        let mut propfind_service =
53            Handler::with_state(axum_route_propfind::<RS>, self.resource_service.clone());
54        let mut proppatch_service =
55            Handler::with_state(axum_route_proppatch::<RS>, self.resource_service.clone());
56        let mut delete_service =
57            Handler::with_state(axum_route_delete::<RS>, self.resource_service.clone());
58        let mut move_service =
59            Handler::with_state(axum_route_move::<RS>, self.resource_service.clone());
60        let mut copy_service =
61            Handler::with_state(axum_route_copy::<RS>, self.resource_service.clone());
62        let mut options_service = Handler::with_state(route_options::<RS>, ());
63        match req.method().as_str() {
64            "PROPFIND" => return Box::pin(Service::call(&mut propfind_service, req)),
65            "PROPPATCH" => return Box::pin(Service::call(&mut proppatch_service, req)),
66            "DELETE" => return Box::pin(Service::call(&mut delete_service, req)),
67            "OPTIONS" => return Box::pin(Service::call(&mut options_service, req)),
68            "MOVE" => return Box::pin(Service::call(&mut move_service, req)),
69            "COPY" => return Box::pin(Service::call(&mut copy_service, req)),
70            "REPORT" => {
71                if let Some(svc) = RS::report() {
72                    return svc(self.resource_service.clone(), req);
73                }
74            }
75            "GET" | "HEAD" => {
76                if let Some(svc) = RS::get() {
77                    return svc(self.resource_service.clone(), req);
78                }
79            }
80            "POST" => {
81                if let Some(svc) = RS::post() {
82                    return svc(self.resource_service.clone(), req);
83                }
84            }
85            "MKCOL" => {
86                if let Some(svc) = RS::mkcol() {
87                    return svc(self.resource_service.clone(), req);
88                }
89            }
90            "MKCALENDAR" => {
91                if let Some(svc) = RS::mkcalendar() {
92                    return svc(self.resource_service.clone(), req);
93                }
94            }
95            "PUT" => {
96                if let Some(svc) = RS::put() {
97                    return svc(self.resource_service.clone(), req);
98                }
99            }
100            _ => {}
101        };
102        Box::pin(async move {
103            Ok(Response::builder()
104                .status(StatusCode::METHOD_NOT_ALLOWED)
105                .body(Body::from("Method not allowed"))
106                .unwrap())
107        })
108    }
109}
110
111async fn route_options<RS: ResourceService + AxumMethods>() -> Response<Body> {
112    // Semantically NO_CONTENT would also make sense,
113    // but GNOME Accounts only works when returning OK
114    // https://gitlab.gnome.org/GNOME/gnome-online-accounts/-/blob/master/src/goabackend/goadavclient.c#L289
115    let mut resp = Response::builder().status(StatusCode::OK);
116    let headers = resp.headers_mut().unwrap();
117    headers.insert("DAV", HeaderValue::from_static(RS::DAV_HEADER));
118    headers.typed_insert(RS::allow_header());
119    resp.body(Body::empty()).unwrap()
120}