iop-client 1.0.2

Alibaba Trade SDK for Rust
Documentation
use chrono::Utc;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::collections::HashMap;

use crate::IopClient;

type HmacSha256 = Hmac<Sha256>;

impl IopClient {
    /// Construct a URL with the given base URL, query parameters, and signature.
    ///
    /// # Arguments
    ///
    /// * `base_url` - The base URL to use for the constructed URL.
    /// * `params` - A `HashMap` containing the query parameters to include in the URL.
    /// * `sign` - The signature to append to the URL.
    ///
    /// # Returns
    ///
    /// A `String` representing the constructed URL.
    pub fn generate_url(
        &self,
        base_url: String,
        params: HashMap<String, String>,
        sign: String,
    ) -> String {
        let mut url = String::from(base_url);
        let mut first = true;

        for (key, value) in params {
            if first {
                url.push_str(&format!("?{}={}", key, value));
                first = false;
            } else {
                url.push_str(&format!("&{}={}", key, value));
            }
        }

        url.push_str(&format!("&sign={}", sign));

        url
    }

    /// Generates a signature based on the provided HTTP method and payload.
    ///
    /// 拼接参数名与参数值 [官方文档](https://open.alibaba.com/doc/doc.htm?spm=a2o9m.11193535.0.0.55fb2f04MHBYoD&docId=107343&docType=1#/?docId=134)
    ///
    /// This function sorts the payload by key, concatenates the keys and values,
    /// and prepends the optional HTTP method if provided. It then generates an
    /// HMAC-SHA256 hash of the resulting string using the app's secret key.
    ///
    /// # Arguments
    ///
    /// * `method` - An optional string representing the HTTP method to be included
    ///              in the signature calculation.
    /// * `payload` - A `HashMap` containing the parameters to be signed, where
    ///               keys are parameter names and values are parameter values.
    ///
    /// # Returns
    ///
    /// A `String` representing the computed HMAC-SHA256 signature in hexadecimal format.
    pub fn generate_sign(
        &self,
        method: Option<String>,
        payload: HashMap<String, String>,
    ) -> String {
        let mut sorted_vec: Vec<(&String, &String)> = payload.iter().collect();
        sorted_vec.sort_by(|a, b| a.0.cmp(b.0));

        let mut concatenated = match method {
            Some(m) => String::from(m),
            None => String::new(),
        };
        for (key, value) in sorted_vec {
            concatenated.push_str(key);
            concatenated.push_str(value);
        }

        self.generate_hmac_sha256(concatenated.as_bytes())
    }

    /// Generates an HMAC-SHA256 signature of the provided data.
    ///
    /// The signature is generated by creating an HMAC context with the app's secret key,
    /// updating the context with the provided data, and then finalizing the context to
    /// obtain the signature. The signature is then converted to a hexadecimal string
    /// and returned.
    ///
    /// # Arguments
    ///
    /// * `data` - The data to be signed, represented as a byte slice.
    ///
    /// # Returns
    ///
    /// A `String` representing the computed HMAC-SHA256 signature in hexadecimal format.
    pub fn generate_hmac_sha256(&self, data: &[u8]) -> String {
        let app_secret = self.app_secret.clone();
        let mut mac = HmacSha256::new_from_slice(app_secret.as_bytes())
            .expect("HMAC can take key of any size");

        mac.update(data);
        let result = mac.finalize();
        let code_bytes = result.into_bytes();
        format!("{:X}", code_bytes)
    }

    /// Builds a complete set of request parameters for API calls.
    ///
    /// This function constructs a `HashMap` containing both common and business-specific
    /// parameters required for API requests. It includes parameters such as `app_key`,
    /// `timestamp`, `sign_method`, and language, and attempts to include an `access_token`
    /// if available. The provided `params` are added to the map as business-specific
    /// parameters.
    ///
    /// # Arguments
    ///
    /// * `params` - A `HashMap` containing business-specific parameters to be included
    ///              in the API request.
    ///
    /// # Returns
    ///
    pub async fn build_request_params(
        &self,
        params: HashMap<String, String>,
    ) -> HashMap<String, String> {
        let now = Utc::now().timestamp_millis().to_string();

        let mut map = HashMap::new();
        map.insert("app_key".to_string(), self.appid.to_string());
        map.insert("sign_method".to_string(), "sha256".to_string());
        map.insert("simplify".to_string(), "true".to_string());
        map.insert("timestamp".to_string(), now);
        map.insert("language".to_string(), "en_US".to_string());

        if let Ok(at) = self.get_access_token().await {
            map.insert("access_token".to_string(), at.access_token.clone());
        }

        // 业务参数
        for (key, value) in params {
            map.insert(key, value);
        }

        map
    }
}