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}