Skip to main content

contacts_client/
lib.rs

1use reqwest::blocking::{
2    Client,
3};
4
5use contacts_core::{
6    //MultiStatus,
7    //PropFindValuesRequest,
8    //PropFindValuesResponse,
9    //PropertyName,
10    //PropertyResponse,
11    Status,
12    XmlError,
13};
14
15mod compliance;
16mod header;
17
18pub use compliance::{
19    Compliance,
20};
21
22/// The `Error`s that may occur when making a `WebDAV` request.
23#[derive(Debug)]
24#[derive(thiserror::Error)]
25pub enum Error {
26    #[error("WebDAV compliance missing")]
27    ComplianceMissing,
28
29    #[error("WebDAV compliance invalid")]
30    ComplianceInvalid,
31
32    #[error(transparent)]
33    Reqwest(#[from] reqwest::Error),
34
35    #[error(transparent)]
36    Xml(#[from] XmlError),
37}
38
39/// A synchronous `Client` to make `WebDAV` requests with.
40pub struct ContactsClient {
41    reqwest: Client,
42    base_url: String,
43    username: String,
44    password: String,
45    pub compliance: Compliance,
46    propfind_method: reqwest::Method,
47}
48
49impl ContactsClient {
50    pub fn new(
51        user_agent: &str,
52        base_url: String,
53        username: String,
54        password: String,
55    ) -> Result<Self, Error> {
56        let propfind_method = reqwest::Method::from_bytes(b"PROPFIND")
57            .expect("PROPFIND method");
58
59        let reqwest = Client::builder()
60            .user_agent(user_agent)
61            .build()?;
62
63        // OPTIONS & compliance classes
64        let response = reqwest.request(reqwest::Method::OPTIONS, &base_url)
65            .basic_auth(&username, Some(&password))
66            .send()?
67            .error_for_status()?;
68        let Some(dav) = response.headers().get("dav") else {
69            return Err(Error::ComplianceMissing);
70        };
71        let Ok(dav) = dav.to_str() else {
72            return Err(Error::ComplianceInvalid);
73        };
74        let compliance = Compliance::from_str(dav);
75        if !compliance.class_1 {
76            return Err(Error::ComplianceInvalid);
77        }
78
79        Ok(Self {
80            reqwest,
81            base_url,
82            username,
83            password,
84            compliance,
85            propfind_method,
86        })
87    }
88
89    //pub fn current_user_principal(&self) -> Result<String, Error> {
90    //    let request = PropFindValuesRequest {
91    //        names: &[
92    //            PropertyName::AddressBookDescription,
93    //            PropertyName::CurrentUserPrincipal,
94    //            PropertyName::ResourceType,
95    //        ],
96    //    }
97    //        .to_xml()?;
98
99    //    let response = self.reqwest.request(
100    //        self.propfind_method.clone(),
101    //        &self.base_url,
102    //    )
103    //        .basic_auth(&self.username, Some(&self.password))
104    //        .header(header::DEPTH, header::DEPTH__0)
105    //        .header(header::CONTENT_TYPE, header::CONTENT_TYPE__XML)
106    //        .body(request)
107    //        .send()?;
108
109    //    if !response.status().is_success() {
110    //        panic!("PROPFIND {} {}", response.status(), response.text()?);
111    //    }
112
113    //    let bytes = response.bytes()?;
114    //    let multi: MultiStatus<PropFindValuesResponse>
115    //        = MultiStatus::from_xml(bytes)?;
116    //    for response in multi.responses {
117    //        for status in response.statuses {
118    //            if let Status::Ok = status.status {
119    //                for value in status.values {
120    //                    if let PropertyResponse::CurrentUserPrincipal(Some(href)) = value {
121    //                        return Ok(href);
122    //                    }
123    //                }
124    //            }
125    //        }
126    //    }
127
128    //    todo!()
129    //}
130}
131