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}