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}