1use core::str;
2use std::{borrow::Cow, collections::BTreeMap};
3
4use anyhow::Result;
5use chrono::DateTime;
6use http::Method;
7use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
8use rand::Rng;
9use reqwest::{
10 Client, Request,
11 header::{HeaderMap, HeaderValue},
12};
13use serde::{Deserialize, Serialize};
14use serde_json::{Value, from_str};
15
16use crate::{
17 error::SdkError,
18 util::{current_timestamp, hmac256, sha256_hex},
19};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Secret {
23 pub secret_id: String,
24 pub secret_key: String,
25}
26
27#[derive(Debug, Clone)]
29pub enum MachineType {
30 Swas,
32
33 Ecs,
35}
36
37impl MachineType {
38 pub fn service_info(&self, region_id: &str) -> (String, &'static str) {
40 match self {
41 MachineType::Swas => {
42 (format!("swas.{}.aliyuncs.com", region_id), "2020-06-01")
43 }
44 MachineType::Ecs => {
45 (format!("ecs.{}.aliyuncs.com", region_id), "2014-05-26")
46 }
47 }
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct CommonResponse<T> {
56 #[serde(rename = "RequestId")]
57 pub request_id: String,
58 #[serde(flatten)]
59 pub response: T,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct Empty {}
64
65#[derive(Debug, Clone)]
66pub(super) struct BasicRequest<'a> {
67 pub(super) machine_type: MachineType,
68 pub(super) region_id: &'a str,
69 pub(super) secret: &'a Secret,
70 pub(super) action: &'static str,
71 pub(super) params: &'a [(&'a str, &'a str)],
72 pub(super) body: &'a str,
73}
74
75pub(super) fn request_builder(
76 client: &Client,
77 basic_request: BasicRequest<'_>,
78) -> Result<Request> {
79 let (host, version) = basic_request
80 .machine_type
81 .service_info(basic_request.region_id);
82 let canonical_uri = "/";
83 let canonical_query_string =
84 build_sored_encoded_query_string(basic_request.params);
85 let hashed_request_payload = sha256_hex("");
86 let now_time = current_timestamp()?;
87 let datetime = DateTime::from_timestamp(now_time as i64, 0).unwrap();
88 let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string();
89
90 let signature_nonce = generate_nonce();
91
92 let mut headers = HeaderMap::new();
94 headers.insert("Host", HeaderValue::from_str(&host)?);
95 headers.insert(
96 "Content-Type",
97 HeaderValue::from_str("application/json; charset=utf-8")?,
98 );
99 headers
100 .insert("x-acs-action", HeaderValue::from_str(basic_request.action)?);
101 headers.insert("x-acs-version", HeaderValue::from_str(version)?);
102 headers.insert("x-acs-date", HeaderValue::from_str(&datetime_str)?);
103 headers.insert(
104 "x-acs-signature-nonce",
105 HeaderValue::from_str(&signature_nonce)?,
106 );
107 headers.insert(
108 "x-acs-content-sha256",
109 HeaderValue::from_str(&hashed_request_payload)?,
110 );
111
112 let sign_header_arr = &[
113 "host",
114 "x-acs-action",
115 "x-acs-content-sha256",
116 "x-acs-date",
117 "x-acs-signature-nonce",
118 "x-acs-version",
119 ];
120
121 let http_request_method = "POST";
122
123 let sign_headers = sign_header_arr.join(";");
124 let canonical_request = format!(
125 "{}\n{}\n{}\n{}\n\n{}\n{}",
126 http_request_method,
127 canonical_uri,
128 canonical_query_string,
129 sign_header_arr
130 .iter()
131 .map(|&header| format!(
132 "{}:{}",
133 header,
134 headers[header].to_str().unwrap()
135 ))
136 .collect::<Vec<_>>()
137 .join("\n"),
138 sign_headers,
139 hashed_request_payload,
140 );
141
142 let result = sha256_hex(&canonical_request);
143 let string_to_sign = format!("ACS3-HMAC-SHA256\n{}", result);
144 let signature =
145 hmac256(basic_request.secret.secret_key.as_bytes(), &string_to_sign)
146 .map_err(|e| anyhow::anyhow!("{}", e))?;
147 let data_sign = hex::encode(&signature);
148 let auth_data = format!(
149 "ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}",
150 basic_request.secret.secret_id, sign_headers, data_sign
151 );
152
153 headers.insert("Authorization", HeaderValue::from_str(&auth_data)?);
154
155 let url = format!("https://{}{}", host, canonical_uri);
156 Ok(client
157 .request(Method::POST, url)
158 .headers(headers)
159 .query(&basic_request.params)
160 .body(basic_request.body.to_string())
161 .build()?)
162}
163
164fn to_error_response(response: &str) -> Option<SdkError> {
165 let response: Value = if let Ok(response) = from_str(response) {
166 response
167 } else {
168 return None;
169 };
170 let request_id = response.get("RequestId")?.as_str()?.to_string();
171 let code = response.get("Code")?.as_str()?.to_string();
172 let message = response.get("Message")?.as_str()?.to_string();
173 Some(SdkError {
174 request_id,
175 code,
176 message,
177 })
178}
179
180pub(super) fn parse_response<'a, T: Deserialize<'a>>(
181 result: &'a str,
182) -> Result<T> {
183 if let Some(error) = to_error_response(result) {
184 Err(error.into())
185 } else {
186 Ok(from_str::<T>(result)?)
187 }
188}
189
190fn build_sored_encoded_query_string(query_params: &[(&str, &str)]) -> String {
191 let sorted_query_params: BTreeMap<_, _> =
192 query_params.iter().copied().collect();
193
194 let encoded_params: Vec<String> = sorted_query_params
195 .into_iter()
196 .map(|(k, v)| {
197 let encoded_key = percent_code(k);
198 let encoded_value = percent_code(v);
199 format!("{}={}", encoded_key, encoded_value)
200 })
201 .collect();
202
203 encoded_params.join("&")
204}
205
206fn percent_code(encode_str: &str) -> Cow<'_, str> {
207 utf8_percent_encode(encode_str, NON_ALPHANUMERIC)
208 .collect::<String>()
209 .replace("%5F", "_")
210 .replace("%2D", "-")
211 .replace("%2E", ".")
212 .replace("%7E", "~")
213 .into()
214}
215
216fn generate_nonce() -> String {
217 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
218 let mut rng = rand::rng();
219 (0..32)
220 .map(|_| CHARSET[rng.random_range(0..CHARSET.len())] as char)
221 .collect()
222}
223
224pub(super) fn flatten_json(
225 value: &Value,
226 prefix: &str,
227) -> Vec<(String, String)> {
228 let mut result = Vec::new();
229
230 fn process_value(
231 value: &Value,
232 current_prefix: &str,
233 result: &mut Vec<(String, String)>,
234 ) {
235 match value {
236 Value::Object(map) => {
237 for (key, val) in map {
238 let new_prefix = if current_prefix.is_empty() {
239 key.to_string()
240 } else {
241 format!("{}.{}", current_prefix, key)
242 };
243 process_value(val, &new_prefix, result);
244 }
245 }
246 Value::Array(arr) => {
247 for (index, elem) in arr.iter().enumerate() {
248 let new_prefix =
249 format!("{}.{}", current_prefix, index + 1);
250 process_value(elem, &new_prefix, result);
251 }
252 }
253 Value::Null => {
254 result.push((current_prefix.to_string(), "null".to_string()))
255 }
256 Value::Bool(b) => {
257 result.push((current_prefix.to_string(), b.to_string()))
258 }
259 Value::Number(n) => {
260 result.push((current_prefix.to_string(), n.to_string()))
261 }
262 Value::String(s) => {
263 result.push((current_prefix.to_string(), s.clone()))
264 }
265 }
266 }
267
268 process_value(value, prefix, &mut result);
269 result
270}