mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
Migrate propfind and report to rustical_xml
This commit is contained in:
@@ -25,3 +25,4 @@ url = { workspace = true }
|
||||
rustical_dav = { workspace = true }
|
||||
rustical_store = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
rustical_xml.workspace = true
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user