iop_client/
core.rs

1use chrono::Utc;
2use hmac::{Hmac, Mac};
3use sha2::Sha256;
4use std::collections::HashMap;
5
6use crate::IopClient;
7
8type HmacSha256 = Hmac<Sha256>;
9
10impl IopClient {
11    /// Construct a URL with the given base URL, query parameters, and signature.
12    ///
13    /// # Arguments
14    ///
15    /// * `base_url` - The base URL to use for the constructed URL.
16    /// * `params` - A `HashMap` containing the query parameters to include in the URL.
17    /// * `sign` - The signature to append to the URL.
18    ///
19    /// # Returns
20    ///
21    /// A `String` representing the constructed URL.
22    pub fn generate_url(
23        &self,
24        base_url: String,
25        params: HashMap<String, String>,
26        sign: String,
27    ) -> String {
28        let mut url = String::from(base_url);
29        let mut first = true;
30
31        for (key, value) in params {
32            if first {
33                url.push_str(&format!("?{}={}", key, value));
34                first = false;
35            } else {
36                url.push_str(&format!("&{}={}", key, value));
37            }
38        }
39
40        url.push_str(&format!("&sign={}", sign));
41
42        url
43    }
44
45    /// Generates a signature based on the provided HTTP method and payload.
46    ///
47    /// 拼接参数名与参数值 [官方文档](https://open.alibaba.com/doc/doc.htm?spm=a2o9m.11193535.0.0.55fb2f04MHBYoD&docId=107343&docType=1#/?docId=134)
48    ///
49    /// This function sorts the payload by key, concatenates the keys and values,
50    /// and prepends the optional HTTP method if provided. It then generates an
51    /// HMAC-SHA256 hash of the resulting string using the app's secret key.
52    ///
53    /// # Arguments
54    ///
55    /// * `method` - An optional string representing the HTTP method to be included
56    ///              in the signature calculation.
57    /// * `payload` - A `HashMap` containing the parameters to be signed, where
58    ///               keys are parameter names and values are parameter values.
59    ///
60    /// # Returns
61    ///
62    /// A `String` representing the computed HMAC-SHA256 signature in hexadecimal format.
63    pub fn generate_sign(
64        &self,
65        method: Option<String>,
66        payload: HashMap<String, String>,
67    ) -> String {
68        let mut sorted_vec: Vec<(&String, &String)> = payload.iter().collect();
69        sorted_vec.sort_by(|a, b| a.0.cmp(b.0));
70
71        let mut concatenated = match method {
72            Some(m) => String::from(m),
73            None => String::new(),
74        };
75        for (key, value) in sorted_vec {
76            concatenated.push_str(key);
77            concatenated.push_str(value);
78        }
79
80        self.generate_hmac_sha256(concatenated.as_bytes())
81    }
82
83    /// Generates an HMAC-SHA256 signature of the provided data.
84    ///
85    /// The signature is generated by creating an HMAC context with the app's secret key,
86    /// updating the context with the provided data, and then finalizing the context to
87    /// obtain the signature. The signature is then converted to a hexadecimal string
88    /// and returned.
89    ///
90    /// # Arguments
91    ///
92    /// * `data` - The data to be signed, represented as a byte slice.
93    ///
94    /// # Returns
95    ///
96    /// A `String` representing the computed HMAC-SHA256 signature in hexadecimal format.
97    pub fn generate_hmac_sha256(&self, data: &[u8]) -> String {
98        let app_secret = self.app_secret.clone();
99        let mut mac = HmacSha256::new_from_slice(app_secret.as_bytes())
100            .expect("HMAC can take key of any size");
101
102        mac.update(data);
103        let result = mac.finalize();
104        let code_bytes = result.into_bytes();
105        format!("{:X}", code_bytes)
106    }
107
108    /// Builds a complete set of request parameters for API calls.
109    ///
110    /// This function constructs a `HashMap` containing both common and business-specific
111    /// parameters required for API requests. It includes parameters such as `app_key`,
112    /// `timestamp`, `sign_method`, and language, and attempts to include an `access_token`
113    /// if available. The provided `params` are added to the map as business-specific
114    /// parameters.
115    ///
116    /// # Arguments
117    ///
118    /// * `params` - A `HashMap` containing business-specific parameters to be included
119    ///              in the API request.
120    ///
121    /// # Returns
122    ///
123    pub async fn build_request_params(
124        &self,
125        params: HashMap<String, String>,
126    ) -> HashMap<String, String> {
127        let now = Utc::now().timestamp_millis().to_string();
128
129        let mut map = HashMap::new();
130        map.insert("app_key".to_string(), self.appid.to_string());
131        map.insert("sign_method".to_string(), "sha256".to_string());
132        map.insert("simplify".to_string(), "true".to_string());
133        map.insert("timestamp".to_string(), now);
134        map.insert("language".to_string(), "en_US".to_string());
135
136        if let Ok(at) = self.get_access_token().await {
137            map.insert("access_token".to_string(), at.access_token.clone());
138        }
139
140        // 业务参数
141        for (key, value) in params {
142            map.insert(key, value);
143        }
144
145        map
146    }
147}