tencent_sdk/
client.rs

1use chrono::{TimeZone, Utc};
2use reqwest::header::{HeaderMap, HeaderValue};
3use reqwest::Client;
4use sha2::{Digest, Sha256};
5use std::error::Error;
6
7use crate::signing::hmac_sha256;
8use hmac::{Hmac, Mac};
9
10type HmacSha256 = Hmac<Sha256>;
11
12/// Tencent Cloud SDK Client
13///
14/// This client is used to send authenticated requests to Tencent Cloud APIs.
15/// It internally builds the canonical request string, computes the TC3-HMAC-SHA256 signature,
16/// sets the appropriate headers, and sends an HTTPS POST request.
17pub struct TencentCloudClient {
18    /// Your Tencent Cloud SecretId.
19    pub secret_id: String,
20    /// Your Tencent Cloud SecretKey.
21    pub secret_key: String,
22    /// Optional token.
23    pub token: Option<String>,
24}
25
26impl TencentCloudClient {
27    /// Creates a new TencentCloudClient.
28    ///
29    /// # Arguments
30    ///
31    /// * `secret_id` - Your Tencent Cloud SecretId.
32    /// * `secret_key` - Your Tencent Cloud SecretKey.
33    /// * `token` - An optional token.
34    pub fn new(secret_id: &str, secret_key: &str, token: Option<&str>) -> Self {
35        Self {
36            secret_id: secret_id.to_owned(),
37            secret_key: secret_key.to_owned(),
38            token: token.map(|s| s.to_owned()),
39        }
40    }
41
42    /// Asynchronous general request function.
43    ///
44    /// This method constructs the canonical request, computes the TC3-HMAC-SHA256 signature,
45    /// builds the Authorization header, and sends an HTTPS POST request.
46    ///
47    /// # Arguments
48    ///
49    /// * `service` - The service name (e.g., "cvm").
50    /// * `host` - The request host (e.g., "cvm.tencentcloudapi.com").
51    /// * `region` - Optional region string.
52    /// * `version` - API version (e.g., "2017-03-12").
53    /// * `action` - API action name (e.g., "DescribeInstances").
54    /// * `payload` - The request body as a JSON string.
55    ///
56    /// # Returns
57    ///
58    /// A `Result` containing the response text on success, or a boxed error on failure.
59    pub async fn request(
60        &self,
61        service: &str,
62        host: &str,
63        region: Option<&str>,
64        version: &str,
65        action: &str,
66        payload: &str,
67    ) -> Result<String, Box<dyn Error>> {
68        let algorithm = "TC3-HMAC-SHA256";
69        let ct = "application/json; charset=utf-8";
70
71        // Step 1: Construct the canonical request string.
72        let http_request_method = "POST";
73        let canonical_uri = "/";
74        let canonical_querystring = "";
75        let canonical_headers = format!(
76            "content-type:{}\nhost:{}\nx-tc-action:{}\n",
77            ct,
78            host,
79            action.to_lowercase()
80        );
81        let signed_headers = "content-type;host;x-tc-action";
82        let hashed_request_payload = {
83            let mut hasher = Sha256::new();
84            hasher.update(payload.as_bytes());
85            format!("{:x}", hasher.finalize())
86        };
87        let canonical_request = format!(
88            "{}\n{}\n{}\n{}\n{}\n{}",
89            http_request_method,
90            canonical_uri,
91            canonical_querystring,
92            canonical_headers,
93            signed_headers,
94            hashed_request_payload
95        );
96
97        // Step 2: Construct the string to sign.
98        let timestamp = Utc::now().timestamp();
99        let date = Utc.timestamp(timestamp, 0).format("%Y-%m-%d").to_string();
100        let credential_scope = format!("{}/{}/tc3_request", date, service);
101        let hashed_canonical_request = {
102            let mut hasher = Sha256::new();
103            hasher.update(canonical_request.as_bytes());
104            format!("{:x}", hasher.finalize())
105        };
106        let string_to_sign = format!(
107            "{}\n{}\n{}\n{}",
108            algorithm, timestamp, credential_scope, hashed_canonical_request
109        );
110
111        // Step 3: Compute the signature.
112        let secret_date = hmac_sha256(format!("TC3{}", self.secret_key).as_bytes(), &date);
113        let secret_service = hmac_sha256(&secret_date, service);
114        let secret_signing = hmac_sha256(&secret_service, "tc3_request");
115        let signature = {
116            let mut mac = HmacSha256::new_from_slice(&secret_signing)
117                .expect("HMAC can accept any key length");
118            mac.update(string_to_sign.as_bytes());
119            format!("{:x}", mac.finalize().into_bytes())
120        };
121
122        // Step 4: Construct the Authorization header.
123        let authorization = format!(
124            "{} Credential={}/{}, SignedHeaders={}, Signature={}",
125            algorithm, self.secret_id, credential_scope, signed_headers, signature
126        );
127
128        // Step 5: Build headers and send the request.
129        let mut headers = HeaderMap::new();
130        headers.insert("Authorization", HeaderValue::from_str(&authorization)?);
131        headers.insert("Content-Type", HeaderValue::from_static(ct));
132        headers.insert("Host", HeaderValue::from_str(host)?);
133        headers.insert("X-TC-Action", HeaderValue::from_str(action)?);
134        headers.insert(
135            "X-TC-Timestamp",
136            HeaderValue::from_str(&timestamp.to_string())?,
137        );
138        headers.insert("X-TC-Version", HeaderValue::from_str(version)?);
139        if let Some(r) = region {
140            headers.insert("X-TC-Region", HeaderValue::from_str(r)?);
141        }
142        if let Some(t) = &self.token {
143            headers.insert("X-TC-Token", HeaderValue::from_str(t)?);
144        }
145
146        let url = format!("https://{}", host);
147        let client = Client::new();
148        let resp = client
149            .post(&url)
150            .headers(headers)
151            .body(payload.to_owned())
152            .send()
153            .await?
154            .text()
155            .await?;
156
157        Ok(resp)
158    }
159}