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