diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-03-18 20:45:30 +0100 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-03-18 20:45:30 +0100 |
commit | d0c47b93fe19a9ebc35d624b9dbed7d1d539ecaa (patch) | |
tree | 6503142e21a5dd36796bb99a4406f491e62d92e9 | |
parent | 2e7ffd4f4ca6ba82069290e0a3a70e85a3a79a7b (diff) | |
download | aerogramme-d0c47b93fe19a9ebc35d624b9dbed7d1d539ecaa.tar.gz aerogramme-d0c47b93fe19a9ebc35d624b9dbed7d1d539ecaa.zip |
Rework webdav types
-rw-r--r-- | aero-dav/src/caldecoder.rs | 43 | ||||
-rw-r--r-- | aero-dav/src/calencoder.rs | 20 | ||||
-rw-r--r-- | aero-dav/src/caltypes.rs | 12 | ||||
-rw-r--r-- | aero-dav/src/decoder.rs | 122 | ||||
-rw-r--r-- | aero-dav/src/encoder.rs | 114 | ||||
-rw-r--r-- | aero-dav/src/types.rs | 30 | ||||
-rw-r--r-- | aero-dav/src/xml.rs | 19 | ||||
-rw-r--r-- | aero-proto/src/dav.rs | 12 |
8 files changed, 226 insertions, 146 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index d3c68f6..dbc6e18 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -3,7 +3,7 @@ use chrono::NaiveDateTime; use super::types as dav; use super::caltypes::*; -use super::xml::{QRead, IRead, Reader, Node, DAV_URN, CAL_URN}; +use super::xml::{QRead, IRead, Reader, DAV_URN, CAL_URN}; use super::error::ParsingError; // ---- ROOT ELEMENTS --- @@ -16,7 +16,7 @@ impl<E: dav::Extension> QRead<MkCalendar<E>> for MkCalendar<E> { } } -impl<E: dav::Extension, N: Node<N>> QRead<MkCalendarResponse<E,N>> for MkCalendarResponse<E,N> { +impl<E: dav::Extension> QRead<MkCalendarResponse<E>> for MkCalendarResponse<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { xml.open(CAL_URN, "mkcalendar-response").await?; let propstats = xml.collect().await?; @@ -162,57 +162,57 @@ impl QRead<Violation> for Violation { impl QRead<Property> for Property { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { - if xml.maybe_open(CAL_URN, "calendar-description").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "calendar-description").await?.is_some() { let lang = xml.prev_attr("xml:lang"); let text = xml.tag_string().await?; xml.close().await?; return Ok(Property::CalendarDescription { lang, text }) } - if xml.maybe_open(CAL_URN, "calendar-timezone").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "calendar-timezone").await?.is_some() { let tz = xml.tag_string().await?; xml.close().await?; return Ok(Property::CalendarTimezone(tz)) } - if xml.maybe_open(CAL_URN, "supported-calendar-component-set").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "supported-calendar-component-set").await?.is_some() { let comp = xml.collect().await?; xml.close().await?; return Ok(Property::SupportedCalendarComponentSet(comp)) } - if xml.maybe_open(CAL_URN, "supported-calendar-data").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "supported-calendar-data").await?.is_some() { let mime = xml.collect().await?; xml.close().await?; return Ok(Property::SupportedCalendarData(mime)) } - if xml.maybe_open(CAL_URN, "max-resource-size").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "max-resource-size").await?.is_some() { let sz = xml.tag_string().await?.parse::<u64>()?; xml.close().await?; return Ok(Property::MaxResourceSize(sz)) } - if xml.maybe_open(CAL_URN, "max-date-time").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "max-date-time").await?.is_some() { let dtstr = xml.tag_string().await?; let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), ICAL_DATETIME_FMT)?.and_utc(); xml.close().await?; return Ok(Property::MaxDateTime(dt)) } - if xml.maybe_open(CAL_URN, "max-instances").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "max-instances").await?.is_some() { let sz = xml.tag_string().await?.parse::<u64>()?; xml.close().await?; return Ok(Property::MaxInstances(sz)) } - if xml.maybe_open(CAL_URN, "max-attendees-per-instance").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "max-attendees-per-instance").await?.is_some() { let sz = xml.tag_string().await?.parse::<u64>()?; xml.close().await?; return Ok(Property::MaxAttendeesPerInstance(sz)) } - if xml.maybe_open(CAL_URN, "supported-collation-set").await?.is_some() { + if xml.maybe_open_start(CAL_URN, "supported-collation-set").await?.is_some() { let cols = xml.collect().await?; xml.close().await?; return Ok(Property::SupportedCollationSet(cols)) @@ -759,6 +759,7 @@ mod tests { use super::*; use chrono::{Utc, TimeZone}; use crate::realization::Calendar; + use crate::xml::Node; //use quick_reader::NsReader; async fn deserialize<T: Node<T>>(src: &str) -> T { @@ -933,19 +934,19 @@ END:VCALENDAR]]></C:calendar-timezone> #[tokio::test] async fn rfc_calendar_query_res() { - let expected = dav::Multistatus::<Calendar, dav::PropValue<Calendar>> { + let expected = dav::Multistatus::<Calendar> { responses: vec![ dav::Response { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()), vec![ dav::PropStat { - prop: dav::PropValue(vec![ - dav::Property::GetEtag("\"fffff-abcd2\"".into()), - dav::Property::Extension(Property::CalendarData(CalendarDataPayload { + prop: dav::AnyProp(vec![ + dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd2\"".into())), + dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload { mime: None, payload: "BEGIN:VCALENDAR".into(), - })), + }))), ]), status: dav::Status(http::status::StatusCode::OK), error: None, @@ -962,12 +963,12 @@ END:VCALENDAR]]></C:calendar-timezone> dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()), vec![ dav::PropStat { - prop: dav::PropValue(vec![ - dav::Property::GetEtag("\"fffff-abcd3\"".into()), - dav::Property::Extension(Property::CalendarData(CalendarDataPayload { + prop: dav::AnyProp(vec![ + dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd3\"".into())), + dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload { mime: None, payload: "BEGIN:VCALENDAR".into(), - })), + }))), ]), status: dav::Status(http::status::StatusCode::OK), error: None, @@ -1008,7 +1009,7 @@ END:VCALENDAR]]></C:calendar-timezone> </D:multistatus> "#; - let got = deserialize::<dav::Multistatus<Calendar,dav::PropValue<Calendar>>>(src).await; + let got = deserialize::<dav::Multistatus<Calendar>>(src).await; assert_eq!(got, expected) } diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs index 54a35a2..5323229 100644 --- a/aero-dav/src/calencoder.rs +++ b/aero-dav/src/calencoder.rs @@ -20,7 +20,7 @@ impl<E: Extension> QWrite for MkCalendar<E> { } } -impl<E: Extension, N: Node<N>> QWrite for MkCalendarResponse<E,N> { +impl<E: Extension> QWrite for MkCalendarResponse<E> { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { let start = xml.create_cal_element("mkcalendar-response"); let end = start.to_end(); @@ -828,18 +828,18 @@ mod tests { #[tokio::test] async fn rfc_calendar_query1_res() { let got = serialize( - &dav::Multistatus::<Calendar,dav::PropValue<Calendar>> { + &dav::Multistatus::<Calendar> { responses: vec![ dav::Response { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()), vec![dav::PropStat { - prop: dav::PropValue(vec![ - dav::Property::GetEtag("\"fffff-abcd2\"".into()), - dav::Property::Extension(Property::CalendarData(CalendarDataPayload { + prop: dav::AnyProp(vec![ + dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd2\"".into())), + dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload { mime: None, payload: "PLACEHOLDER".into() - })), + }))), ]), status: dav::Status(http::status::StatusCode::OK), error: None, @@ -854,12 +854,12 @@ mod tests { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()), vec![dav::PropStat { - prop: dav::PropValue(vec![ - dav::Property::GetEtag("\"fffff-abcd3\"".into()), - dav::Property::Extension(Property::CalendarData(CalendarDataPayload{ + prop: dav::AnyProp(vec![ + dav::AnyProperty::Value(dav::Property::GetEtag("\"fffff-abcd3\"".into())), + dav::AnyProperty::Value(dav::Property::Extension(Property::CalendarData(CalendarDataPayload{ mime: None, payload: "PLACEHOLDER".into(), - })), + }))), ]), status: dav::Status(http::status::StatusCode::OK), error: None, diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs index cb0a98c..5ac50e6 100644 --- a/aero-dav/src/caltypes.rs +++ b/aero-dav/src/caltypes.rs @@ -31,7 +31,7 @@ pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ"; /// ```xmlschema /// <!ELEMENT mkcalendar (DAV:set)> /// ``` -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct MkCalendar<E: dav::Extension>(pub dav::Set<E>); @@ -48,8 +48,8 @@ pub struct MkCalendar<E: dav::Extension>(pub dav::Set<E>); /// Definition: /// /// <!ELEMENT mkcol-response (propstat+)> -#[derive(Debug, PartialEq)] -pub struct MkCalendarResponse<E: dav::Extension, N: xml::Node<N>>(pub Vec<dav::PropStat<E,N>>); +#[derive(Debug, PartialEq, Clone)] +pub struct MkCalendarResponse<E: dav::Extension>(pub Vec<dav::PropStat<E>>); // --- (REPORT PART) --- @@ -66,7 +66,7 @@ pub struct MkCalendarResponse<E: dav::Extension, N: xml::Node<N>>(pub Vec<dav::P /// <!ELEMENT calendar-query ((DAV:allprop | /// DAV:propname | /// DAV:prop)?, filter, timezone?)> -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct CalendarQuery<E: dav::Extension> { pub selector: Option<CalendarSelector<E>>, pub filter: Filter, @@ -87,7 +87,7 @@ pub struct CalendarQuery<E: dav::Extension> { /// <!ELEMENT calendar-multiget ((DAV:allprop | /// DAV:propname | /// DAV:prop)?, DAV:href+)> -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct CalendarMultiget<E: dav::Extension> { pub selector: Option<CalendarSelector<E>>, pub href: Vec<dav::Href>, @@ -104,7 +104,7 @@ pub struct CalendarMultiget<E: dav::Extension> { /// /// Definition: /// <!ELEMENT free-busy-query (time-range)> -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct FreeBusyQuery(pub TimeRange); // ----- Hooks ----- diff --git a/aero-dav/src/decoder.rs b/aero-dav/src/decoder.rs index de04dd4..bb8d9de 100644 --- a/aero-dav/src/decoder.rs +++ b/aero-dav/src/decoder.rs @@ -12,7 +12,7 @@ use super::xml::{Node, QRead, Reader, IRead, DAV_URN}; // (2) Rewrite QRead and replace Result<Option<_>, _> with Result<_, _>, not found being a possible // error. // (3) Rewrite vectors with xml.collect<E: QRead>() -> Result<Vec<E>, _> -// (4) Something for alternatives would be great but no idea yet +// (4) Something for alternatives like xml::choices on some lib would be great but no idea yet // ---- ROOT ---- @@ -61,7 +61,7 @@ impl<E: Extension> QRead<PropertyUpdate<E>> for PropertyUpdate<E> { } /// Generic response -impl<E: Extension, N: Node<N>> QRead<Multistatus<E,N>> for Multistatus<E,N> { +impl<E: Extension> QRead<Multistatus<E>> for Multistatus<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { xml.open(DAV_URN, "multistatus").await?; let mut responses = Vec::new(); @@ -113,6 +113,7 @@ impl QRead<LockInfo> for LockInfo { // LOCK RESPONSE impl<E: Extension> QRead<PropValue<E>> for PropValue<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + println!("---- propvalue"); xml.open(DAV_URN, "prop").await?; let acc = xml.collect::<Property<E>>().await?; xml.close().await?; @@ -134,7 +135,7 @@ impl<E: Extension> QRead<Error<E>> for Error<E> { // ---- INNER XML -impl<E: Extension, N: Node<N>> QRead<Response<E,N>> for Response<E,N> { +impl<E: Extension> QRead<Response<E>> for Response<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { xml.open(DAV_URN, "response").await?; let (mut status, mut error, mut responsedescription, mut location) = (None, None, None, None); @@ -145,7 +146,7 @@ impl<E: Extension, N: Node<N>> QRead<Response<E,N>> for Response<E,N> { let mut dirty = false; xml.maybe_read::<Status>(&mut status, &mut dirty).await?; xml.maybe_push::<Href>(&mut href, &mut dirty).await?; - xml.maybe_push::<PropStat<E,N>>(&mut propstat, &mut dirty).await?; + xml.maybe_push::<PropStat<E>>(&mut propstat, &mut dirty).await?; xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?; xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?; xml.maybe_read::<Location>(&mut location, &mut dirty).await?; @@ -174,15 +175,15 @@ impl<E: Extension, N: Node<N>> QRead<Response<E,N>> for Response<E,N> { } } -impl<E: Extension, N: Node<N>> QRead<PropStat<E,N>> for PropStat<E,N> { +impl<E: Extension> QRead<PropStat<E>> for PropStat<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { xml.open(DAV_URN, "propstat").await?; - let (mut m_prop, mut m_status, mut error, mut responsedescription) = (None, None, None, None); + let (mut m_any_prop, mut m_status, mut error, mut responsedescription) = (None, None, None, None); loop { let mut dirty = false; - xml.maybe_read::<N>(&mut m_prop, &mut dirty).await?; + xml.maybe_read::<AnyProp<E>>(&mut m_any_prop, &mut dirty).await?; xml.maybe_read::<Status>(&mut m_status, &mut dirty).await?; xml.maybe_read::<Error<E>>(&mut error, &mut dirty).await?; xml.maybe_read::<ResponseDescription>(&mut responsedescription, &mut dirty).await?; @@ -196,7 +197,7 @@ impl<E: Extension, N: Node<N>> QRead<PropStat<E,N>> for PropStat<E,N> { } xml.close().await?; - match (m_prop, m_status) { + match (m_any_prop, m_status) { (Some(prop), Some(status)) => Ok(PropStat { prop, status, error, responsedescription }), _ => Err(ParsingError::MissingChild), } @@ -309,6 +310,25 @@ impl<E: Extension> QRead<PropName<E>> for PropName<E> { } } +impl<E: Extension> QRead<AnyProp<E>> for AnyProp<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + xml.open(DAV_URN, "prop").await?; + let acc = xml.collect::<AnyProperty<E>>().await?; + xml.close().await?; + Ok(AnyProp(acc)) + } +} + +impl<E: Extension> QRead<AnyProperty<E>> for AnyProperty<E> { + async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { + match Property::qread(xml).await { + Err(ParsingError::Recoverable) => (), + otherwise => return otherwise.map(Self::Value) + } + PropertyRequest::qread(xml).await.map(Self::Request) + } +} + impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { let maybe = if xml.maybe_open(DAV_URN, "creationdate").await?.is_some() { @@ -348,43 +368,43 @@ impl<E: Extension> QRead<PropertyRequest<E>> for PropertyRequest<E> { impl<E: Extension> QRead<Property<E>> for Property<E> { async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> { // Core WebDAV properties - if xml.maybe_open(DAV_URN, "creationdate").await?.is_some() { + if xml.maybe_open_start(DAV_URN, "creationdate").await?.is_some() { let datestr = xml.tag_string().await?; xml.close().await?; return Ok(Property::CreationDate(DateTime::parse_from_rfc3339(datestr.as_str())?)) - } else if xml.maybe_open(DAV_URN, "displayname").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "displayname").await?.is_some() { let name = xml.tag_string().await?; xml.close().await?; return Ok(Property::DisplayName(name)) - } else if xml.maybe_open(DAV_URN, "getcontentlanguage").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "getcontentlanguage").await?.is_some() { let lang = xml.tag_string().await?; xml.close().await?; return Ok(Property::GetContentLanguage(lang)) - } else if xml.maybe_open(DAV_URN, "getcontentlength").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "getcontentlength").await?.is_some() { let cl = xml.tag_string().await?.parse::<u64>()?; xml.close().await?; return Ok(Property::GetContentLength(cl)) - } else if xml.maybe_open(DAV_URN, "getcontenttype").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "getcontenttype").await?.is_some() { let ct = xml.tag_string().await?; xml.close().await?; return Ok(Property::GetContentType(ct)) - } else if xml.maybe_open(DAV_URN, "getetag").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "getetag").await?.is_some() { let etag = xml.tag_string().await?; xml.close().await?; return Ok(Property::GetEtag(etag)) - } else if xml.maybe_open(DAV_URN, "getlastmodified").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "getlastmodified").await?.is_some() { let datestr = xml.tag_string().await?; xml.close().await?; return Ok(Property::GetLastModified(DateTime::parse_from_rfc2822(datestr.as_str())?)) - } else if xml.maybe_open(DAV_URN, "lockdiscovery").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "lockdiscovery").await?.is_some() { let acc = xml.collect::<ActiveLock>().await?; xml.close().await?; return Ok(Property::LockDiscovery(acc)) - } else if xml.maybe_open(DAV_URN, "resourcetype").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "resourcetype").await?.is_some() { let acc = xml.collect::<ResourceType<E>>().await?; xml.close().await?; return Ok(Property::ResourceType(acc)) - } else if xml.maybe_open(DAV_URN, "supportedlock").await?.is_some() { + } else if xml.maybe_open_start(DAV_URN, "supportedlock").await?.is_some() { let acc = xml.collect::<LockEntry>().await?; xml.close().await?; return Ok(Property::SupportedLock(acc)) @@ -758,7 +778,7 @@ mod tests { "#; let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<Multistatus::<Core, PropName<Core>>>().await.unwrap(); + let got = rdr.find::<Multistatus::<Core>>().await.unwrap(); assert_eq!(got, Multistatus { responses: vec![ @@ -766,11 +786,11 @@ mod tests { status_or_propstat: StatusOrPropstat::PropStat( Href("http://www.example.com/container/".into()), vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, + prop: AnyProp(vec![ + AnyProperty::Request(PropertyRequest::CreationDate), + AnyProperty::Request(PropertyRequest::DisplayName), + AnyProperty::Request(PropertyRequest::ResourceType), + AnyProperty::Request(PropertyRequest::SupportedLock), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -785,15 +805,15 @@ mod tests { status_or_propstat: StatusOrPropstat::PropStat( Href("http://www.example.com/container/front.html".into()), vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::GetContentLength, - PropertyRequest::GetContentType, - PropertyRequest::GetEtag, - PropertyRequest::GetLastModified, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, + prop: AnyProp(vec![ + AnyProperty::Request(PropertyRequest::CreationDate), + AnyProperty::Request(PropertyRequest::DisplayName), + AnyProperty::Request(PropertyRequest::GetContentLength), + AnyProperty::Request(PropertyRequest::GetContentType), + AnyProperty::Request(PropertyRequest::GetEtag), + AnyProperty::Request(PropertyRequest::GetLastModified), + AnyProperty::Request(PropertyRequest::ResourceType), + AnyProperty::Request(PropertyRequest::SupportedLock), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -869,7 +889,7 @@ mod tests { </D:multistatus>"#; let mut rdr = Reader::new(NsReader::from_reader(src.as_bytes())).await.unwrap(); - let got = rdr.find::<Multistatus::<Core, PropValue<Core>>>().await.unwrap(); + let got = rdr.find::<Multistatus::<Core>>().await.unwrap(); assert_eq!(got, Multistatus { responses: vec![ @@ -877,11 +897,11 @@ mod tests { status_or_propstat: StatusOrPropstat::PropStat( Href("/container/".into()), vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 17, 42, 21).unwrap()), - Property::DisplayName("Example collection".into()), - Property::ResourceType(vec![ResourceType::Collection]), - Property::SupportedLock(vec![ + prop: AnyProp(vec![ + AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 17, 42, 21).unwrap())), + AnyProperty::Value(Property::DisplayName("Example collection".into())), + AnyProperty::Value(Property::ResourceType(vec![ResourceType::Collection])), + AnyProperty::Value(Property::SupportedLock(vec![ LockEntry { lockscope: LockScope::Exclusive, locktype: LockType::Write, @@ -890,7 +910,7 @@ mod tests { lockscope: LockScope::Shared, locktype: LockType::Write, }, - ]), + ])), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -906,15 +926,17 @@ mod tests { status_or_propstat: StatusOrPropstat::PropStat( Href("/container/front.html".into()), vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 18, 27, 21).unwrap()), - Property::DisplayName("Example HTML resource".into()), - Property::GetContentLength(4525), - Property::GetContentType("text/html".into()), - Property::GetEtag(r#""zzyzx""#.into()), - Property::GetLastModified(FixedOffset::west_opt(0).unwrap().with_ymd_and_hms(1998, 01, 12, 09, 25, 56).unwrap()), - Property::ResourceType(vec![]), - Property::SupportedLock(vec![ + prop: AnyProp(vec![ + AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600).unwrap().with_ymd_and_hms(1997, 12, 01, 18, 27, 21).unwrap())), + AnyProperty::Value(Property::DisplayName("Example HTML resource".into())), + AnyProperty::Value(Property::GetContentLength(4525)), + AnyProperty::Value(Property::GetContentType("text/html".into())), + AnyProperty::Value(Property::GetEtag(r#""zzyzx""#.into())), + AnyProperty::Value(Property::GetLastModified(FixedOffset::west_opt(0).unwrap().with_ymd_and_hms(1998, 01, 12, 09, 25, 56).unwrap())), + //@FIXME know bug, can't disambiguate between an empty resource + //type value and a request resource type + AnyProperty::Request(PropertyRequest::ResourceType), + AnyProperty::Value(Property::SupportedLock(vec![ LockEntry { lockscope: LockScope::Exclusive, locktype: LockType::Write, @@ -923,7 +945,7 @@ mod tests { lockscope: LockScope::Shared, locktype: LockType::Write, }, - ]), + ])), ]), status: Status(http::status::StatusCode::OK), error: None, diff --git a/aero-dav/src/encoder.rs b/aero-dav/src/encoder.rs index 3b0bfda..1320c8a 100644 --- a/aero-dav/src/encoder.rs +++ b/aero-dav/src/encoder.rs @@ -48,7 +48,7 @@ impl<E: Extension> QWrite for PropertyUpdate<E> { /// PROPFIND RESPONSE, PROPPATCH RESPONSE, COPY RESPONSE, MOVE RESPONSE /// DELETE RESPONSE, -impl<E: Extension, N: Node<N>> QWrite for Multistatus<E,N> { +impl<E: Extension> QWrite for Multistatus<E> { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { let start = xml.create_dav_element("multistatus"); let end = start.to_end(); @@ -154,6 +154,28 @@ impl<E: Extension> QWrite for PropName<E> { } } +impl<E: Extension> QWrite for AnyProp<E> { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + let start = xml.create_dav_element("prop"); + let end = start.to_end(); + + xml.q.write_event_async(Event::Start(start.clone())).await?; + for propname in &self.0 { + propname.qwrite(xml).await?; + } + xml.q.write_event_async(Event::End(end)).await + } +} + +impl<E: Extension> QWrite for AnyProperty<E> { + async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { + match self { + Self::Request(v) => v.qwrite(xml).await, + Self::Value(v) => v.qwrite(xml).await, + } + } +} + impl QWrite for Href { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { @@ -166,7 +188,7 @@ impl QWrite for Href { } } -impl<E: Extension, N: Node<N>> QWrite for Response<E,N> { +impl<E: Extension> QWrite for Response<E> { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { let start = xml.create_dav_element("response"); let end = start.to_end(); @@ -186,7 +208,7 @@ impl<E: Extension, N: Node<N>> QWrite for Response<E,N> { } } -impl<E: Extension, N: Node<N>> QWrite for StatusOrPropstat<E,N> { +impl<E: Extension> QWrite for StatusOrPropstat<E> { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { match self { Self::Status(many_href, status) => { @@ -244,7 +266,7 @@ impl QWrite for Location { } } -impl<E: Extension, N: Node<N>> QWrite for PropStat<E,N> { +impl<E: Extension> QWrite for PropStat<E> { async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> { let start = xml.create_dav_element("propstat"); let end = start.to_end(); @@ -672,7 +694,7 @@ mod tests { #[tokio::test] async fn basic_multistatus() { - let orig = Multistatus::<Core, PropName<Core>> { + let orig = Multistatus::<Core> { responses: vec![], responsedescription: Some(ResponseDescription("Hello world".into())) }; @@ -683,7 +705,7 @@ mod tests { </D:multistatus>"#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - assert_eq!(deserialize::<Multistatus::<Core, PropName<Core>>>(got.as_str()).await, orig) + assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig) } @@ -722,17 +744,17 @@ mod tests { #[tokio::test] async fn rfc_propname_res() { - let orig = Multistatus::<Core, PropName<Core>> { + let orig = Multistatus::<Core> { responses: vec![ Response { status_or_propstat: StatusOrPropstat::PropStat( Href("http://www.example.com/container/".into()), vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, + prop: AnyProp(vec![ + AnyProperty::Request(PropertyRequest::CreationDate), + AnyProperty::Request(PropertyRequest::DisplayName), + AnyProperty::Request(PropertyRequest::ResourceType), + AnyProperty::Request(PropertyRequest::SupportedLock), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -747,15 +769,15 @@ mod tests { status_or_propstat: StatusOrPropstat::PropStat( Href("http://www.example.com/container/front.html".into()), vec![PropStat { - prop: PropName(vec![ - PropertyRequest::CreationDate, - PropertyRequest::DisplayName, - PropertyRequest::GetContentLength, - PropertyRequest::GetContentType, - PropertyRequest::GetEtag, - PropertyRequest::GetLastModified, - PropertyRequest::ResourceType, - PropertyRequest::SupportedLock, + prop: AnyProp(vec![ + AnyProperty::Request(PropertyRequest::CreationDate), + AnyProperty::Request(PropertyRequest::DisplayName), + AnyProperty::Request(PropertyRequest::GetContentLength), + AnyProperty::Request(PropertyRequest::GetContentType), + AnyProperty::Request(PropertyRequest::GetEtag), + AnyProperty::Request(PropertyRequest::GetLastModified), + AnyProperty::Request(PropertyRequest::ResourceType), + AnyProperty::Request(PropertyRequest::SupportedLock), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -805,7 +827,7 @@ mod tests { assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - assert_eq!(deserialize::<Multistatus::<Core, PropName<Core>>>(got.as_str()).await, orig) + assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig) } #[tokio::test] @@ -825,20 +847,20 @@ mod tests { async fn rfc_allprop_res() { use chrono::{FixedOffset,TimeZone}; - let orig = Multistatus::<Core, PropValue<Core>> { + let orig = Multistatus::<Core> { responses: vec![ Response { status_or_propstat: StatusOrPropstat::PropStat( Href("/container/".into()), vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600) + prop: AnyProp(vec![ + AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600) .unwrap() .with_ymd_and_hms(1997, 12, 1, 17, 42, 21) - .unwrap()), - Property::DisplayName("Example collection".into()), - Property::ResourceType(vec![ResourceType::Collection]), - Property::SupportedLock(vec![ + .unwrap())), + AnyProperty::Value(Property::DisplayName("Example collection".into())), + AnyProperty::Value(Property::ResourceType(vec![ResourceType::Collection])), + AnyProperty::Value(Property::SupportedLock(vec![ LockEntry { lockscope: LockScope::Exclusive, locktype: LockType::Write, @@ -847,7 +869,7 @@ mod tests { lockscope: LockScope::Shared, locktype: LockType::Write, }, - ]), + ])), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -862,21 +884,23 @@ mod tests { status_or_propstat: StatusOrPropstat::PropStat( Href("/container/front.html".into()), vec![PropStat { - prop: PropValue(vec![ - Property::CreationDate(FixedOffset::west_opt(8 * 3600) + prop: AnyProp(vec![ + AnyProperty::Value(Property::CreationDate(FixedOffset::west_opt(8 * 3600) .unwrap() .with_ymd_and_hms(1997, 12, 1, 18, 27, 21) - .unwrap()), - Property::DisplayName("Example HTML resource".into()), - Property::GetContentLength(4525), - Property::GetContentType("text/html".into()), - Property::GetEtag(r#""zzyzx""#.into()), - Property::GetLastModified(FixedOffset::east_opt(0) + .unwrap())), + AnyProperty::Value(Property::DisplayName("Example HTML resource".into())), + AnyProperty::Value(Property::GetContentLength(4525)), + AnyProperty::Value(Property::GetContentType("text/html".into())), + AnyProperty::Value(Property::GetEtag(r#""zzyzx""#.into())), + AnyProperty::Value(Property::GetLastModified(FixedOffset::east_opt(0) .unwrap() .with_ymd_and_hms(1998, 1, 12, 9, 25, 56) - .unwrap()), - Property::ResourceType(vec![]), - Property::SupportedLock(vec![ + .unwrap())), + //@FIXME know bug, can't disambiguate between an empty resource + //type value and a request resource type + AnyProperty::Request(PropertyRequest::ResourceType), + AnyProperty::Value(Property::SupportedLock(vec![ LockEntry { lockscope: LockScope::Exclusive, locktype: LockType::Write, @@ -885,7 +909,7 @@ mod tests { lockscope: LockScope::Shared, locktype: LockType::Write, }, - ]), + ])), ]), status: Status(http::status::StatusCode::OK), error: None, @@ -970,7 +994,7 @@ mod tests { </D:multistatus>"#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - assert_eq!(deserialize::<Multistatus::<Core, PropValue<Core>>>(got.as_str()).await, orig) + assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig) } #[tokio::test] @@ -1025,7 +1049,7 @@ mod tests { #[tokio::test] async fn rfc_delete_locked2() { - let orig = Multistatus::<Core, PropValue<Core>> { + let orig = Multistatus::<Core> { responses: vec![Response { status_or_propstat: StatusOrPropstat::Status( vec![Href("http://www.example.com/container/resource3".into())], @@ -1051,7 +1075,7 @@ mod tests { </D:multistatus>"#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); - assert_eq!(deserialize::<Multistatus::<Core, PropValue<Core>>>(got.as_str()).await, orig) + assert_eq!(deserialize::<Multistatus::<Core>>(got.as_str()).await, orig) } #[tokio::test] diff --git a/aero-dav/src/types.rs b/aero-dav/src/types.rs index 79e98fd..9457a8f 100644 --- a/aero-dav/src/types.rs +++ b/aero-dav/src/types.rs @@ -331,8 +331,8 @@ pub enum LockType { /// /// <!ELEMENT multistatus (response*, responsedescription?) > #[derive(Debug, PartialEq, Clone)] -pub struct Multistatus<E: Extension, N: xml::Node<N>> { - pub responses: Vec<Response<E, N>>, +pub struct Multistatus<E: Extension> { + pub responses: Vec<Response<E>>, pub responsedescription: Option<ResponseDescription>, } @@ -387,6 +387,9 @@ pub struct PropName<E: Extension>(pub Vec<PropertyRequest<E>>); #[derive(Debug, PartialEq, Clone)] pub struct PropValue<E: Extension>(pub Vec<Property<E>>); +#[derive(Debug, PartialEq, Clone)] +pub struct AnyProp<E: Extension>(pub Vec<AnyProperty<E>>); + /// 14.19. propertyupdate XML Element /// /// Name: propertyupdate @@ -462,14 +465,19 @@ pub enum PropFind<E: Extension> { /// the properties named in 'prop'. /// /// <!ELEMENT propstat (prop, status, error?, responsedescription?) > +/// +/// --- +/// +/// #[derive(Debug, PartialEq, Clone)] -pub struct PropStat<E: Extension, N: xml::Node<N>> { - pub prop: N, +pub struct PropStat<E: Extension> { + pub prop: AnyProp<E>, pub status: Status, pub error: Option<Error<E>>, pub responsedescription: Option<ResponseDescription>, } + /// 14.23. remove XML Element /// /// Name: remove @@ -512,16 +520,16 @@ pub struct Remove<E: Extension>(pub PropName<E>); /// --- rewritten as --- /// <!ELEMENT response ((href+, status)|(href, propstat+), error?, responsedescription?, location?> #[derive(Debug, PartialEq, Clone)] -pub enum StatusOrPropstat<E: Extension, N: xml::Node<N>> { +pub enum StatusOrPropstat<E: Extension> { // One status, multiple hrefs... Status(Vec<Href>, Status), // A single href, multiple properties... - PropStat(Href, Vec<PropStat<E, N>>), + PropStat(Href, Vec<PropStat<E>>), } #[derive(Debug, PartialEq, Clone)] -pub struct Response<E: Extension, N: xml::Node<N>> { - pub status_or_propstat: StatusOrPropstat<E, N>, +pub struct Response<E: Extension> { + pub status_or_propstat: StatusOrPropstat<E>, pub error: Option<Error<E>>, pub responsedescription: Option<ResponseDescription>, pub location: Option<Location>, @@ -645,6 +653,12 @@ pub enum Timeout { /// 4.2. Server implementors SHOULD strip LWS from these values before /// using as WebDAV property values. #[derive(Debug, PartialEq, Clone)] +pub enum AnyProperty<E: Extension> { + Request(PropertyRequest<E>), + Value(Property<E>), +} + +#[derive(Debug, PartialEq, Clone)] pub enum PropertyRequest<E: Extension> { CreationDate, DisplayName, diff --git a/aero-dav/src/xml.rs b/aero-dav/src/xml.rs index 827e9d0..26f54cc 100644 --- a/aero-dav/src/xml.rs +++ b/aero-dav/src/xml.rs @@ -258,6 +258,17 @@ impl<T: IRead> Reader<T> { Ok(evt) } + pub async fn open_start(&mut self, ns: &[u8], key: &str) -> Result<Event<'static>, ParsingError> { + let evt = match self.peek() { + Event::Start(_) if self.is_tag(ns, key) => self.next().await?, + _ => return Err(ParsingError::Recoverable), + }; + + //println!("open tag {:?}", evt); + self.parents.push(evt.clone()); + Ok(evt) + } + pub async fn maybe_open(&mut self, ns: &[u8], key: &str) -> Result<Option<Event<'static>>, ParsingError> { match self.open(ns, key).await { Ok(v) => Ok(Some(v)), @@ -266,6 +277,14 @@ impl<T: IRead> Reader<T> { } } + pub async fn maybe_open_start(&mut self, ns: &[u8], key: &str) -> Result<Option<Event<'static>>, ParsingError> { + match self.open_start(ns, key).await { + Ok(v) => Ok(Some(v)), + Err(ParsingError::Recoverable) => Ok(None), + Err(e) => Err(e), + } + } + pub fn prev_attr(&self, attr: &str) -> Option<String> { match &self.prev { Event::Start(bs) | Event::Empty(bs) => match bs.try_get_attribute(attr) { diff --git a/aero-proto/src/dav.rs b/aero-proto/src/dav.rs index 252cae8..480d163 100644 --- a/aero-proto/src/dav.rs +++ b/aero-proto/src/dav.rs @@ -375,18 +375,18 @@ trait DavNode: Send { // ----- common /// building DAV responses - fn multistatus_name(&self, user: &ArcUser, depth: dav::Depth) -> dav::Multistatus<Calendar, dav::PropName<Calendar>> { + fn multistatus_name(&self, user: &ArcUser, depth: dav::Depth) -> dav::Multistatus<Calendar> { let mut names = vec![(self.path(user), self.supported_properties(user))]; if matches!(depth, dav::Depth::One | dav::Depth::Infinity) { names.extend(self.children(user).iter().map(|c| (c.path(user), c.supported_properties(user)))); } - dav::Multistatus::<Calendar, dav::PropName<Calendar>> { + dav::Multistatus::<Calendar> { responses: names.into_iter().map(|(url, names)| dav::Response { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href(url), vec![dav::PropStat { - prop: names, + prop: dav::AnyProp(names.0.into_iter().map(dav::AnyProperty::Request).collect()), status: dav::Status(hyper::StatusCode::OK), error: None, responsedescription: None, @@ -400,7 +400,7 @@ trait DavNode: Send { } } - fn multistatus_val(&self, user: &ArcUser, props: &dav::PropName<Calendar>, depth: dav::Depth) -> dav::Multistatus<Calendar, dav::PropValue<Calendar>> { + fn multistatus_val(&self, user: &ArcUser, props: &dav::PropName<Calendar>, depth: dav::Depth) -> dav::Multistatus<Calendar> { let mut values = vec![(self.path(user), self.properties(user, props))]; if matches!(depth, dav::Depth::One | dav::Depth::Infinity) { values.extend(self @@ -410,12 +410,12 @@ trait DavNode: Send { ); } - dav::Multistatus::<Calendar, dav::PropValue<Calendar>> { + dav::Multistatus::<Calendar> { responses: values.into_iter().map(|(url, propval)| dav::Response { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href(url), vec![dav::PropStat { - prop: propval, + prop: dav::AnyProp(propval.0.into_iter().map(dav::AnyProperty::Value).collect()), status: dav::Status(hyper::StatusCode::OK), error: None, responsedescription: None, |