1use std::fmt::Debug;
7
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize, de::DeserializeOwned};
10
11use crate::oauth10a::{
12 Client as OAuthClient, ClientError, Request, RestClient,
13 reqwest::{self, Method},
14};
15
16pub mod v2;
17pub mod v4;
18
19pub use oauth10a::client as oauth10a;
23
24pub const PUBLIC_ENDPOINT: &str = "https://api.clever-cloud.com";
28pub const PUBLIC_API_BRIDGE_ENDPOINT: &str = "https://api-bridge.clever-cloud.com";
29
30pub const DEFAULT_CONSUMER_KEY: &str = "T5nFjKeHH4AIlEveuGhB5S3xg8T19e";
38pub fn default_consumer_key() -> String {
39 DEFAULT_CONSUMER_KEY.to_string()
40}
41
42pub const DEFAULT_CONSUMER_SECRET: &str = "MgVMqTr6fWlf2M0tkC2MXOnhfqBWDT";
43pub fn default_consumer_secret() -> String {
44 DEFAULT_CONSUMER_SECRET.to_string()
45}
46
47#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
51#[serde(untagged)]
52pub enum Credentials {
53 OAuth1 {
54 #[serde(rename = "token")]
55 token: String,
56 #[serde(rename = "secret")]
57 secret: String,
58 #[serde(rename = "consumer-key", default = "default_consumer_key")]
59 consumer_key: String,
60 #[serde(rename = "consumer-secret", default = "default_consumer_secret")]
61 consumer_secret: String,
62 },
63 Basic {
64 #[serde(rename = "username")]
65 username: String,
66 #[serde(rename = "password")]
67 password: String,
68 },
69 Bearer {
70 #[serde(rename = "token")]
71 token: String,
72 },
73}
74
75impl Default for Credentials {
76 #[tracing::instrument(skip_all)]
77 fn default() -> Self {
78 Self::OAuth1 {
79 token: String::new(),
80 secret: String::new(),
81 consumer_key: DEFAULT_CONSUMER_KEY.to_string(),
82 consumer_secret: DEFAULT_CONSUMER_SECRET.to_string(),
83 }
84 }
85}
86
87impl From<oauth10a::Credentials> for Credentials {
88 #[tracing::instrument(skip_all)]
89 fn from(credentials: oauth10a::Credentials) -> Self {
90 match credentials {
91 oauth10a::Credentials::Bearer { token } => Self::Bearer { token },
92 oauth10a::Credentials::Basic { username, password } => {
93 Self::Basic { username, password }
94 }
95 oauth10a::Credentials::OAuth1 {
96 token,
97 secret,
98 consumer_key,
99 consumer_secret,
100 } => Self::OAuth1 {
101 token,
102 secret,
103 consumer_key,
104 consumer_secret,
105 },
106 }
107 }
108}
109
110#[allow(clippy::from_over_into)]
111impl Into<oauth10a::Credentials> for Credentials {
112 #[tracing::instrument(skip_all)]
113 fn into(self) -> oauth10a::Credentials {
114 match self {
115 Self::Bearer { token } => oauth10a::Credentials::Bearer { token },
116 Self::Basic { username, password } => {
117 oauth10a::Credentials::Basic { username, password }
118 }
119 Self::OAuth1 {
120 token,
121 secret,
122 consumer_key,
123 consumer_secret,
124 } => oauth10a::Credentials::OAuth1 {
125 token,
126 secret,
127 consumer_key,
128 consumer_secret,
129 },
130 }
131 }
132}
133
134impl Credentials {
135 #[tracing::instrument(skip_all)]
136 pub fn bearer(token: String) -> Self {
137 Self::Bearer { token }
138 }
139
140 #[tracing::instrument(skip_all)]
141 pub fn basic(username: String, password: String) -> Self {
142 Self::Basic { username, password }
143 }
144
145 #[tracing::instrument(skip_all)]
146 pub fn oauth1(
147 token: String,
148 secret: String,
149 consumer_key: String,
150 consumer_secret: String,
151 ) -> Self {
152 Self::OAuth1 {
153 token,
154 secret,
155 consumer_key,
156 consumer_secret,
157 }
158 }
159}
160
161#[derive(Clone, Debug, Default)]
165pub struct Builder {
166 endpoint: Option<String>,
167 credentials: Option<Credentials>,
168}
169
170impl Builder {
171 #[cfg_attr(feature = "tracing", tracing::instrument)]
172 pub fn with_endpoint(mut self, endpoint: String) -> Self {
173 self.endpoint = Some(endpoint);
174 self
175 }
176
177 #[cfg_attr(feature = "tracing", tracing::instrument)]
178 pub fn with_credentials(mut self, credentials: Credentials) -> Self {
179 self.credentials = Some(credentials);
180 self
181 }
182
183 #[cfg_attr(feature = "tracing", tracing::instrument)]
184 pub fn build(self, client: reqwest::Client) -> Client {
185 let endpoint = match self.endpoint {
186 Some(endpoint) => endpoint,
187 None => {
188 if matches!(self.credentials, Some(Credentials::Bearer { .. })) {
189 PUBLIC_API_BRIDGE_ENDPOINT.to_string()
190 } else {
191 PUBLIC_ENDPOINT.to_string()
192 }
193 }
194 };
195
196 Client {
197 inner: OAuthClient::new(client, self.credentials.map(Into::into)),
198 endpoint,
199 }
200 }
201}
202
203#[derive(Clone, Debug)]
207pub struct Client {
208 inner: OAuthClient,
209 endpoint: String,
210}
211
212#[async_trait]
213impl Request for Client {
214 type Error = ClientError;
215
216 #[cfg_attr(feature = "tracing", tracing::instrument)]
217 async fn request<T, U>(
218 &self,
219 method: &Method,
220 endpoint: &str,
221 payload: &T,
222 ) -> Result<U, Self::Error>
223 where
224 T: Serialize + Debug + Send + Sync,
225 U: DeserializeOwned + Debug + Send + Sync,
226 {
227 self.inner.request(method, endpoint, payload).await
228 }
229
230 #[cfg_attr(feature = "tracing", tracing::instrument)]
231 async fn execute(&self, request: reqwest::Request) -> Result<reqwest::Response, Self::Error> {
232 self.inner.execute(request).await
233 }
234}
235
236#[async_trait]
237impl RestClient for Client {
238 type Error = ClientError;
239
240 #[cfg_attr(feature = "tracing", tracing::instrument)]
241 async fn get<T>(&self, endpoint: &str) -> Result<T, Self::Error>
242 where
243 T: DeserializeOwned + Debug + Send + Sync,
244 {
245 self.inner.get(endpoint).await
246 }
247
248 #[cfg_attr(feature = "tracing", tracing::instrument)]
249 async fn post<T, U>(&self, endpoint: &str, payload: &T) -> Result<U, Self::Error>
250 where
251 T: Serialize + Debug + Send + Sync,
252 U: DeserializeOwned + Debug + Send + Sync,
253 {
254 self.inner.post(endpoint, payload).await
255 }
256
257 #[cfg_attr(feature = "tracing", tracing::instrument)]
258 async fn put<T, U>(&self, endpoint: &str, payload: &T) -> Result<U, Self::Error>
259 where
260 T: Serialize + Debug + Send + Sync,
261 U: DeserializeOwned + Debug + Send + Sync,
262 {
263 self.inner.put(endpoint, payload).await
264 }
265
266 #[cfg_attr(feature = "tracing", tracing::instrument)]
267 async fn patch<T, U>(&self, endpoint: &str, payload: &T) -> Result<U, Self::Error>
268 where
269 T: Serialize + Debug + Send + Sync,
270 U: DeserializeOwned + Debug + Send + Sync,
271 {
272 self.inner.patch(endpoint, payload).await
273 }
274
275 #[cfg_attr(feature = "tracing", tracing::instrument)]
276 async fn delete(&self, endpoint: &str) -> Result<(), Self::Error> {
277 self.inner.delete(endpoint).await
278 }
279}
280
281impl From<reqwest::Client> for Client {
282 #[cfg_attr(feature = "tracing", tracing::instrument)]
283 fn from(client: reqwest::Client) -> Self {
284 Self::builder().build(client)
285 }
286}
287
288impl From<Credentials> for Client {
289 #[cfg_attr(feature = "tracing", tracing::instrument)]
290 fn from(credentials: Credentials) -> Self {
291 match &credentials {
292 Credentials::Bearer { .. } => Self::builder()
293 .with_endpoint(PUBLIC_API_BRIDGE_ENDPOINT.to_string())
294 .with_credentials(credentials)
295 .build(reqwest::Client::new()),
296 _ => Self::builder()
297 .with_credentials(credentials)
298 .build(reqwest::Client::new()),
299 }
300 }
301}
302
303impl Default for Client {
304 #[cfg_attr(feature = "tracing", tracing::instrument)]
305 fn default() -> Self {
306 Self::builder().build(reqwest::Client::new())
307 }
308}
309
310impl Client {
311 #[cfg_attr(feature = "tracing", tracing::instrument)]
312 pub fn new(
313 client: reqwest::Client,
314 endpoint: String,
315 credentials: Option<Credentials>,
316 ) -> Self {
317 let mut builder = Self::builder().with_endpoint(endpoint);
318
319 if let Some(credentials) = credentials {
320 builder = builder.with_credentials(credentials);
321 }
322
323 builder.build(client)
324 }
325
326 #[cfg_attr(feature = "tracing", tracing::instrument)]
327 pub fn builder() -> Builder {
328 Builder::default()
329 }
330
331 #[cfg_attr(feature = "tracing", tracing::instrument)]
332 pub fn set_endpoint(&mut self, endpoint: String) {
333 self.endpoint = endpoint;
334 }
335
336 #[cfg_attr(feature = "tracing", tracing::instrument)]
337 pub fn set_credentials(&mut self, credentials: Option<Credentials>) {
338 self.inner.set_credentials(credentials.map(Into::into));
339 }
340
341 #[cfg_attr(feature = "tracing", tracing::instrument)]
342 pub fn inner(&self) -> &reqwest::Client {
343 self.inner.inner()
344 }
345}