clevercloud_sdk/
lib.rs

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