diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-05-27 18:16:53 +0200 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-05-27 18:16:53 +0200 |
commit | 5b1da2a33b265b674a130a90377c289faea7a210 (patch) | |
tree | e3c5ec8bc0745d978fd49ddc60d7e9170055dc9f /aero-dav | |
parent | 418adf92be86ea83008a145180837f1e0ad3018a (diff) | |
download | aerogramme-5b1da2a33b265b674a130a90377c289faea7a210.tar.gz aerogramme-5b1da2a33b265b674a130a90377c289faea7a210.zip |
webdav sync core codec
Diffstat (limited to 'aero-dav')
-rw-r--r-- | aero-dav/src/caldecoder.rs | 2 | ||||
-rw-r--r-- | aero-dav/src/calencoder.rs | 2 | ||||
-rw-r--r-- | aero-dav/src/caltypes.rs | 2 | ||||
-rw-r--r-- | aero-dav/src/lib.rs | 13 | ||||
-rw-r--r-- | aero-dav/src/realization.rs | 33 | ||||
-rw-r--r-- | aero-dav/src/syncdecoder.rs | 175 | ||||
-rw-r--r-- | aero-dav/src/syncencoder.rs | 144 | ||||
-rw-r--r-- | aero-dav/src/synctypes.rs | 68 | ||||
-rw-r--r-- | aero-dav/src/types.rs | 5 | ||||
-rw-r--r-- | aero-dav/src/versioningdecoder.rs | 62 | ||||
-rw-r--r-- | aero-dav/src/versioningencoder.rs | 81 | ||||
-rw-r--r-- | aero-dav/src/versioningtypes.rs | 36 |
12 files changed, 617 insertions, 6 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index b6a843f..ff79845 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -25,7 +25,7 @@ impl<E: dav::Extension> QRead<MkCalendarResponse<E>> for MkCalendarResponse<E> { } } -impl<E: dav::Extension> QRead<Report<E>> for Report<E> { +impl<E: dav::Extension> QRead<ReportType<E>> for ReportType<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { match CalendarQuery::<E>::qread(xml).await { Err(ParsingError::Recoverable) => (), diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs index 723d95d..48c93d0 100644 --- a/aero-dav/src/calencoder.rs +++ b/aero-dav/src/calencoder.rs @@ -33,7 +33,7 @@ impl<E: Extension> QWrite for MkCalendarResponse<E> { } // ----------------------- REPORT METHOD ------------------------------------- -impl<E: Extension> QWrite for Report<E> { +impl<E: Extension> QWrite for ReportType<E> { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { match self { Self::Query(v) => v.qwrite(xml).await, diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs index 50cdb92..a763653 100644 --- a/aero-dav/src/caltypes.rs +++ b/aero-dav/src/caltypes.rs @@ -51,7 +51,7 @@ pub struct MkCalendarResponse<E: dav::Extension>(pub Vec<dav::PropStat<E>>); // --- (REPORT PART) --- #[derive(Debug, PartialEq, Clone)] -pub enum Report<E: dav::Extension> { +pub enum ReportType<E: dav::Extension> { Query(CalendarQuery<E>), Multiget(CalendarMultiget<E>), FreeBusy(FreeBusyQuery), diff --git a/aero-dav/src/lib.rs b/aero-dav/src/lib.rs index 7507ddc..64be929 100644 --- a/aero-dav/src/lib.rs +++ b/aero-dav/src/lib.rs @@ -16,13 +16,20 @@ pub mod caldecoder; pub mod calencoder; pub mod caltypes; -// acl (wip) +// acl (partial) pub mod acldecoder; pub mod aclencoder; pub mod acltypes; -// versioning (wip) -mod versioningtypes; +// versioning (partial) +pub mod versioningdecoder; +pub mod versioningencoder; +pub mod versioningtypes; + +// sync +pub mod syncdecoder; +pub mod syncencoder; +pub mod synctypes; // final type pub mod realization; diff --git a/aero-dav/src/realization.rs b/aero-dav/src/realization.rs index 7283e68..0f3aec4 100644 --- a/aero-dav/src/realization.rs +++ b/aero-dav/src/realization.rs @@ -1,6 +1,7 @@ use super::acltypes as acl; use super::caltypes as cal; use super::error; +use super::synctypes as sync; use super::types as dav; use super::xml; @@ -31,6 +32,7 @@ impl dav::Extension for Core { type Property = Disabled; type PropertyRequest = Disabled; type ResourceType = Disabled; + type ReportType = Disabled; } // WebDAV with the base Calendar implementation (RFC4791) @@ -41,6 +43,7 @@ impl dav::Extension for Calendar { type Property = cal::Property; type PropertyRequest = cal::PropertyRequest; type ResourceType = cal::ResourceType; + type ReportType = cal::ReportType<Calendar>; } // ACL @@ -51,6 +54,7 @@ impl dav::Extension for Acl { type Property = acl::Property; type PropertyRequest = acl::PropertyRequest; type ResourceType = acl::ResourceType; + type ReportType = Disabled; } // All merged @@ -61,6 +65,7 @@ impl dav::Extension for All { type Property = Property; type PropertyRequest = PropertyRequest; type ResourceType = ResourceType; + type ReportType = ReportType<All>; } #[derive(Debug, PartialEq, Clone)] @@ -142,3 +147,31 @@ impl xml::QWrite for ResourceType { } } } + +#[derive(Debug, PartialEq, Clone)] +pub enum ReportType<E: dav::Extension> { + Cal(cal::ReportType<E>), + Sync(sync::SyncCollection<E>), +} +impl<E: dav::Extension> xml::QRead<ReportType<E>> for ReportType<E> { + async fn qread( + xml: &mut xml::Reader<impl xml::IRead>, + ) -> Result<ReportType<E>, error::ParsingError> { + match cal::ReportType::qread(xml).await { + Err(error::ParsingError::Recoverable) => (), + otherwise => return otherwise.map(ReportType::Cal), + } + sync::SyncCollection::qread(xml).await.map(ReportType::Sync) + } +} +impl<E: dav::Extension> xml::QWrite for ReportType<E> { + async fn qwrite( + &self, + xml: &mut xml::Writer<impl xml::IWrite>, + ) -> Result<(), quick_xml::Error> { + match self { + Self::Cal(c) => c.qwrite(xml).await, + Self::Sync(s) => s.qwrite(xml).await, + } + } +} diff --git a/aero-dav/src/syncdecoder.rs b/aero-dav/src/syncdecoder.rs new file mode 100644 index 0000000..8e035ab --- /dev/null +++ b/aero-dav/src/syncdecoder.rs @@ -0,0 +1,175 @@ +use quick_xml::events::Event; + +use super::error::ParsingError; +use super::synctypes::*; +use super::types as dav; +use super::xml::{IRead, QRead, Reader, DAV_URN}; + +impl<E: dav::Extension> QRead<SyncCollection<E>> for SyncCollection<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "sync-collection").await?; + let (mut sync_token, mut sync_level, mut limit, mut prop) = (None, None, None, None); + loop { + let mut dirty = false; + xml.maybe_read(&mut sync_token, &mut dirty).await?; + xml.maybe_read(&mut sync_level, &mut dirty).await?; + xml.maybe_read(&mut limit, &mut dirty).await?; + xml.maybe_read(&mut prop, &mut dirty).await?; + + if !dirty { + match xml.peek() { + Event::End(_) => break, + _ => xml.skip().await?, + }; + } + } + + xml.close().await?; + match (sync_token, sync_level, prop) { + (Some(sync_token), Some(sync_level), Some(prop)) => Ok(SyncCollection { + sync_token, + sync_level, + limit, + prop, + }), + _ => Err(ParsingError::MissingChild), + } + } +} + +impl QRead<SyncTokenRequest> for SyncTokenRequest { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "sync-token").await?; + let token = match xml.tag_string().await { + Ok(v) => SyncTokenRequest::IncrementalSync(v), + Err(ParsingError::Recoverable) => SyncTokenRequest::InitialSync, + Err(e) => return Err(e), + }; + xml.close().await?; + Ok(token) + } +} + +impl QRead<SyncToken> for SyncToken { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "sync-token").await?; + let token = xml.tag_string().await?; + xml.close().await?; + Ok(SyncToken(token)) + } +} + +impl QRead<SyncLevel> for SyncLevel { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "sync-level").await?; + let lvl = match xml.tag_string().await?.to_lowercase().as_str() { + "1" => SyncLevel::One, + "infinite" => SyncLevel::Infinite, + _ => return Err(ParsingError::InvalidValue), + }; + xml.close().await?; + Ok(lvl) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::realization::All; + use crate::types as dav; + use crate::versioningtypes as vers; + use crate::xml::Node; + + async fn deserialize<T: Node<T>>(src: &str) -> T { + let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes())) + .await + .unwrap(); + rdr.find().await.unwrap() + } + + #[tokio::test] + async fn sync_level() { + { + let expected = SyncLevel::One; + let src = r#"<D:sync-level xmlns:D="DAV:">1</D:sync-level>"#; + let got = deserialize::<SyncLevel>(src).await; + assert_eq!(got, expected); + } + { + let expected = SyncLevel::Infinite; + let src = r#"<D:sync-level xmlns:D="DAV:">infinite</D:sync-level>"#; + let got = deserialize::<SyncLevel>(src).await; + assert_eq!(got, expected); + } + } + + #[tokio::test] + async fn sync_token_request() { + { + let expected = SyncTokenRequest::InitialSync; + let src = r#"<D:sync-token xmlns:D="DAV:"/>"#; + let got = deserialize::<SyncTokenRequest>(src).await; + assert_eq!(got, expected); + } + { + let expected = + SyncTokenRequest::IncrementalSync("http://example.com/ns/sync/1232".into()); + let src = + r#"<D:sync-token xmlns:D="DAV:">http://example.com/ns/sync/1232</D:sync-token>"#; + let got = deserialize::<SyncTokenRequest>(src).await; + assert_eq!(got, expected); + } + } + + #[tokio::test] + async fn sync_token() { + let expected = SyncToken("http://example.com/ns/sync/1232".into()); + let src = r#"<D:sync-token xmlns:D="DAV:">http://example.com/ns/sync/1232</D:sync-token>"#; + let got = deserialize::<SyncToken>(src).await; + assert_eq!(got, expected); + } + + #[tokio::test] + async fn sync_collection() { + { + let expected = SyncCollection::<All> { + sync_token: SyncTokenRequest::IncrementalSync( + "http://example.com/ns/sync/1232".into(), + ), + sync_level: SyncLevel::One, + limit: Some(vers::Limit(vers::NResults(100))), + prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]), + }; + let src = r#"<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://example.com/ns/sync/1232</D:sync-token> + <D:sync-level>1</D:sync-level> + <D:limit> + <D:nresults>100</D:nresults> + </D:limit> + <D:prop> + <D:getetag/> + </D:prop> + </D:sync-collection>"#; + let got = deserialize::<SyncCollection<All>>(src).await; + assert_eq!(got, expected); + } + + { + let expected = SyncCollection::<All> { + sync_token: SyncTokenRequest::InitialSync, + sync_level: SyncLevel::Infinite, + limit: None, + prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]), + }; + let src = r#"<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>infinite</D:sync-level> + <D:prop> + <D:getetag/> + </D:prop> + </D:sync-collection>"#; + let got = deserialize::<SyncCollection<All>>(src).await; + assert_eq!(got, expected); + } + } +} diff --git a/aero-dav/src/syncencoder.rs b/aero-dav/src/syncencoder.rs new file mode 100644 index 0000000..22b288b --- /dev/null +++ b/aero-dav/src/syncencoder.rs @@ -0,0 +1,144 @@ +use quick_xml::events::{BytesText, Event}; +use quick_xml::Error as QError; + +use super::synctypes::*; +use super::types::Extension; +use super::xml::{IWrite, QWrite, Writer}; + +impl<E: Extension> QWrite for SyncCollection<E> { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("sync-collection"); + let end = start.to_end(); + + xml.q.write_event_async(Event::Start(start.clone())).await?; + self.sync_token.qwrite(xml).await?; + self.sync_level.qwrite(xml).await?; + if let Some(limit) = &self.limit { + limit.qwrite(xml).await?; + } + self.prop.qwrite(xml).await?; + xml.q.write_event_async(Event::End(end)).await + } +} + +impl QWrite for SyncTokenRequest { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("sync-token"); + + match self { + Self::InitialSync => xml.q.write_event_async(Event::Empty(start)).await, + Self::IncrementalSync(uri) => { + let end = start.to_end(); + xml.q.write_event_async(Event::Start(start.clone())).await?; + xml.q + .write_event_async(Event::Text(BytesText::new(uri.as_str()))) + .await?; + xml.q.write_event_async(Event::End(end)).await + } + } + } +} + +impl QWrite for SyncToken { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("sync-token"); + let end = start.to_end(); + + xml.q.write_event_async(Event::Start(start.clone())).await?; + xml.q + .write_event_async(Event::Text(BytesText::new(self.0.as_str()))) + .await?; + xml.q.write_event_async(Event::End(end)).await + } +} + +impl QWrite for SyncLevel { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("sync-level"); + let end = start.to_end(); + let text = match self { + Self::One => "1", + Self::Infinite => "infinite", + }; + + xml.q.write_event_async(Event::Start(start.clone())).await?; + xml.q + .write_event_async(Event::Text(BytesText::new(text))) + .await?; + xml.q.write_event_async(Event::End(end)).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::realization::All; + use crate::types as dav; + use crate::versioningtypes as vers; + use crate::xml::Node; + use crate::xml::Reader; + use tokio::io::AsyncWriteExt; + + async fn serialize_deserialize<T: Node<T>>(src: &T) { + let mut buffer = Vec::new(); + let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); + let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4); + let ns_to_apply = vec![ + ("xmlns:D".into(), "DAV:".into()), + ("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".into()), + ]; + let mut writer = Writer { q, ns_to_apply }; + + src.qwrite(&mut writer).await.expect("xml serialization"); + tokio_buffer.flush().await.expect("tokio buffer flush"); + let got = std::str::from_utf8(buffer.as_slice()).unwrap(); + + // deserialize + let mut rdr = Reader::new(quick_xml::NsReader::from_reader(got.as_bytes())) + .await + .unwrap(); + let res = rdr.find().await.unwrap(); + + // check + assert_eq!(src, &res); + } + + #[tokio::test] + async fn sync_level() { + serialize_deserialize(&SyncLevel::One).await; + serialize_deserialize(&SyncLevel::Infinite).await; + } + + #[tokio::test] + async fn sync_token_request() { + serialize_deserialize(&SyncTokenRequest::InitialSync).await; + serialize_deserialize(&SyncTokenRequest::IncrementalSync( + "http://example.com/ns/sync/1232".into(), + )) + .await; + } + + #[tokio::test] + async fn sync_token() { + serialize_deserialize(&SyncToken("http://example.com/ns/sync/1232".into())).await; + } + + #[tokio::test] + async fn sync_collection() { + serialize_deserialize(&SyncCollection::<All> { + sync_token: SyncTokenRequest::IncrementalSync("http://example.com/ns/sync/1232".into()), + sync_level: SyncLevel::One, + limit: Some(vers::Limit(vers::NResults(100))), + prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]), + }) + .await; + + serialize_deserialize(&SyncCollection::<All> { + sync_token: SyncTokenRequest::InitialSync, + sync_level: SyncLevel::Infinite, + limit: None, + prop: dav::PropName(vec![dav::PropertyRequest::GetEtag]), + }) + .await; + } +} diff --git a/aero-dav/src/synctypes.rs b/aero-dav/src/synctypes.rs new file mode 100644 index 0000000..a2f40bd --- /dev/null +++ b/aero-dav/src/synctypes.rs @@ -0,0 +1,68 @@ +use super::types as dav; +use super::versioningtypes as vers; + +// RFC 6578 +// https://datatracker.ietf.org/doc/html/rfc6578 + +//@FIXME add SyncTokenRequest to PropertyRequest +//@FIXME add SyncToken to Property +//@FIXME add SyncToken to Multistatus + +/// Name: sync-collection +/// +/// Namespace: DAV: +/// +/// Purpose: WebDAV report used to synchronize data between client and +/// server. +/// +/// Description: See Section 3. +/// +/// <!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 --> + +#[derive(Debug, PartialEq, Clone)] +pub struct SyncCollection<E: dav::Extension> { + pub sync_token: SyncTokenRequest, + pub sync_level: SyncLevel, + pub limit: Option<vers::Limit>, + pub prop: dav::PropName<E>, +} + +/// Name: sync-token +/// +/// Namespace: DAV: +/// +/// Purpose: The synchronization token provided by the server and +/// returned by the client. +/// +/// Description: See Section 3. +/// +/// <!ELEMENT sync-token CDATA> +/// +/// <!-- Text MUST be a URI --> +/// Used by multistatus +#[derive(Debug, PartialEq, Clone)] +pub struct SyncToken(pub String); + +/// Used by propfind and report sync-collection +#[derive(Debug, PartialEq, Clone)] +pub enum SyncTokenRequest { + InitialSync, + IncrementalSync(String), +} + +/// Name: sync-level +/// +/// Namespace: DAV: +/// +/// Purpose: Indicates the "scope" of the synchronization report +/// request. +/// +/// Description: See Section 3.3. +#[derive(Debug, PartialEq, Clone)] +pub enum SyncLevel { + One, + Infinite, +} diff --git a/aero-dav/src/types.rs b/aero-dav/src/types.rs index d5466da..6039a26 100644 --- a/aero-dav/src/types.rs +++ b/aero-dav/src/types.rs @@ -11,6 +11,7 @@ pub trait Extension: std::fmt::Debug + PartialEq + Clone { type Property: xml::Node<Self::Property>; type PropertyRequest: xml::Node<Self::PropertyRequest>; type ResourceType: xml::Node<Self::ResourceType>; + type ReportType: xml::Node<Self::ReportType>; } /// 14.1. activelock XML Element @@ -328,6 +329,10 @@ pub enum LockType { /// response descriptions contained within the responses. /// /// <!ELEMENT multistatus (response*, responsedescription?) > +/// +/// In WebDAV sync (rfc6578), multistatus is extended: +/// +/// <!ELEMENT multistatus (response*, responsedescription?, sync-token?) > #[derive(Debug, PartialEq, Clone)] pub struct Multistatus<E: Extension> { pub responses: Vec<Response<E>>, diff --git a/aero-dav/src/versioningdecoder.rs b/aero-dav/src/versioningdecoder.rs new file mode 100644 index 0000000..4816cf1 --- /dev/null +++ b/aero-dav/src/versioningdecoder.rs @@ -0,0 +1,62 @@ +use super::error::ParsingError; +use super::types as dav; +use super::versioningtypes::*; +use super::xml::{IRead, QRead, Reader, DAV_URN}; + +impl<E: dav::Extension> QRead<Report<E>> for Report<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + //@FIXME VersionTree not implemented + //@FIXME ExpandTree not implemented + + E::ReportType::qread(xml).await.map(Report::Extension) + } +} + +impl QRead<Limit> for Limit { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "limit").await?; + let nres = xml.find().await?; + xml.close().await?; + Ok(Limit(nres)) + } +} + +impl QRead<NResults> for NResults { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "nresults").await?; + let sz = xml.tag_string().await?.parse::<u64>()?; + xml.close().await?; + Ok(NResults(sz)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::xml::Node; + + async fn deserialize<T: Node<T>>(src: &str) -> T { + let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes())) + .await + .unwrap(); + rdr.find().await.unwrap() + } + + #[tokio::test] + async fn nresults() { + let expected = NResults(100); + let src = r#"<D:nresults xmlns:D="DAV:">100</D:nresults>"#; + let got = deserialize::<NResults>(src).await; + assert_eq!(got, expected); + } + + #[tokio::test] + async fn limit() { + let expected = Limit(NResults(1024)); + let src = r#"<D:limit xmlns:D="DAV:"> + <D:nresults>1024</D:nresults> + </D:limit>"#; + let got = deserialize::<Limit>(src).await; + assert_eq!(got, expected); + } +} diff --git a/aero-dav/src/versioningencoder.rs b/aero-dav/src/versioningencoder.rs new file mode 100644 index 0000000..bd40f1b --- /dev/null +++ b/aero-dav/src/versioningencoder.rs @@ -0,0 +1,81 @@ +use quick_xml::events::{BytesText, Event}; +use quick_xml::Error as QError; + +use super::types::Extension; +use super::versioningtypes::*; +use super::xml::{IWrite, QWrite, Writer}; + +impl<E: Extension> QWrite for Report<E> { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + match self { + Report::VersionTree => unimplemented!(), + Report::ExpandProperty => unimplemented!(), + Report::Extension(inner) => inner.qwrite(xml).await, + } + } +} + +impl QWrite for Limit { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("limit"); + let end = start.to_end(); + + xml.q.write_event_async(Event::Start(start.clone())).await?; + self.0.qwrite(xml).await?; + xml.q.write_event_async(Event::End(end)).await + } +} + +impl QWrite for NResults { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("nresults"); + let end = start.to_end(); + + xml.q.write_event_async(Event::Start(start.clone())).await?; + xml.q + .write_event_async(Event::Text(BytesText::new(&format!("{}", self.0)))) + .await?; + xml.q.write_event_async(Event::End(end)).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::xml::Node; + use crate::xml::Reader; + use tokio::io::AsyncWriteExt; + + async fn serialize_deserialize<T: Node<T>>(src: &T) -> T { + let mut buffer = Vec::new(); + let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); + let q = quick_xml::writer::Writer::new_with_indent(&mut tokio_buffer, b' ', 4); + let ns_to_apply = vec![ + ("xmlns:D".into(), "DAV:".into()), + ("xmlns:C".into(), "urn:ietf:params:xml:ns:caldav".into()), + ]; + let mut writer = Writer { q, ns_to_apply }; + + src.qwrite(&mut writer).await.expect("xml serialization"); + tokio_buffer.flush().await.expect("tokio buffer flush"); + let got = std::str::from_utf8(buffer.as_slice()).unwrap(); + + // deserialize + let mut rdr = Reader::new(quick_xml::NsReader::from_reader(got.as_bytes())) + .await + .unwrap(); + rdr.find().await.unwrap() + } + + #[tokio::test] + async fn nresults() { + let orig = NResults(100); + assert_eq!(orig, serialize_deserialize(&orig).await); + } + + #[tokio::test] + async fn limit() { + let orig = Limit(NResults(1024)); + assert_eq!(orig, serialize_deserialize(&orig).await); + } +} diff --git a/aero-dav/src/versioningtypes.rs b/aero-dav/src/versioningtypes.rs index 6c1c204..ba64b05 100644 --- a/aero-dav/src/versioningtypes.rs +++ b/aero-dav/src/versioningtypes.rs @@ -1,3 +1,39 @@ +use super::types as dav; + //@FIXME required for a full DAV implementation // See section 7.1 of the CalDAV RFC // It seems it's mainly due to the fact that the REPORT method is re-used. +// https://datatracker.ietf.org/doc/html/rfc4791#section-7.1 +// +// Defines (required by CalDAV): +// - REPORT method +// - expand-property root report method +// +// Defines (required by Sync): +// - limit, nresults +// - supported-report-set + +// This property identifies the reports that are supported by the +// resource. +// +// <!ELEMENT supported-report-set (supported-report*)> +// <!ELEMENT supported-report report> +// <!ELEMENT report ANY> +// ANY value: a report element type + +#[derive(Debug, PartialEq, Clone)] +pub enum Report<E: dav::Extension> { + VersionTree, // Not yet implemented + ExpandProperty, // Not yet implemented + Extension(E::ReportType), +} + +/// Limit +/// <!ELEMENT limit (nresults) > +#[derive(Debug, PartialEq, Clone)] +pub struct Limit(pub NResults); + +/// NResults +/// <!ELEMENT nresults (#PCDATA) > +#[derive(Debug, PartialEq, Clone)] +pub struct NResults(pub u64); |