use http::Method;
use crate::{
Depth,
dav::{ListedResource, WebDavError, extract_listed_resources},
names,
requests::{DavRequest, ParseResponseError, PreparedRequest, xml_content_type_header},
xmlutils::XmlNode,
};
#[derive(Debug, Clone)]
pub struct ListResources<'a> {
collection_href: &'a str,
}
impl<'a> ListResources<'a> {
#[must_use]
pub fn new(collection_href: &'a str) -> Self {
Self { collection_href }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListResourcesResponse {
pub resources: Vec<ListedResource>,
}
impl DavRequest for ListResources<'_> {
type Response = ListResourcesResponse;
type ParseError = ParseResponseError;
type Error<E> = WebDavError<E>;
fn prepare_request(&self) -> Result<PreparedRequest, http::Error> {
let mut prop = XmlNode::new(&names::PROP);
prop.children = vec![
XmlNode::new(&names::RESOURCETYPE),
XmlNode::new(&names::GETCONTENTTYPE),
XmlNode::new(&names::GETETAG),
];
let mut propfind = XmlNode::new(&names::PROPFIND);
propfind.children = vec![prop];
Ok(PreparedRequest {
method: Method::from_bytes(b"PROPFIND")?,
path: self.collection_href.to_string(),
body: propfind.render_node(),
headers: vec![
("Depth".to_string(), Depth::One.to_string()),
xml_content_type_header(),
],
})
}
fn parse_response(
&self,
parts: &http::response::Parts,
body: &[u8],
) -> Result<Self::Response, ParseResponseError> {
if !parts.status.is_success() {
return Err(ParseResponseError::BadStatusCode(parts.status));
}
let resources = extract_listed_resources(body, self.collection_href)?;
Ok(ListResourcesResponse { resources })
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::StatusCode;
#[test]
fn test_prepare_request() {
let req = ListResources::new("/calendars/personal/");
let prepared = req.prepare_request().unwrap();
assert_eq!(prepared.method, Method::from_bytes(b"PROPFIND").unwrap());
assert_eq!(prepared.path, "/calendars/personal/");
assert!(prepared.body.contains("resourcetype"));
assert!(prepared.body.contains("getcontenttype"));
assert!(prepared.body.contains("getetag"));
assert!(prepared.body.contains(r#"xmlns:D="DAV:""#));
assert!(
prepared
.headers
.contains(&("Depth".to_string(), "1".to_string()))
);
}
#[test]
fn test_parse_response_success() {
let req = ListResources::new("/calendars/personal/");
let body = br#"<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">
<response>
<href>/calendars/personal/</href>
<propstat>
<prop>
<resourcetype><collection/></resourcetype>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/calendars/personal/event1.ics</href>
<propstat>
<prop>
<getetag>"abc123"</getetag>
<getcontenttype>text/calendar</getcontenttype>
<resourcetype/>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/calendars/personal/event2.ics</href>
<propstat>
<prop>
<getetag>"def456"</getetag>
<getcontenttype>text/calendar</getcontenttype>
<resourcetype/>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>"#;
let response = http::Response::builder()
.status(StatusCode::MULTI_STATUS)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, body).unwrap();
assert_eq!(result.resources.len(), 2);
assert_eq!(result.resources[0].href, "/calendars/personal/event1.ics");
assert_eq!(result.resources[0].etag, Some("\"abc123\"".to_string()));
assert_eq!(
result.resources[0].content_type,
Some("text/calendar".to_string())
);
assert_eq!(result.resources[1].href, "/calendars/personal/event2.ics");
assert_eq!(result.resources[1].etag, Some("\"def456\"".to_string()));
}
#[test]
fn test_parse_response_empty_collection() {
let req = ListResources::new("/calendars/empty/");
let body = br#"<?xml version="1.0" encoding="utf-8"?>
<multistatus xmlns="DAV:">
<response>
<href>/calendars/empty/</href>
<propstat>
<prop>
<resourcetype><collection/></resourcetype>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>"#;
let response = http::Response::builder()
.status(StatusCode::MULTI_STATUS)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, body).unwrap();
assert!(result.resources.is_empty());
}
#[test]
fn test_parse_response_bad_status() {
let req = ListResources::new("/calendars/personal/");
let response = http::Response::builder()
.status(StatusCode::FORBIDDEN)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(matches!(
result,
Err(ParseResponseError::BadStatusCode(StatusCode::FORBIDDEN))
));
}
#[test]
fn test_parse_response_not_found() {
let req = ListResources::new("/calendars/nonexistent/");
let response = http::Response::builder()
.status(StatusCode::NOT_FOUND)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, b"");
assert!(matches!(
result,
Err(ParseResponseError::BadStatusCode(StatusCode::NOT_FOUND))
));
}
}