mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 08:12:24 +00:00
Massive refactoring how DAV resources and routes work
This commit is contained in:
@@ -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],
|
||||
|
||||
@@ -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>))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user