Massive refactoring how DAV resources and routes work

This commit is contained in:
Lennart
2024-10-03 23:02:13 +02:00
parent 9c8c05eaca
commit a47d056df0
15 changed files with 218 additions and 162 deletions

View File

@@ -1,12 +1,11 @@
use crate::depth_extractor::Depth;
use crate::resource::HandlePropfind;
use crate::resource::Resource;
use crate::resource::ResourceService;
use crate::xml::multistatus::PropstatWrapper;
use crate::xml::MultistatusElement;
use crate::xml::TagList;
use crate::Error;
use actix_web::web::{Data, Path};
use actix_web::web::Path;
use actix_web::HttpRequest;
use derive_more::derive::Deref;
use log::debug;
@@ -43,7 +42,6 @@ pub async fn route_propfind<R: ResourceService>(
path_components: Path<R::PathComponents>,
body: String,
req: HttpRequest,
prefix: Data<ServicePrefix>,
user: User,
depth: Depth,
) -> Result<
@@ -54,7 +52,6 @@ pub async fn route_propfind<R: ResourceService>(
R::Error,
> {
debug!("{body}");
let prefix = prefix.into_inner();
let resource_service = R::new(&req, path_components.into_inner()).await?;
@@ -81,13 +78,19 @@ pub async fn route_propfind<R: ResourceService>(
let mut member_responses = Vec::new();
if depth != Depth::Zero {
for (path, member) in resource_service.get_members().await? {
member_responses.push(member.propfind(&prefix, &path, props.clone()).await?);
for (path, member) in resource_service.get_members(req.resource_map()).await? {
member_responses.push(
member
.propfind(&path, props.clone(), req.resource_map())
.await?,
);
}
}
let resource = resource_service.get_resource(user.id).await?;
let response = resource.propfind(&prefix, req.path(), props).await?;
let response = resource
.propfind(req.path(), props, req.resource_map())
.await?;
Ok(MultistatusElement {
responses: vec![response],

View File

@@ -1,6 +1,12 @@
use crate::methods::{route_propfind, route_proppatch};
use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper};
use crate::xml::{multistatus::ResponseElement, TagList};
use crate::Error;
use actix_web::dev::ResourceMap;
use actix_web::error::UrlGenerationError;
use actix_web::http::Method;
use actix_web::test::TestRequest;
use actix_web::web;
use actix_web::{http::StatusCode, HttpRequest, ResponseError};
use async_trait::async_trait;
use core::fmt;
@@ -18,7 +24,8 @@ pub trait Resource: Clone {
Self::PropName::VARIANTS
}
fn get_prop(&self, prefix: &str, prop: Self::PropName) -> Result<Self::Prop, Self::Error>;
fn get_prop(&self, rmap: &ResourceMap, prop: Self::PropName)
-> Result<Self::Prop, Self::Error>;
fn set_prop(&mut self, _prop: Self::Prop) -> Result<(), crate::Error> {
Err(crate::Error::PropReadOnly)
@@ -27,61 +34,31 @@ pub trait Resource: Clone {
fn remove_prop(&mut self, _prop: Self::PropName) -> Result<(), crate::Error> {
Err(crate::Error::PropReadOnly)
}
}
pub trait InvalidProperty {
fn invalid_property(&self) -> bool;
}
fn resource_name() -> &'static str;
// A resource is identified by a URI and has properties
// A resource can also be a collection
// A resource cannot be none, only Methods like PROPFIND, GET, REPORT, etc. can be exposed
// A resource exists
#[async_trait(?Send)]
pub trait ResourceService: Sized {
type MemberType: Resource<Error = Self::Error>;
type PathComponents: Sized + Clone; // defines how the resource URI maps to parameters, i.e. /{principal}/{calendar} -> (String, String)
type Resource: Resource<Error = Self::Error>;
type Error: ResponseError + From<crate::Error> + From<anyhow::Error>;
async fn new(
req: &HttpRequest,
path_components: Self::PathComponents,
) -> Result<Self, Self::Error>;
async fn get_members(&self) -> Result<Vec<(String, Self::MemberType)>, Self::Error> {
Ok(vec![])
fn get_url<U, I>(rmap: &ResourceMap, elements: U) -> Result<String, UrlGenerationError>
where
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
Ok(rmap
.url_for(
&TestRequest::default().to_http_request(),
Self::resource_name(),
elements,
)?
.path()
.to_owned())
}
async fn get_resource(&self, principal: String) -> Result<Self::Resource, Self::Error>;
async fn save_resource(&self, file: Self::Resource) -> Result<(), Self::Error>;
async fn delete_resource(&self, _use_trashbin: bool) -> Result<(), Self::Error> {
Err(crate::Error::Unauthorized.into())
}
}
#[async_trait(?Send)]
pub trait HandlePropfind {
type Error: ResponseError + From<crate::Error> + From<anyhow::Error>;
#[allow(async_fn_in_trait)]
async fn propfind(
&self,
prefix: &str,
path: &str,
props: Vec<&str>,
) -> Result<impl Serialize, Self::Error>;
}
#[async_trait(?Send)]
impl<R: Resource> HandlePropfind for R {
type Error = R::Error;
async fn propfind(
&self,
prefix: &str,
path: &str,
mut props: Vec<&str>,
) -> Result<ResponseElement<PropstatWrapper<R::Prop>>, R::Error> {
rmap: &ResourceMap,
) -> Result<ResponseElement<PropstatWrapper<Self::Prop>>, Self::Error> {
if props.contains(&"propname") {
if props.len() != 1 {
// propname MUST be the only queried prop per spec
@@ -89,7 +66,7 @@ impl<R: Resource> HandlePropfind for R {
Error::BadRequest("propname MUST be the only queried prop".to_owned()).into(),
);
}
let props: Vec<String> = R::list_props()
let props: Vec<String> = Self::list_props()
.iter()
.map(|&prop| prop.to_string())
.collect();
@@ -109,26 +86,26 @@ impl<R: Resource> HandlePropfind for R {
Error::BadRequest("allprop MUST be the only queried prop".to_owned()).into(),
);
}
props = R::list_props().into();
props = Self::list_props().into();
}
let (valid_props, invalid_props): (Vec<Option<R::PropName>>, Vec<Option<&str>>) = props
let (valid_props, invalid_props): (Vec<Option<Self::PropName>>, Vec<Option<&str>>) = props
.into_iter()
.map(|prop| {
if let Ok(valid_prop) = R::PropName::from_str(prop) {
if let Ok(valid_prop) = Self::PropName::from_str(prop) {
(Some(valid_prop), None)
} else {
(None, Some(prop))
}
})
.unzip();
let valid_props: Vec<R::PropName> = valid_props.into_iter().flatten().collect();
let valid_props: Vec<Self::PropName> = valid_props.into_iter().flatten().collect();
let invalid_props: Vec<&str> = invalid_props.into_iter().flatten().collect();
let prop_responses = valid_props
.into_iter()
.map(|prop| self.get_prop(prefix, prop))
.collect::<Result<Vec<R::Prop>, R::Error>>()?;
.map(|prop| self.get_prop(rmap, prop))
.collect::<Result<Vec<Self::Prop>, Self::Error>>()?;
let mut propstats = vec![PropstatWrapper::Normal(PropstatElement {
status: format!("HTTP/1.1 {}", StatusCode::OK),
@@ -153,3 +130,51 @@ impl<R: Resource> HandlePropfind for R {
})
}
}
pub trait InvalidProperty {
fn invalid_property(&self) -> bool;
}
// A resource is identified by a URI and has properties
// A resource can also be a collection
// A resource cannot be none, only Methods like PROPFIND, GET, REPORT, etc. can be exposed
// A resource exists
#[async_trait(?Send)]
pub trait ResourceService: Sized + 'static {
type MemberType: Resource<Error = Self::Error>;
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<Error = Self::Error>;
type Error: ResponseError + From<crate::Error> + From<anyhow::Error>;
async fn new(
req: &HttpRequest,
path_components: Self::PathComponents,
) -> Result<Self, Self::Error>;
async fn get_members(
&self,
_rmap: &ResourceMap,
) -> Result<Vec<(String, Self::MemberType)>, Self::Error> {
Ok(vec![])
}
async fn get_resource(&self, principal: String) -> Result<Self::Resource, Self::Error>;
async fn save_resource(&self, file: Self::Resource) -> Result<(), Self::Error>;
async fn delete_resource(&self, _use_trashbin: bool) -> Result<(), Self::Error> {
Err(crate::Error::Unauthorized.into())
}
fn resource_name() -> &'static str {
Self::Resource::resource_name()
}
fn actix_resource() -> actix_web::Resource {
let propfind_method = || web::method(Method::from_str("PROPFIND").unwrap());
let proppatch_method = || web::method(Method::from_str("PROPPATCH").unwrap());
web::resource("")
.name(Self::resource_name())
.route(propfind_method().to(route_propfind::<Self>))
.route(proppatch_method().to(route_proppatch::<Self>))
}
}