tencent_sdk/
client.rs

1use crate::{
2    core::{Credentials, Endpoint, TencentCloudError, TencentCloudResult},
3    middleware::{RetryAsync, RetryBlocking},
4    signing::{build_tc3_headers, SigningInput},
5    transport::{
6        async_impl::{AsyncTransport, DefaultAsyncTransport},
7        blocking_impl::{BlockingTransport, DefaultBlockingTransport},
8    },
9};
10use chrono::Utc;
11use http::Method;
12use serde_json::Value;
13use std::time::Duration;
14use url::Url;
15
16const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
17const DEFAULT_USER_AGENT: &str = concat!("tencent-sdk-rust/", env!("CARGO_PKG_VERSION"));
18
19pub struct TencentCloudAsyncBuilder<T = DefaultAsyncTransport> {
20    credentials: Credentials,
21    default_region: Option<String>,
22    user_agent: String,
23    insecure: bool,
24    timeout: Duration,
25    no_proxy: bool,
26    transport: T,
27}
28
29impl TencentCloudAsyncBuilder<DefaultAsyncTransport> {
30    fn default_builder(credentials: Credentials) -> TencentCloudResult<Self> {
31        let transport =
32            DefaultAsyncTransport::new(false, DEFAULT_USER_AGENT, DEFAULT_TIMEOUT, false)
33                .map_err(TencentCloudError::transport_build)?;
34
35        Ok(Self {
36            credentials,
37            default_region: None,
38            user_agent: DEFAULT_USER_AGENT.to_string(),
39            insecure: false,
40            timeout: DEFAULT_TIMEOUT,
41            no_proxy: false,
42            transport,
43        })
44    }
45
46    fn refresh_transport(&mut self) -> TencentCloudResult<()> {
47        self.transport = DefaultAsyncTransport::new(
48            self.insecure,
49            &self.user_agent,
50            self.timeout,
51            self.no_proxy,
52        )
53        .map_err(TencentCloudError::transport_build)?;
54        Ok(())
55    }
56
57    pub fn try_timeout(mut self, value: Duration) -> TencentCloudResult<Self> {
58        self.timeout = value;
59        self.refresh_transport()?;
60        Ok(self)
61    }
62
63    pub fn timeout(self, value: Duration) -> Self {
64        self.try_timeout(value)
65            .expect("failed to apply timeout to TencentCloudAsyncBuilder")
66    }
67
68    pub fn try_user_agent(mut self, value: impl Into<String>) -> TencentCloudResult<Self> {
69        self.user_agent = value.into();
70        self.refresh_transport()?;
71        Ok(self)
72    }
73
74    pub fn user_agent(self, value: impl Into<String>) -> Self {
75        self.try_user_agent(value)
76            .expect("failed to apply user agent to TencentCloudAsyncBuilder")
77    }
78
79    pub fn try_danger_accept_invalid_certs(mut self, yes: bool) -> TencentCloudResult<Self> {
80        self.insecure = yes;
81        self.refresh_transport()?;
82        Ok(self)
83    }
84
85    pub fn danger_accept_invalid_certs(self, yes: bool) -> Self {
86        self.try_danger_accept_invalid_certs(yes)
87            .expect("failed to update TLS settings for TencentCloudAsyncBuilder")
88    }
89
90    pub fn try_no_system_proxy(mut self) -> TencentCloudResult<Self> {
91        self.no_proxy = true;
92        self.refresh_transport()?;
93        Ok(self)
94    }
95
96    pub fn no_system_proxy(self) -> Self {
97        self.try_no_system_proxy()
98            .expect("failed to disable system proxy for TencentCloudAsyncBuilder")
99    }
100}
101
102impl<T> TencentCloudAsyncBuilder<T> {
103    pub fn with_default_region(mut self, region: impl Into<String>) -> Self {
104        self.default_region = Some(region.into());
105        self
106    }
107
108    pub fn clear_default_region(mut self) -> Self {
109        self.default_region = None;
110        self
111    }
112
113    pub fn with_token(mut self, token: impl Into<String>) -> Self {
114        self.credentials.set_token(token);
115        self
116    }
117}
118
119impl<T: AsyncTransport> TencentCloudAsyncBuilder<T> {
120    pub fn transport<NT: AsyncTransport>(self, transport: NT) -> TencentCloudAsyncBuilder<NT> {
121        TencentCloudAsyncBuilder {
122            credentials: self.credentials,
123            default_region: self.default_region,
124            user_agent: self.user_agent,
125            insecure: self.insecure,
126            timeout: self.timeout,
127            no_proxy: self.no_proxy,
128            transport,
129        }
130    }
131
132    pub fn with_retry(
133        self,
134        max: usize,
135        delay: Duration,
136    ) -> TencentCloudAsyncBuilder<RetryAsync<T>> {
137        let TencentCloudAsyncBuilder {
138            credentials,
139            default_region,
140            user_agent,
141            insecure,
142            timeout,
143            no_proxy,
144            transport,
145        } = self;
146
147        TencentCloudAsyncBuilder {
148            credentials,
149            default_region,
150            user_agent,
151            insecure,
152            timeout,
153            no_proxy,
154            transport: RetryAsync::new(transport, max, delay),
155        }
156    }
157
158    pub fn build(self) -> TencentCloudResult<TencentCloudAsync<T>> {
159        Ok(TencentCloudAsync {
160            credentials: self.credentials,
161            default_region: self.default_region,
162            timeout: self.timeout,
163            transport: self.transport,
164        })
165    }
166}
167
168pub struct TencentCloudAsync<T: AsyncTransport = DefaultAsyncTransport> {
169    credentials: Credentials,
170    default_region: Option<String>,
171    timeout: Duration,
172    transport: T,
173}
174
175impl TencentCloudAsync<DefaultAsyncTransport> {
176    pub fn builder(
177        secret_id: impl Into<String>,
178        secret_key: impl Into<String>,
179    ) -> TencentCloudResult<TencentCloudAsyncBuilder<DefaultAsyncTransport>> {
180        let credentials = Credentials::new(secret_id, secret_key);
181        TencentCloudAsyncBuilder::default_builder(credentials)
182    }
183
184    pub fn new(
185        secret_id: impl Into<String>,
186        secret_key: impl Into<String>,
187    ) -> TencentCloudResult<Self> {
188        Self::builder(secret_id, secret_key)?.build()
189    }
190}
191
192impl<T: AsyncTransport> TencentCloudAsync<T> {
193    pub async fn request<E: Endpoint>(&self, endpoint: &E) -> TencentCloudResult<E::Output> {
194        let scheme = endpoint.scheme();
195        let host = endpoint.host();
196        let path = endpoint.path();
197        let service = endpoint.service();
198        let action = endpoint.action();
199        let version = endpoint.version();
200        let scheme_str = scheme.as_ref();
201        let host_str = host.as_ref();
202        let path_str = path.as_ref();
203        let service_str = service.as_ref();
204        let action_str = action.as_ref();
205        let version_str = version.as_ref();
206        let region = endpoint
207            .region()
208            .map(|value| value.into_owned())
209            .or_else(|| self.default_region.clone());
210
211        let payload_value = endpoint.payload();
212        let payload = serde_json::to_string(&payload_value)?;
213        let timestamp = Utc::now().timestamp();
214
215        let url = Url::parse(&format!("{}://{}{}", scheme_str, host_str, path_str))?;
216
217        let mut headers = build_tc3_headers(
218            &self.credentials,
219            &SigningInput {
220                service: service_str,
221                host: host_str,
222                path: path_str,
223                region: region.as_deref(),
224                action: action_str,
225                version: version_str,
226                payload: &payload,
227                timestamp,
228            },
229        )?;
230
231        if let Some(extra) = endpoint.extra_headers() {
232            for (key, value) in extra {
233                headers.insert(key.into_owned(), value.into_owned());
234            }
235        }
236
237        let body = Some(payload);
238        let (status, text) = self
239            .transport
240            .send(Method::POST, url.clone(), headers, body, self.timeout)
241            .await?;
242
243        if !status.is_success() {
244            return Err(TencentCloudError::http(status, Method::POST, url, text));
245        }
246
247        let json: Value = serde_json::from_str(&text)?;
248        if let Some(err) = service_error_from_value(&json) {
249            return Err(err);
250        }
251        endpoint.parse(json)
252    }
253}
254
255pub struct TencentCloudBlockingBuilder<T = DefaultBlockingTransport> {
256    credentials: Credentials,
257    default_region: Option<String>,
258    user_agent: String,
259    insecure: bool,
260    timeout: Duration,
261    no_proxy: bool,
262    transport: T,
263}
264
265impl TencentCloudBlockingBuilder<DefaultBlockingTransport> {
266    fn default_builder(credentials: Credentials) -> TencentCloudResult<Self> {
267        let transport =
268            DefaultBlockingTransport::new(false, DEFAULT_USER_AGENT, DEFAULT_TIMEOUT, false)
269                .map_err(TencentCloudError::transport_build)?;
270
271        Ok(Self {
272            credentials,
273            default_region: None,
274            user_agent: DEFAULT_USER_AGENT.to_string(),
275            insecure: false,
276            timeout: DEFAULT_TIMEOUT,
277            no_proxy: false,
278            transport,
279        })
280    }
281
282    fn refresh_transport(&mut self) -> TencentCloudResult<()> {
283        self.transport = DefaultBlockingTransport::new(
284            self.insecure,
285            &self.user_agent,
286            self.timeout,
287            self.no_proxy,
288        )
289        .map_err(TencentCloudError::transport_build)?;
290        Ok(())
291    }
292
293    pub fn try_timeout(mut self, value: Duration) -> TencentCloudResult<Self> {
294        self.timeout = value;
295        self.refresh_transport()?;
296        Ok(self)
297    }
298
299    pub fn timeout(self, value: Duration) -> Self {
300        self.try_timeout(value)
301            .expect("failed to apply timeout to TencentCloudBlockingBuilder")
302    }
303
304    pub fn try_user_agent(mut self, value: impl Into<String>) -> TencentCloudResult<Self> {
305        self.user_agent = value.into();
306        self.refresh_transport()?;
307        Ok(self)
308    }
309
310    pub fn user_agent(self, value: impl Into<String>) -> Self {
311        self.try_user_agent(value)
312            .expect("failed to apply user agent to TencentCloudBlockingBuilder")
313    }
314
315    pub fn try_danger_accept_invalid_certs(mut self, yes: bool) -> TencentCloudResult<Self> {
316        self.insecure = yes;
317        self.refresh_transport()?;
318        Ok(self)
319    }
320
321    pub fn danger_accept_invalid_certs(self, yes: bool) -> Self {
322        self.try_danger_accept_invalid_certs(yes)
323            .expect("failed to update TLS settings for TencentCloudBlockingBuilder")
324    }
325
326    pub fn try_no_system_proxy(mut self) -> TencentCloudResult<Self> {
327        self.no_proxy = true;
328        self.refresh_transport()?;
329        Ok(self)
330    }
331
332    pub fn no_system_proxy(self) -> Self {
333        self.try_no_system_proxy()
334            .expect("failed to disable system proxy for TencentCloudBlockingBuilder")
335    }
336}
337
338impl<T> TencentCloudBlockingBuilder<T> {
339    pub fn with_default_region(mut self, region: impl Into<String>) -> Self {
340        self.default_region = Some(region.into());
341        self
342    }
343
344    pub fn clear_default_region(mut self) -> Self {
345        self.default_region = None;
346        self
347    }
348
349    pub fn with_token(mut self, token: impl Into<String>) -> Self {
350        self.credentials.set_token(token);
351        self
352    }
353}
354
355impl<T: BlockingTransport> TencentCloudBlockingBuilder<T> {
356    pub fn transport<NT: BlockingTransport>(
357        self,
358        transport: NT,
359    ) -> TencentCloudBlockingBuilder<NT> {
360        TencentCloudBlockingBuilder {
361            credentials: self.credentials,
362            default_region: self.default_region,
363            user_agent: self.user_agent,
364            insecure: self.insecure,
365            timeout: self.timeout,
366            no_proxy: self.no_proxy,
367            transport,
368        }
369    }
370
371    pub fn with_retry(
372        self,
373        max: usize,
374        delay: Duration,
375    ) -> TencentCloudBlockingBuilder<RetryBlocking<T>> {
376        let TencentCloudBlockingBuilder {
377            credentials,
378            default_region,
379            user_agent,
380            insecure,
381            timeout,
382            no_proxy,
383            transport,
384        } = self;
385
386        TencentCloudBlockingBuilder {
387            credentials,
388            default_region,
389            user_agent,
390            insecure,
391            timeout,
392            no_proxy,
393            transport: RetryBlocking::new(transport, max, delay),
394        }
395    }
396
397    pub fn build(self) -> TencentCloudResult<TencentCloudBlocking<T>> {
398        Ok(TencentCloudBlocking {
399            credentials: self.credentials,
400            default_region: self.default_region,
401            timeout: self.timeout,
402            transport: self.transport,
403        })
404    }
405}
406
407pub struct TencentCloudBlocking<T: BlockingTransport = DefaultBlockingTransport> {
408    credentials: Credentials,
409    default_region: Option<String>,
410    timeout: Duration,
411    transport: T,
412}
413
414impl TencentCloudBlocking<DefaultBlockingTransport> {
415    pub fn builder(
416        secret_id: impl Into<String>,
417        secret_key: impl Into<String>,
418    ) -> TencentCloudResult<TencentCloudBlockingBuilder<DefaultBlockingTransport>> {
419        let credentials = Credentials::new(secret_id, secret_key);
420        TencentCloudBlockingBuilder::default_builder(credentials)
421    }
422
423    pub fn new(
424        secret_id: impl Into<String>,
425        secret_key: impl Into<String>,
426    ) -> TencentCloudResult<Self> {
427        Self::builder(secret_id, secret_key)?.build()
428    }
429}
430
431impl<T: BlockingTransport> TencentCloudBlocking<T> {
432    pub fn request<E: Endpoint>(&self, endpoint: &E) -> TencentCloudResult<E::Output> {
433        let scheme = endpoint.scheme();
434        let host = endpoint.host();
435        let path = endpoint.path();
436        let service = endpoint.service();
437        let action = endpoint.action();
438        let version = endpoint.version();
439        let scheme_str = scheme.as_ref();
440        let host_str = host.as_ref();
441        let path_str = path.as_ref();
442        let service_str = service.as_ref();
443        let action_str = action.as_ref();
444        let version_str = version.as_ref();
445        let region = endpoint
446            .region()
447            .map(|value| value.into_owned())
448            .or_else(|| self.default_region.clone());
449
450        let payload_value = endpoint.payload();
451        let payload = serde_json::to_string(&payload_value)?;
452        let timestamp = Utc::now().timestamp();
453
454        let url = Url::parse(&format!("{}://{}{}", scheme_str, host_str, path_str))?;
455
456        let mut headers = build_tc3_headers(
457            &self.credentials,
458            &SigningInput {
459                service: service_str,
460                host: host_str,
461                path: path_str,
462                region: region.as_deref(),
463                action: action_str,
464                version: version_str,
465                payload: &payload,
466                timestamp,
467            },
468        )?;
469
470        if let Some(extra) = endpoint.extra_headers() {
471            for (key, value) in extra {
472                headers.insert(key.into_owned(), value.into_owned());
473            }
474        }
475
476        let body = Some(payload);
477        let (status, text) =
478            self.transport
479                .send(Method::POST, url.clone(), headers, body, self.timeout)?;
480
481        if !status.is_success() {
482            return Err(TencentCloudError::http(status, Method::POST, url, text));
483        }
484
485        let json: Value = serde_json::from_str(&text)?;
486        if let Some(err) = service_error_from_value(&json) {
487            return Err(err);
488        }
489        endpoint.parse(json)
490    }
491}
492
493fn service_error_from_value(value: &Value) -> Option<TencentCloudError> {
494    let response = value.get("Response")?;
495    let error = response.get("Error")?;
496    let code = error.get("Code")?.as_str()?.to_string();
497    let message = error.get("Message")?.as_str()?.to_string();
498    let request_id = response
499        .get("RequestId")
500        .and_then(|id| id.as_str().map(|s| s.to_string()));
501    Some(TencentCloudError::service(code, message, request_id))
502}