libdav 0.10.3

CalDAV and CardDAV client implementations.
Documentation
//! Find the address book home set for a principal.

use http::Uri;

use crate::{
    dav::{FindPropertyHrefs, WebDavError},
    names,
    requests::{DavRequest, ParseResponseError, PreparedRequest},
};

/// Request to query the address book home set for a principal.
///
/// See also: <https://www.rfc-editor.org/rfc/rfc6352#section-7.1.1>
pub struct FindAddressBookHomeSet<'a> {
    inner: FindPropertyHrefs<'a>,
}

impl FindAddressBookHomeSet<'_> {
    /// Create a new request for the given principal.
    ///
    /// `principal` should be a URI pointing to the user's principal resource, for example:
    /// `https://example.com/principals/user/`.
    #[must_use]
    pub fn new(principal: &Uri) -> Self {
        Self {
            inner: FindPropertyHrefs::new(principal, &names::ADDRESSBOOK_HOME_SET),
        }
    }
}

/// Response from a [`FindAddressBookHomeSet`] request.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FindAddressBookHomeSetResponse {
    /// The address book home set URLs for the principal.
    ///
    /// These are the base URLs where address books can be found or created.
    pub home_sets: Vec<Uri>,
}

impl DavRequest for FindAddressBookHomeSet<'_> {
    type Response = FindAddressBookHomeSetResponse;
    type ParseError = ParseResponseError;
    type Error<E> = WebDavError<E>;

    fn prepare_request(&self) -> Result<PreparedRequest, http::Error> {
        self.inner.prepare_request()
    }

    fn parse_response(
        &self,
        parts: &http::response::Parts,
        body: &[u8],
    ) -> Result<Self::Response, ParseResponseError> {
        let response = self.inner.parse_response(parts, body)?;
        Ok(FindAddressBookHomeSetResponse {
            home_sets: response.hrefs,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::{Method, StatusCode};

    #[test]
    fn test_prepare_request() {
        let principal = Uri::from_static("https://example.com/principals/user/");
        let req = FindAddressBookHomeSet::new(&principal);
        let prepared = req.prepare_request().unwrap();

        assert_eq!(prepared.method, Method::from_bytes(b"PROPFIND").unwrap());
        assert_eq!(prepared.path, "/principals/user/");
        assert!(
            prepared
                .body
                .contains(r#"xmlns:CARD="urn:ietf:params:xml:ns:carddav""#)
        );
        assert!(prepared.body.contains("<CARD:addressbook-home-set/>"));
        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 principal = Uri::from_static("https://example.com/principals/user/");
        let req = FindAddressBookHomeSet::new(&principal);
        let response_xml = r#"<?xml version="1.0"?>
            <multistatus xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">
                <response>
                    <href>/principals/user/</href>
                    <propstat>
                        <prop>
                            <CARD:addressbook-home-set>
                                <href>/addressbooks/user/</href>
                            </CARD:addressbook-home-set>
                        </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.home_sets.len(), 1);
        // The href is returned as a path, but gets parsed as a relative URI
        assert_eq!(result.home_sets[0].path(), "/addressbooks/user/");
    }

    #[test]
    fn test_parse_response_multiple_home_sets() {
        let principal = Uri::from_static("https://example.com/principals/user/");
        let req = FindAddressBookHomeSet::new(&principal);
        let response_xml = r#"<?xml version="1.0"?>
            <multistatus xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">
                <response>
                    <href>/principals/user/</href>
                    <propstat>
                        <prop>
                            <CARD:addressbook-home-set>
                                <href>/addressbooks/user/</href>
                                <href>/shared-addressbooks/</href>
                            </CARD:addressbook-home-set>
                        </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.home_sets.len(), 2);
        // Hrefs are returned as paths, but get parsed as relative URIs
        assert_eq!(result.home_sets[0].path(), "/addressbooks/user/");
        assert_eq!(result.home_sets[1].path(), "/shared-addressbooks/");
    }

    #[test]
    fn test_parse_response_bad_status() {
        let principal = Uri::from_static("https://example.com/principals/user/");
        let req = FindAddressBookHomeSet::new(&principal);
        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());
    }
}