diff --git a/crates/dav/src/error.rs b/crates/dav/src/error.rs index 4549168..e7b8e6c 100644 --- a/crates/dav/src/error.rs +++ b/crates/dav/src/error.rs @@ -1,4 +1,4 @@ -use actix_web::{HttpResponse, http::StatusCode}; +use http::StatusCode; use rustical_xml::XmlError; use thiserror::Error; use tracing::error; @@ -30,8 +30,8 @@ pub enum Error { PreconditionFailed, } -impl actix_web::error::ResponseError for Error { - fn status_code(&self) -> StatusCode { +impl Error { + pub fn status_code(&self) -> StatusCode { match self { Self::InternalError => StatusCode::INTERNAL_SERVER_ERROR, Self::NotFound => StatusCode::NOT_FOUND, @@ -51,14 +51,21 @@ impl actix_web::error::ResponseError for Error { Self::IOError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } +} - fn error_response(&self) -> HttpResponse { +#[cfg(feature = "actix")] +impl actix_web::error::ResponseError for Error { + fn status_code(&self) -> StatusCode { + self.status_code() + } + + fn error_response(&self) -> actix_web::HttpResponse { error!("Error: {self}"); match self { - Error::Unauthorized => HttpResponse::build(self.status_code()) + Error::Unauthorized => actix_web::HttpResponse::build(self.status_code()) .append_header(("WWW-Authenticate", "Basic")) .body(self.to_string()), - _ => HttpResponse::build(self.status_code()).body(self.to_string()), + _ => actix_web::HttpResponse::build(self.status_code()).body(self.to_string()), } } } diff --git a/crates/dav/src/header/depth.rs b/crates/dav/src/header/depth.rs index d06f9d6..35ad1e2 100644 --- a/crates/dav/src/header/depth.rs +++ b/crates/dav/src/header/depth.rs @@ -1,5 +1,7 @@ -use actix_web::{FromRequest, HttpRequest, ResponseError, http::StatusCode}; +#[cfg(feature = "actix")] +use actix_web::{HttpRequest, ResponseError}; use futures_util::future::{Ready, err, ok}; +use http::StatusCode; use rustical_xml::{ValueDeserialize, ValueSerialize, XmlError}; use thiserror::Error; @@ -7,6 +9,7 @@ use thiserror::Error; #[error("Invalid Depth header")] pub struct InvalidDepthHeader; +#[cfg(feature = "actix")] impl ResponseError for InvalidDepthHeader { fn status_code(&self) -> actix_web::http::StatusCode { StatusCode::BAD_REQUEST @@ -57,7 +60,8 @@ impl TryFrom<&[u8]> for Depth { } } -impl FromRequest for Depth { +#[cfg(feature = "actix")] +impl actix_web::FromRequest for Depth { type Error = InvalidDepthHeader; type Future = Ready>; diff --git a/crates/dav/src/header/overwrite.rs b/crates/dav/src/header/overwrite.rs index e893700..c9d52ad 100644 --- a/crates/dav/src/header/overwrite.rs +++ b/crates/dav/src/header/overwrite.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "actix")] use actix_web::{FromRequest, HttpRequest, ResponseError, http::StatusCode}; use futures_util::future::{Ready, err, ok}; use thiserror::Error; @@ -6,6 +7,7 @@ use thiserror::Error; #[error("Invalid Overwrite header")] pub struct InvalidOverwriteHeader; +#[cfg(feature = "actix")] impl ResponseError for InvalidOverwriteHeader { fn status_code(&self) -> actix_web::http::StatusCode { StatusCode::BAD_REQUEST @@ -37,6 +39,7 @@ impl TryFrom<&[u8]> for Overwrite { } } +#[cfg(feature = "actix")] impl FromRequest for Overwrite { type Error = InvalidOverwriteHeader; type Future = Ready>; diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index a85b864..4bbfc08 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -7,9 +7,8 @@ pub mod resource; pub mod resources; pub mod xml; -use actix_web::FromRequest; pub use error::Error; -pub trait Principal: std::fmt::Debug + Clone + FromRequest + 'static { +pub trait Principal: std::fmt::Debug + Clone + 'static { fn get_id(&self) -> &str; } diff --git a/crates/dav/src/resource/methods/delete.rs b/crates/dav/src/resource/methods/delete.rs index 35954e0..a8a6ed3 100644 --- a/crates/dav/src/resource/methods/delete.rs +++ b/crates/dav/src/resource/methods/delete.rs @@ -69,7 +69,7 @@ pub async fn route_delete( if_match: Option, if_none_match: Option, ) -> Result<(), R::Error> { - let resource = resource_service.get_resource(&path_components).await?; + let resource = resource_service.get_resource(path_components).await?; let privileges = resource.get_user_privileges(&principal)?; if !privileges.has(&UserPrivilege::Write) { diff --git a/crates/dav/src/resource/methods/mod.rs b/crates/dav/src/resource/methods/mod.rs index 2b06cfb..eea604d 100644 --- a/crates/dav/src/resource/methods/mod.rs +++ b/crates/dav/src/resource/methods/mod.rs @@ -11,3 +11,6 @@ pub(crate) use delete::actix_route_delete; #[cfg(feature = "actix")] pub(crate) use propfind::actix_route_propfind; + +#[cfg(feature = "actix")] +pub(crate) use proppatch::actix_route_proppatch; diff --git a/crates/dav/src/resource/methods/proppatch.rs b/crates/dav/src/resource/methods/proppatch.rs index d3b4dcd..f12a7a7 100644 --- a/crates/dav/src/resource/methods/proppatch.rs +++ b/crates/dav/src/resource/methods/proppatch.rs @@ -5,9 +5,7 @@ use crate::resource::ResourceService; use crate::xml::MultistatusElement; use crate::xml::TagList; use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement}; -use actix_web::http::StatusCode; -use actix_web::web::Data; -use actix_web::{HttpRequest, web::Path}; +use http::StatusCode; use quick_xml::name::Namespace; use rustical_xml::EnumUnitVariants; use rustical_xml::Unparsed; @@ -63,23 +61,41 @@ enum Operation { #[xml(ns = "crate::namespace::NS_DAV")] struct PropertyupdateElement(#[xml(ty = "untagged", flatten)] Vec>); +#[cfg(feature = "actix")] #[instrument(parent = root_span.id(), skip(path, req, root_span, resource_service))] -pub(crate) async fn route_proppatch( - path: Path, +pub(crate) async fn actix_route_proppatch( + path: actix_web::web::Path, body: String, - req: HttpRequest, + req: actix_web::HttpRequest, principal: R::Principal, root_span: RootSpan, - resource_service: Data, + resource_service: actix_web::web::Data, ) -> Result, R::Error> { - let href = req.path().to_owned(); + route_proppatch( + &path.into_inner(), + req.path(), + body, + principal, + resource_service.as_ref(), + ) + .await +} + +pub(crate) async fn route_proppatch( + path_components: &R::PathComponents, + path: &str, + body: String, + principal: R::Principal, + resource_service: &R, +) -> Result, R::Error> { + let href = path.to_owned(); // Extract operations let PropertyupdateElement::::Prop>>( operations, ) = XmlDocument::parse_str(&body).map_err(Error::XmlError)?; - let mut resource = resource_service.get_resource(&path).await?; + let mut resource = resource_service.get_resource(path_components).await?; let privileges = resource.get_user_privileges(&principal)?; if !privileges.has(&UserPrivilege::Write) { return Err(Error::Unauthorized.into()); @@ -151,7 +167,9 @@ pub(crate) async fn route_proppatch( if props_not_found.is_empty() && props_conflict.is_empty() { // Only save if no errors occured - resource_service.save_resource(&path, resource).await?; + resource_service + .save_resource(path_components, resource) + .await?; } Ok(MultistatusElement { diff --git a/crates/dav/src/resource/resource_service.rs b/crates/dav/src/resource/resource_service.rs index 7a9b029..d3e3848 100644 --- a/crates/dav/src/resource/resource_service.rs +++ b/crates/dav/src/resource/resource_service.rs @@ -1,8 +1,9 @@ -use super::methods::{actix_route_delete, actix_route_propfind, route_proppatch}; +#[cfg(feature = "actix")] +use super::methods::{actix_route_delete, actix_route_propfind, actix_route_proppatch}; use super::{PrincipalUri, Resource}; use crate::Principal; -use actix_web::web::Data; -use actix_web::{ResponseError, http::Method, web}; +#[cfg(feature = "actix")] +use actix_web::{http::Method, web, web::Data}; use async_trait::async_trait; use serde::Deserialize; use std::str::FromStr; @@ -12,7 +13,7 @@ pub trait ResourceService: Sized + 'static { type MemberType: Resource; type PathComponents: for<'de> Deserialize<'de> + Sized + Clone + 'static; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String) type Resource: Resource; - type Error: ResponseError + From; + type Error: From; type Principal: Principal; type PrincipalUri: PrincipalUri; @@ -44,16 +45,28 @@ pub trait ResourceService: Sized + 'static { Err(crate::Error::Unauthorized.into()) } + #[cfg(feature = "actix")] #[inline] - fn actix_resource(self) -> actix_web::Resource { + fn actix_resource(self) -> actix_web::Resource + where + Self::Error: actix_web::ResponseError, + Self::Principal: actix_web::FromRequest, + { web::resource("") .app_data(Data::new(self)) .route( web::method(Method::from_str("PROPFIND").unwrap()).to(actix_route_propfind::), ) - .route(web::method(Method::from_str("PROPPATCH").unwrap()).to(route_proppatch::)) + .route( + web::method(Method::from_str("PROPPATCH").unwrap()) + .to(actix_route_proppatch::), + ) .delete(actix_route_delete::) } - fn actix_scope(self) -> actix_web::Scope; + #[cfg(feature = "actix")] + fn actix_scope(self) -> actix_web::Scope + where + Self::Error: actix_web::ResponseError, + Self::Principal: actix_web::FromRequest; } diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 0d0782a..c410cdf 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -5,7 +5,6 @@ use crate::extensions::{ use crate::privileges::UserPrivilegeSet; use crate::resource::{PrincipalUri, Resource, ResourceService}; use crate::xml::{Resourcetype, ResourcetypeInner}; -use actix_web::web; use async_trait::async_trait; use std::marker::PhantomData; @@ -74,8 +73,13 @@ impl + Clone, P: Principal, PURI: PrincipalU Ok(RootResource::::default()) } - fn actix_scope(self) -> actix_web::Scope { - web::scope("") + #[cfg(feature = "actix")] + fn actix_scope(self) -> actix_web::Scope + where + Self::Error: actix_web::ResponseError, + Self::Principal: actix_web::FromRequest, + { + actix_web::web::scope("") .service(self.0.clone().actix_scope()) .service(self.actix_resource()) } diff --git a/crates/dav/src/xml/multistatus.rs b/crates/dav/src/xml/multistatus.rs index 0db6edb..69fdd64 100644 --- a/crates/dav/src/xml/multistatus.rs +++ b/crates/dav/src/xml/multistatus.rs @@ -1,13 +1,12 @@ -use std::collections::HashMap; - use crate::xml::TagList; +#[cfg(feature = "actix")] use actix_web::{ - body::BoxBody, - http::{header::ContentType, StatusCode}, - HttpRequest, HttpResponse, Responder, ResponseError, + HttpRequest, HttpResponse, Responder, ResponseError, body::BoxBody, http::header::ContentType, }; +use http::StatusCode; use quick_xml::name::Namespace; use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot}; +use std::collections::HashMap; #[derive(XmlSerialize)] pub struct PropTagWrapper(#[xml(flatten, ty = "untagged")] pub Vec); @@ -109,6 +108,7 @@ impl Default for MultistatusElement } } +#[cfg(feature = "actix")] impl Responder for MultistatusElement { type Body = BoxBody;