etebase/http_client/
mod.rs

1use std::sync::Arc;
2
3use url::Url;
4
5use crate::{error::Error, online_managers::Authenticator};
6
7use super::error::Result;
8
9mod client_impl;
10#[cfg(feature = "networking")]
11mod reqwest_client;
12
13pub use client_impl::{ClientImplementation, Response};
14
15#[cfg(feature = "networking")]
16use reqwest_client::Client as ReqwestImpl;
17
18/// The network client to use to interact with the Etebase server
19///
20/// This is in charge of actually connecting to the server and making network requests.
21/// If the `"networking"` crate feature is enabled, it uses an internal HTTP client based on
22/// the `reqwest` crate. If the feature is not enabled, an external HTTP(S) client implementation
23/// implementing the [`ClientImplementation`] trait needs to be supplied.
24#[derive(Clone)]
25pub struct Client {
26    auth_token: Option<String>,
27    pub(crate) api_base: Url,
28    #[cfg(feature = "networking")]
29    imp: Arc<ReqwestImpl>,
30    #[cfg(not(feature = "networking"))]
31    imp: Arc<Box<dyn ClientImplementation>>,
32}
33
34impl Client {
35    fn normalize_url(server_url: &str) -> Result<Url> {
36        let mut ret = Url::parse(server_url)?;
37
38        if !["http", "https"].contains(&ret.scheme()) {
39            return Err(Error::UrlParse(format!(
40                "Invalid server URL scheme, expected http or https: {}",
41                ret.scheme()
42            )));
43        }
44
45        if !ret.path().ends_with('/') {
46            ret.path_segments_mut().unwrap().push("");
47        }
48        Ok(ret)
49    }
50
51    /// Creates a new client object for the server located at `server_url`.
52    ///
53    /// The `client_name` will be used to populate the `User-Agent` header in all requests
54    /// to the server.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use etebase::Client;
60    ///
61    /// // For an application called "FancyClient"
62    /// let my_client = Client::new("FancyClient", "https://myhost.example");
63    /// ```
64    #[cfg(feature = "networking")]
65    pub fn new(client_name: &str, server_url: &str) -> Result<Self> {
66        let imp = ReqwestImpl::new(client_name)?;
67        Ok(Self {
68            api_base: Self::normalize_url(server_url)?,
69            auth_token: None,
70            imp: Arc::new(imp),
71        })
72    }
73
74    /// Creates a new client object for the server located at `server_url` using a user-supplied
75    /// HTTP(S) client implementation.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use etebase::{Client, http_custom_client::ClientImplementation};
81    /// # use etebase::http_custom_client::Response;
82    /// # struct ExternalClient;
83    ///
84    /// // For some `ExternalClient` provided by the HTTP client implementation of choice:
85    /// impl ClientImplementation for ExternalClient {
86    ///     // ...
87    /// #   fn get(&self, _: &str, _: Option<&str>) -> Response { unimplemented!() }
88    /// #   fn post(&self, _: &str, _: Option<&str>, _: Vec<u8>) -> Response { unimplemented!() }
89    /// #   fn put(&self, _: &str, _: Option<&str>, _: Vec<u8>) -> Response { unimplemented!() }
90    /// #   fn patch(&self, _: &str, _: Option<&str>, _: Vec<u8>) -> Response { unimplemented!() }
91    /// #   fn delete(&self, _: &str, _: Option<&str>) -> Response { unimplemented!() }
92    /// }
93    ///
94    /// let external_client = Box::new(ExternalClient);
95    /// let my_client = Client::new_with_impl("https://myhost.example", external_client);
96    /// ```
97    #[cfg(not(feature = "networking"))]
98    pub fn new_with_impl(server_url: &str, imp: Box<dyn ClientImplementation>) -> Result<Self> {
99        Ok(Self {
100            api_base: Self::normalize_url(server_url)?,
101            auth_token: None,
102            imp: Arc::new(imp),
103        })
104    }
105
106    /// Checks whether the [`Client`] is pointing to a valid Etebase server.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use etebase::Client;
112    ///
113    /// let invalid_client = Client::new("ExampleClient", "https://example.com").unwrap();
114    /// assert!(!invalid_client.is_server_valid().unwrap());
115    ///
116    /// let valid_client = Client::new("ExampleClient", "https://api.etebase.com/").unwrap();
117    /// assert!(valid_client.is_server_valid().unwrap());
118    /// ```
119    pub fn is_server_valid(&self) -> Result<bool> {
120        Authenticator::new(self).is_etebase_server()
121    }
122
123    pub(crate) fn set_token(&mut self, token: Option<&str>) {
124        self.auth_token = token.map(str::to_string)
125    }
126
127    pub(crate) fn token(&self) -> Option<&str> {
128        self.auth_token.as_deref()
129    }
130
131    /// Set the server url associated with this client
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use etebase::Client;
137    ///
138    /// let mut client = Client::new("ExampleClient", "https://invalid.example").unwrap();
139    /// client.set_server_url("https://another.example");
140    ///
141    /// assert_eq!(client.server_url().to_string(), "https://another.example/");
142    /// ```
143    pub fn set_server_url(&mut self, server_url: &str) -> Result<()> {
144        self.api_base = Self::normalize_url(server_url)?;
145
146        Ok(())
147    }
148
149    /// Return the server url associated with this client
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use etebase::Client;
155    ///
156    /// let client = Client::new("ExampleClient", "https://invalid.example").unwrap();
157    ///
158    /// assert_eq!(client.server_url().to_string(), "https://invalid.example/");
159    /// ```
160    pub fn server_url(&self) -> &Url {
161        &self.api_base
162    }
163
164    pub(crate) fn get(&self, url: &str) -> Result<Response> {
165        self.imp.get(url, self.auth_token.as_deref()).into_result()
166    }
167
168    pub(crate) fn post(&self, url: &str, body: Vec<u8>) -> Result<Response> {
169        self.imp
170            .post(url, self.auth_token.as_deref(), body)
171            .into_result()
172    }
173
174    pub(crate) fn put(&self, url: &str, body: Vec<u8>) -> Result<Response> {
175        self.imp
176            .put(url, self.auth_token.as_deref(), body)
177            .into_result()
178    }
179
180    pub(crate) fn patch(&self, url: &str, body: Vec<u8>) -> Result<Response> {
181        self.imp
182            .patch(url, self.auth_token.as_deref(), body)
183            .into_result()
184    }
185
186    pub(crate) fn delete(&self, url: &str) -> Result<Response> {
187        self.imp
188            .delete(url, self.auth_token.as_deref())
189            .into_result()
190    }
191}