Migrate propfind and report to rustical_xml

This commit is contained in:
Lennart
2024-12-23 16:44:26 +01:00
parent 8e0a25b223
commit 72844aa94e
28 changed files with 528 additions and 335 deletions

View File

@@ -25,3 +25,4 @@ url = { workspace = true }
rustical_dav = { workspace = true }
rustical_store = { workspace = true }
chrono = { workspace = true }
rustical_xml.workspace = true

View File

@@ -14,14 +14,14 @@ use rustical_dav::{
xml::{PropElement, PropfindType},
};
use rustical_store::{auth::User, AddressObject, AddressbookStore};
use serde::Deserialize;
use rustical_xml::XmlDeserialize;
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub struct AddressbookMultigetRequest {
#[serde(flatten)]
#[xml(ty = "untagged")]
prop: PropfindType,
#[xml(flatten)]
href: Vec<String>,
}
@@ -84,7 +84,10 @@ pub async fn handle_addressbook_multiget<AS: AddressbookStore + ?Sized>(
PropfindType::Propname => {
vec!["propname".to_owned()]
}
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags.into_inner(),
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags
.into_iter()
.map(|propname| propname.name)
.collect(),
};
let props: Vec<&str> = props.iter().map(String::as_str).collect();

View File

@@ -5,24 +5,15 @@ use actix_web::{
};
use addressbook_multiget::{handle_addressbook_multiget, AddressbookMultigetRequest};
use rustical_store::{auth::User, AddressbookStore};
use serde::{Deserialize, Serialize};
use rustical_xml::{XmlDeserialize, XmlDocument};
use sync_collection::{handle_sync_collection, SyncCollectionRequest};
use tracing::instrument;
mod addressbook_multiget;
mod sync_collection;
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum PropQuery {
Allprop,
Prop,
Propname,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum ReportRequest {
#[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)]
pub(crate) enum ReportRequest {
AddressbookMultiget(AddressbookMultigetRequest),
SyncCollection(SyncCollectionRequest),
}
@@ -40,7 +31,7 @@ pub async fn route_report_addressbook<AS: AddressbookStore + ?Sized>(
return Err(Error::Unauthorized);
}
let request: ReportRequest = quick_xml::de::from_str(&body)?;
let request = ReportRequest::parse_str(&body).map_err(crate::Error::NewXmlDecodeError)?;
Ok(match request.clone() {
ReportRequest::AddressbookMultiget(addr_multiget) => {
@@ -67,3 +58,40 @@ pub async fn route_report_addressbook<AS: AddressbookStore + ?Sized>(
}
})
}
#[cfg(test)]
mod tests {
use rustical_dav::xml::{PropElement, Propname};
use sync_collection::SyncLevel;
use super::*;
#[test]
fn test_xml_sync_collection() {
let report_request = ReportRequest::parse_str(
r#"
<?xml version='1.0' encoding='UTF-8' ?>
<sync-collection xmlns="DAV:">
<sync-token />
<sync-level>1</sync-level>
<prop>
<getetag />
</prop>
</sync-collection>"#,
)
.unwrap();
assert_eq!(
report_request,
ReportRequest::SyncCollection(SyncCollectionRequest {
sync_token: "".to_owned(),
sync_level: SyncLevel::One,
prop: rustical_dav::xml::PropfindType::Prop(PropElement {
prop: vec![Propname {
name: "getetag".to_owned()
}]
}),
limit: None
})
)
}
}

View File

@@ -13,28 +13,42 @@ use rustical_store::{
synctoken::{format_synctoken, parse_synctoken},
AddressbookStore,
};
use serde::Deserialize;
use rustical_xml::{Value, XmlDeserialize};
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
enum SyncLevel {
#[serde(rename = "1")]
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum SyncLevel {
One,
Infinity,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
#[allow(dead_code)]
impl Value for SyncLevel {
fn deserialize(val: &str) -> Result<Self, rustical_xml::XmlDeError> {
Ok(match val {
"1" => Self::One,
"Infinity" => Self::Infinity,
// TODO: proper error
_ => return Err(rustical_xml::XmlDeError::UnknownError),
})
}
fn serialize(&self) -> String {
match self {
SyncLevel::One => "1",
SyncLevel::Infinity => "Infinity",
}
.to_owned()
}
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
pub struct SyncCollectionRequest {
sync_token: String,
sync_level: SyncLevel,
#[serde(flatten)]
pub(crate) struct SyncCollectionRequest {
pub(crate) sync_token: String,
pub(crate) sync_level: SyncLevel,
#[xml(ty = "untagged")]
pub prop: PropfindType,
limit: Option<u64>,
pub(crate) limit: Option<u64>,
}
pub async fn handle_sync_collection<AS: AddressbookStore + ?Sized>(
@@ -53,7 +67,10 @@ pub async fn handle_sync_collection<AS: AddressbookStore + ?Sized>(
PropfindType::Propname => {
vec!["propname".to_owned()]
}
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags.into_inner(),
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags
.into_iter()
.map(|propname| propname.name)
.collect(),
};
let props: Vec<&str> = props.iter().map(String::as_str).collect();

View File

@@ -1,5 +1,5 @@
use super::methods::mkcol::route_mkcol;
// use super::methods::report::route_report_addressbook;
use super::methods::report::route_report_addressbook;
use super::prop::{SupportedAddressData, SupportedReportSet};
use crate::address_object::resource::AddressObjectResource;
use crate::principal::PrincipalResource;
@@ -242,9 +242,8 @@ impl<AS: AddressbookStore + ?Sized> ResourceService for AddressbookResourceServi
#[inline]
fn actix_additional_routes(res: actix_web::Resource) -> actix_web::Resource {
let mkcol_method = web::method(Method::from_str("MKCOL").unwrap());
// TODO: Re-enable REPORT
// let report_method = web::method(Method::from_str("REPORT").unwrap());
let report_method = web::method(Method::from_str("REPORT").unwrap());
res.route(mkcol_method.to(route_mkcol::<AS>))
// .route(report_method.to(route_report_addressbook::<AS>))
.route(report_method.to(route_report_addressbook::<AS>))
}
}

View File

@@ -18,6 +18,9 @@ pub enum Error {
#[error(transparent)]
DavError(#[from] rustical_dav::Error),
#[error(transparent)]
NewXmlDecodeError(#[from] rustical_xml::XmlDeError),
#[error(transparent)]
XmlDecodeError(#[from] quick_xml::DeError),
@@ -35,6 +38,7 @@ impl actix_web::ResponseError for Error {
},
Error::DavError(err) => err.status_code(),
Error::Unauthorized => StatusCode::UNAUTHORIZED,
Error::NewXmlDecodeError(_) => StatusCode::BAD_REQUEST,
Error::XmlDecodeError(_) => StatusCode::BAD_REQUEST,
Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
Error::Other(_) => StatusCode::INTERNAL_SERVER_ERROR,