use std::io::Cursor; use quick_xml::Error as QError; use quick_xml::events::{Event, BytesEnd, BytesStart, BytesText}; use quick_xml::writer::{ElementWriter, Writer}; use quick_xml::name::PrefixDeclaration; use tokio::io::AsyncWrite; use super::types::*; //-------------- TRAITS ---------------------- /// Basic encode trait to make a type encodable pub trait QuickWritable { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError>; } /// Encoding context pub trait Context: Extension { fn child(&self) -> Self; fn create_dav_element(&self, name: &str) -> BytesStart; async fn hook_error(&self, err: &Self::Error, xml: &mut Writer) -> Result<(), QError>; async fn hook_property(&self, prop: &Self::Property, xml: &mut Writer) -> Result<(), QError>; async fn hook_propertyrequest(&self, prop: &Self::PropertyRequest, xml: &mut Writer) -> Result<(), QError>; async fn hook_resourcetype(&self, prop: &Self::ResourceType, xml: &mut Writer) -> Result<(), QError>; } /// -------------- NoExtension Encoding Context impl Context for NoExtension { fn child(&self) -> Self { Self { root: false } } fn create_dav_element(&self, name: &str) -> BytesStart { let mut start = BytesStart::new(format!("D:{}", name)); if self.root { start.push_attribute(("xmlns:D", "DAV:")); } start } async fn hook_error(&self, err: &Disabled, xml: &mut Writer) -> Result<(), QError> { unreachable!(); } async fn hook_property(&self, prop: &Disabled, xml: &mut Writer) -> Result<(), QError> { unreachable!(); } async fn hook_propertyrequest(&self, prop: &Disabled, xml: &mut Writer) -> Result<(), QError> { unreachable!(); } async fn hook_resourcetype(&self, restype: &Disabled, xml: &mut Writer) -> Result<(), QError> { unreachable!(); } } //--------------------- ENCODING -------------------- // --- XML ROOTS /// PROPFIND REQUEST impl QuickWritable for PropFind { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("propfind"); let end = start.to_end(); let ctx = ctx.child(); xml.write_event_async(Event::Start(start.clone())).await?; match self { Self::PropName => xml.write_event_async(Event::Empty(ctx.create_dav_element("propname"))).await?, Self::AllProp(maybe_include) => { xml.write_event_async(Event::Empty(ctx.create_dav_element("allprop"))).await?; if let Some(include) = maybe_include { include.write(xml, ctx.child()).await?; } }, Self::Prop(many_propreq) => { let start = ctx.create_dav_element("prop"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for propreq in many_propreq.iter() { propreq.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; }, } xml.write_event_async(Event::End(end)).await } } /// PROPFIND RESPONSE, PROPPATCH RESPONSE, COPY RESPONSE, MOVE RESPONSE /// DELETE RESPONSE, impl QuickWritable for Multistatus { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("multistatus"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for response in self.responses.iter() { response.write(xml, ctx.child()).await?; } if let Some(description) = &self.responsedescription { description.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; Ok(()) } } /// LOCK REQUEST impl QuickWritable for LockInfo { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("lockinfo"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.lockscope.write(xml, ctx.child()).await?; self.locktype.write(xml, ctx.child()).await?; if let Some(owner) = &self.owner { owner.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await } } /// SOME LOCK RESPONSES impl QuickWritable for Prop { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("prop"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; match self { Self::Name(many_names) => { for propname in many_names { propname.write(xml, ctx.child()).await?; } }, Self::Value(many_values) => { for propval in many_values { propval.write(xml, ctx.child()).await?; } } }; xml.write_event_async(Event::End(end)).await?; Ok(()) } } // --- XML inner elements impl QuickWritable for Href { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("href"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(&self.0))).await?; xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for Response { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("response"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.href.write(xml, ctx.child()).await?; self.status_or_propstat.write(xml, ctx.child()).await?; if let Some(error) = &self.error { error.write(xml, ctx.child()).await?; } if let Some(responsedescription) = &self.responsedescription { responsedescription.write(xml, ctx.child()).await?; } if let Some(location) = &self.location { location.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for StatusOrPropstat { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { match self { Self::Status(status) => status.write(xml, ctx.child()).await, Self::PropStat(propstat_list) => { for propstat in propstat_list.iter() { propstat.write(xml, ctx.child()).await?; } Ok(()) } } } } impl QuickWritable for Status { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("status"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; let txt = format!("HTTP/1.1 {} {}", self.0.as_str(), self.0.canonical_reason().unwrap_or("No reason")); xml.write_event_async(Event::Text(BytesText::new(&txt))).await?; xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for ResponseDescription { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("responsedescription"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(&self.0))).await?; xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for Location { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("location"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.0.write(xml, ctx.child()).await?; xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for PropStat { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("propstat"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.prop.write(xml, ctx.child()).await?; self.status.write(xml, ctx.child()).await?; if let Some(error) = &self.error { error.write(xml, ctx.child()).await?; } if let Some(description) = &self.responsedescription { description.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for Property { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { use Property::*; match self { CreationDate(date) => { // 1997-12-01T17:42:21-08:00 let start = ctx.create_dav_element("creationdate"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(&date.to_rfc3339()))).await?; xml.write_event_async(Event::End(end)).await?; }, DisplayName(name) => { // Example collection let start = ctx.create_dav_element("displayname"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(name))).await?; xml.write_event_async(Event::End(end)).await?; }, GetContentLanguage(lang) => { let start = ctx.create_dav_element("getcontentlanguage"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(lang))).await?; xml.write_event_async(Event::End(end)).await?; }, GetContentLength(len) => { // 4525 let start = ctx.create_dav_element("getcontentlength"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(&len.to_string()))).await?; xml.write_event_async(Event::End(end)).await?; }, GetContentType(ct) => { // text/html let start = ctx.create_dav_element("getcontenttype"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(&ct))).await?; xml.write_event_async(Event::End(end)).await?; }, GetEtag(et) => { // "zzyzx" let start = ctx.create_dav_element("getetag"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(et))).await?; xml.write_event_async(Event::End(end)).await?; }, GetLastModified(date) => { // Mon, 12 Jan 1998 09:25:56 GMT let start = ctx.create_dav_element("getlastmodified"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; xml.write_event_async(Event::Text(BytesText::new(&date.to_rfc2822()))).await?; xml.write_event_async(Event::End(end)).await?; }, LockDiscovery(many_locks) => { // ... let start = ctx.create_dav_element("lockdiscovery"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for lock in many_locks.iter() { lock.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; }, ResourceType(many_types) => { // // // // // // let start = ctx.create_dav_element("resourcetype"); if many_types.is_empty() { xml.write_event_async(Event::Empty(start)).await?; } else { let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for restype in many_types.iter() { restype.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; } }, SupportedLock(many_entries) => { // // ... let start = ctx.create_dav_element("supportedlock"); if many_entries.is_empty() { xml.write_event_async(Event::Empty(start)).await?; } else { let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for entry in many_entries.iter() { entry.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; } }, Extension(inner) => { ctx.hook_property(inner, xml).await?; }, }; Ok(()) } } impl QuickWritable for ResourceType { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { match self { Self::Collection => xml.write_event_async(Event::Empty(ctx.create_dav_element("collection"))).await, Self::Extension(inner) => ctx.hook_resourcetype(inner, xml).await, } } } impl QuickWritable for Include { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("include"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for prop in self.0.iter() { prop.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await } } impl QuickWritable for PropertyRequest { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { use PropertyRequest::*; let mut atom = (async |c| xml.write_event_async(Event::Empty(ctx.create_dav_element(c))).await); match self { CreationDate => atom("creationdate").await, DisplayName => atom("displayname").await, GetContentLanguage => atom("getcontentlanguage").await, GetContentLength => atom("getcontentlength").await, GetContentType => atom("getcontenttype").await, GetEtag => atom("getetag").await, GetLastModified => atom("getlastmodified").await, LockDiscovery => atom("lockdiscovery").await, ResourceType => atom("resourcetype").await, SupportedLock => atom("supportedlock").await, Extension(inner) => ctx.hook_propertyrequest(inner, xml).await, } } } impl QuickWritable for ActiveLock { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { // // // // infinity // // http://example.org/~ejw/contact.html // // Second-604800 // // urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4 // // // http://example.com/workspace/webdav/proposal.doc // // let start = ctx.create_dav_element("activelock"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.locktype.write(xml, ctx.child()).await?; self.lockscope.write(xml, ctx.child()).await?; self.depth.write(xml, ctx.child()).await?; if let Some(owner) = &self.owner { owner.write(xml, ctx.child()).await?; } if let Some(timeout) = &self.timeout { timeout.write(xml, ctx.child()).await?; } if let Some(locktoken) = &self.locktoken { locktoken.write(xml, ctx.child()).await?; } self.lockroot.write(xml, ctx.child()).await?; xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for LockType { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("locktype"); let end = start.to_end(); let ctx = ctx.child(); xml.write_event_async(Event::Start(start.clone())).await?; match self { Self::Write => xml.write_event_async(Event::Empty(ctx.create_dav_element("write"))).await?, }; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for LockScope { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("lockscope"); let end = start.to_end(); let ctx = ctx.child(); xml.write_event_async(Event::Start(start.clone())).await?; match self { Self::Exclusive => xml.write_event_async(Event::Empty(ctx.create_dav_element("exclusive"))).await?, Self::Shared => xml.write_event_async(Event::Empty(ctx.create_dav_element("shared"))).await?, }; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for Owner { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("owner"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; if let Some(txt) = &self.txt { xml.write_event_async(Event::Text(BytesText::new(&txt))).await?; } if let Some(href) = &self.url { href.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await } } impl QuickWritable for Depth { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("depth"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; match self { Self::Zero => xml.write_event_async(Event::Text(BytesText::new("0"))).await?, Self::One => xml.write_event_async(Event::Text(BytesText::new("1"))).await?, Self::Infinity => xml.write_event_async(Event::Text(BytesText::new("infinity"))).await?, }; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for Timeout { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("timeout"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; match self { Self::Seconds(count) => { let txt = format!("Second-{}", count); xml.write_event_async(Event::Text(BytesText::new(&txt))).await? }, Self::Infinite => xml.write_event_async(Event::Text(BytesText::new("Infinite"))).await? }; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for LockToken { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("locktoken"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.0.write(xml, ctx.child()).await?; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for LockRoot { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("lockroot"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.0.write(xml, ctx.child()).await?; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for LockEntry { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("lockentry"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; self.lockscope.write(xml, ctx.child()).await?; self.locktype.write(xml, ctx.child()).await?; xml.write_event_async(Event::End(end)).await } } impl QuickWritable for Error { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { let start = ctx.create_dav_element("error"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for violation in &self.0 { violation.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; Ok(()) } } impl QuickWritable for Violation { async fn write(&self, xml: &mut Writer, ctx: C) -> Result<(), QError> { match self { Violation::LockTokenMatchesRequestUri => xml.write_event_async(Event::Empty(ctx.create_dav_element("lock-token-matches-request-uri"))).await?, Violation::LockTokenSubmitted(hrefs) => { let start = ctx.create_dav_element("lock-token-submitted"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for href in hrefs { href.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; }, Violation::NoConflictingLock(hrefs) => { let start = ctx.create_dav_element("no-conflicting-lock"); let end = start.to_end(); xml.write_event_async(Event::Start(start.clone())).await?; for href in hrefs { href.write(xml, ctx.child()).await?; } xml.write_event_async(Event::End(end)).await?; }, Violation::NoExternalEntities => xml.write_event_async(Event::Empty(ctx.create_dav_element("no-external-entities"))).await?, Violation::PreservedLiveProperties => xml.write_event_async(Event::Empty(ctx.create_dav_element("preserved-live-properties"))).await?, Violation::PropfindFiniteDepth => xml.write_event_async(Event::Empty(ctx.create_dav_element("propfind-finite-depth"))).await?, Violation::CannotModifyProtectedProperty => xml.write_event_async(Event::Empty(ctx.create_dav_element("cannot-modify-protected-property"))).await?, Violation::Extension(inner) => { ctx.hook_error(inner, xml).await?; }, }; Ok(()) } } #[cfg(test)] mod tests { use super::*; use tokio::io::AsyncWriteExt; /// To run only the unit tests and avoid the behavior ones: /// cargo test --bin aerogramme async fn serialize>(ctx: C, elem: &Q) -> String { let mut buffer = Vec::new(); let mut tokio_buffer = tokio::io::BufWriter::new(&mut buffer); let mut writer = Writer::new_with_indent(&mut tokio_buffer, b' ', 4); elem.write(&mut writer, ctx).await.expect("xml serialization"); tokio_buffer.flush().await.expect("tokio buffer flush"); let got = std::str::from_utf8(buffer.as_slice()).unwrap(); return got.into() } #[tokio::test] async fn basic_href() { let got = serialize( NoExtension { root: false }, &Href("/SOGo/dav/so/".into()) ).await; let expected = "/SOGo/dav/so/"; assert_eq!(&got, expected); } #[tokio::test] async fn basic_multistatus() { let got = serialize( NoExtension { root: true }, &Multistatus { responses: vec![], responsedescription: Some(ResponseDescription("Hello world".into())) }, ).await; let expected = r#" Hello world "#; assert_eq!(&got, expected); } #[tokio::test] async fn rfc_error_delete_locked() { let got = serialize( NoExtension { root: true }, &Error(vec![ Violation::LockTokenSubmitted(vec![ Href("/locked/".into()) ]) ]), ).await; let expected = r#" /locked/ "#; assert_eq!(&got, expected); } #[tokio::test] async fn rfc_propname_req() { let got = serialize( NoExtension { root: true }, &PropFind::PropName, ).await; let expected = r#" "#; assert_eq!(&got, expected); } #[tokio::test] async fn rfc_propname_res() { let got = serialize( NoExtension { root: true }, &Multistatus { responses: vec![ Response { href: Href("http://www.example.com/container/".into()), status_or_propstat: StatusOrPropstat::PropStat(vec![PropStat { prop: Prop::Name(vec![ PropertyRequest::CreationDate, PropertyRequest::DisplayName, PropertyRequest::ResourceType, PropertyRequest::SupportedLock, ]), status: Status(http::status::StatusCode::OK), error: None, responsedescription: None, }]), error: None, responsedescription: None, location: None, }, Response { href: Href("http://www.example.com/container/front.html".into()), status_or_propstat: StatusOrPropstat::PropStat(vec![PropStat { prop: Prop::Name(vec![ PropertyRequest::CreationDate, PropertyRequest::DisplayName, PropertyRequest::GetContentLength, PropertyRequest::GetContentType, PropertyRequest::GetEtag, PropertyRequest::GetLastModified, PropertyRequest::ResourceType, PropertyRequest::SupportedLock, ]), status: Status(http::status::StatusCode::OK), error: None, responsedescription: None, }]), error: None, responsedescription: None, location: None, }, ], responsedescription: None, }, ).await; let expected = r#" http://www.example.com/container/ HTTP/1.1 200 OK http://www.example.com/container/front.html HTTP/1.1 200 OK "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); } #[tokio::test] async fn rfc_allprop_req() { let got = serialize( NoExtension { root: true }, &PropFind::AllProp(None), ).await; let expected = r#" "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); } #[tokio::test] async fn rfc_allprop_res() { use chrono::{DateTime,FixedOffset,TimeZone}; let got = serialize( NoExtension { root: true }, &Multistatus { responses: vec![ Response { href: Href("/container/".into()), status_or_propstat: StatusOrPropstat::PropStat(vec![PropStat { prop: Prop::Value(vec![ 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![ LockEntry { lockscope: LockScope::Exclusive, locktype: LockType::Write, }, LockEntry { lockscope: LockScope::Shared, locktype: LockType::Write, }, ]), ]), status: Status(http::status::StatusCode::OK), error: None, responsedescription: None, }]), error: None, responsedescription: None, location: None, }, Response { href: Href("/container/front.html".into()), status_or_propstat: StatusOrPropstat::PropStat(vec![PropStat { prop: Prop::Value(vec![ 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() .with_ymd_and_hms(1998, 1, 12, 9, 25, 56) .unwrap()), Property::ResourceType(vec![]), Property::SupportedLock(vec![ LockEntry { lockscope: LockScope::Exclusive, locktype: LockType::Write, }, LockEntry { lockscope: LockScope::Shared, locktype: LockType::Write, }, ]), ]), status: Status(http::status::StatusCode::OK), error: None, responsedescription: None, }]), error: None, responsedescription: None, location: None, }, ], responsedescription: None, } ).await; let expected = r#" /container/ 1997-12-01T17:42:21-08:00 Example collection HTTP/1.1 200 OK /container/front.html 1997-12-01T18:27:21-08:00 Example HTML resource 4525 text/html "zzyzx" Mon, 12 Jan 1998 09:25:56 +0000 HTTP/1.1 200 OK "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); } #[tokio::test] async fn rfc_allprop_include() { let got = serialize( NoExtension { root: true }, &PropFind::AllProp(Some(Include(vec![ PropertyRequest::DisplayName, PropertyRequest::ResourceType, ]))), ).await; let expected = r#" "#; assert_eq!(&got, expected, "\n---GOT---\n{got}\n---EXP---\n{expected}\n"); } }