use crate::{
Depth,
dav::{Propfind, WebDavError},
names,
requests::{DavRequest, ParseResponseError, PreparedRequest},
xmlutils::validate_xml_response,
};
pub struct GetEtag<'a> {
propfind: Propfind<'a>,
}
impl<'a> GetEtag<'a> {
#[must_use]
pub fn new(href: &'a str) -> Self {
Self {
propfind: Propfind::new(href)
.with_properties(&[&names::GETETAG])
.with_depth(Depth::Zero),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetEtagResponse {
pub etag: String,
}
impl DavRequest for GetEtag<'_> {
type Response = GetEtagResponse;
type ParseError = ParseResponseError;
type Error<E> = WebDavError<E>;
fn prepare_request(&self) -> Result<PreparedRequest, http::Error> {
self.propfind.prepare_request()
}
fn parse_response(
&self,
parts: &http::response::Parts,
body: &[u8],
) -> Result<Self::Response, ParseResponseError> {
let doc = validate_xml_response(parts, body)?;
let root = doc.root_element();
let etag_node = root
.descendants()
.find(|node| node.tag_name() == names::GETETAG)
.ok_or_else(|| {
ParseResponseError::InvalidResponse("missing getetag in response".into())
})?;
let etag = etag_node
.text()
.ok_or_else(|| ParseResponseError::InvalidResponse("missing text in getetag".into()))?
.to_string();
Ok(GetEtagResponse { etag })
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::{Method, StatusCode};
#[test]
fn test_prepare_request() {
let req = GetEtag::new("/calendar/event.ics");
let prepared = req.prepare_request().unwrap();
assert_eq!(prepared.method, Method::from_bytes(b"PROPFIND").unwrap());
assert_eq!(prepared.path, "/calendar/event.ics");
assert_eq!(
prepared.body,
r#"<D:propfind xmlns:D="DAV:"><D:prop><D:getetag/></D:prop></D:propfind>"#
);
assert_eq!(
prepared.headers,
vec![
("Depth".to_string(), "0".to_string()),
(
"Content-Type".to_string(),
"application/xml; charset=utf-8".to_string()
)
]
);
}
#[test]
fn test_parse_response() {
let req = GetEtag::new("/calendar/event.ics");
let response_xml = r#"<?xml version="1.0"?>
<multistatus xmlns="DAV:">
<response>
<href>/calendar/event.ics</href>
<propstat>
<prop><getetag>"abc123"</getetag></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, response_xml.as_bytes()).unwrap();
assert_eq!(result.etag, "\"abc123\"");
}
#[test]
fn test_parse_response_missing_etag() {
let req = GetEtag::new("/calendar/event.ics");
let response_xml = r#"<?xml version="1.0"?>
<multistatus xmlns="DAV:">
<response>
<href>/calendar/event.ics</href>
<propstat>
<prop></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, response_xml.as_bytes());
assert!(result.is_err());
}
#[test]
fn test_parse_response_bad_status() {
let req = GetEtag::new("/calendar/event.ics");
let response_xml = b"";
let response = http::Response::builder()
.status(StatusCode::NOT_FOUND)
.body(())
.unwrap();
let (parts, ()) = response.into_parts();
let result = req.parse_response(&parts, response_xml);
assert!(result.is_err());
}
}