wechat-minapp 3.4.3

微信小程序和服务号服务端API SDK
Documentation
use crate::{
    Result, constants,
    error::{Error, ErrorCode},
};
use http::{HeaderValue, Method, Request, Response, header};
use serde::{Deserialize, de::DeserializeOwned};
use serde_json::{Map, Value};
use tracing::{debug, error};
use url::Url;

pub fn parse_url(url: impl Into<String>) -> Result<Url> {
    Ok(Url::parse(&url.into())?)
}

pub fn parse_query(url: impl Into<String>) -> Result<Vec<(String, String)>> {
    let url = parse_url(url)?;
    let paris = url
        .query_pairs()
        .into_iter()
        .map(|s| (s.0.to_string(), s.1.to_string()))
        .collect();
    Ok(paris)
}

pub fn build_request(
    url: &str,
    method: Method,
    headers: Option<Value>,
    query: Option<Value>,
    body: Option<Value>,
) -> Result<Request<Vec<u8>>> {
    let mut req_url = Url::parse(url)?;
    let default_map = Map::new();

    if let Some(value) = query
        && value.is_object()
    {
        debug!("req query value:{:?}", value);

        value
            .as_object()
            .unwrap_or(&default_map)
            .iter()
            .for_each(|item| {
                debug!("req query item:{:?}", item);
                let value_str = match item.1 {
                    Value::String(s) => s,
                    _ => &value.to_string(),
                };
                req_url.query_pairs_mut().append_pair(item.0, value_str);
            });
    }

    let req_builder = Request::builder()
        .uri(req_url.to_string())
        .header(header::USER_AGENT, constants::HTTP_CLIENT_USER_AGENT)
        .method(method);

    let req_builder_with_headers = if let Some(headers_value) = headers
        && headers_value.is_object()
    {
        headers_value
            .as_object()
            .unwrap_or(&default_map)
            .iter()
            .fold(req_builder, |current_builder, (key, value)| {
                let value_str = value.as_str().unwrap_or("");
                let header_value =
                    HeaderValue::from_str(value_str).unwrap_or(HeaderValue::from_static(""));
                current_builder.header(key.as_str(), header_value)
            })
    } else {
        req_builder
    };

    if let Some(value) = body
        && value.is_object()
    {
        debug!("builder body {:?}", &value);
        let body = serde_json::to_vec(&value)?;
        Ok(req_builder_with_headers.body(body)?)
    } else {
        Ok(req_builder_with_headers.body(Vec::new())?)
    }
}

#[derive(Debug)]
pub struct RequestBuilder {
    url: String,
    method: Method,
    headers: Option<Value>,
    query: Option<Value>,
    body: Option<Value>,
}

impl RequestBuilder {
    pub fn new(url: impl Into<String>) -> Self {
        RequestBuilder {
            url: url.into(),
            method: Method::POST,
            headers: None,
            query: None,
            body: None,
        }
    }

    pub fn method(mut self, method: Method) -> Self {
        self.method = method;
        self
    }

    pub fn headers(mut self, headers: Value) -> Self {
        self.headers = Some(headers);
        self
    }

    pub fn query(mut self, query: Value) -> Self {
        self.query = Some(query);
        self
    }

    pub fn body(mut self, body: Value) -> Self {
        self.body = Some(body);
        self
    }

    pub fn build(self) -> Result<Request<Vec<u8>>> {
        build_request(&self.url, self.method, self.headers, self.query, self.body)
    }
}

pub trait ResponseExt {
    fn to_json<T>(self) -> Result<T>
    where
        T: DeserializeOwned + std::fmt::Debug;

    fn to_raw(self) -> Result<Vec<u8>>;
}

impl ResponseExt for Response<Vec<u8>> {
    fn to_json<T>(self) -> Result<T>
    where
        T: DeserializeOwned + std::fmt::Debug,
    {
        if self.status().is_success() {
            let (_parts, body) = self.into_parts();

            let json = serde_json::from_slice::<MpResponse<T>>(&body.to_vec())?;

            debug!("msg_sec_check result: {:#?}", json);

            #[cfg(test)]
            eprintln!("msg_sec_check result: {:#?}", json);

            Ok(json.extract()?)
        } else {
            let (_parts, body) = self.into_parts();
            let message = String::from_utf8_lossy(&body.to_vec()).to_string();
            Err(Error::InternalServer(message))
        }
    }
    fn to_raw(self) -> Result<Vec<u8>> {
        if self.status().is_success() {
            Ok(self.into_body())
        } else {
            let (_parts, body) = self.into_parts();
            let message = String::from_utf8_lossy(&body.to_vec()).to_string();
            Err(Error::InternalServer(message))
        }
    }
}

/// 微信小程序返回的数据结构
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum MpResponse<T> {
    Success {
        #[serde(flatten)]
        data: T,
    },
    Error {
        #[serde(rename = "errcode")]
        code: ErrorCode,
        #[serde(rename = "errmsg")]
        message: String,
    },
}

impl<T> MpResponse<T> {
    /// 获取微信小程序返回的数据
    pub(crate) fn extract(self) -> Result<T> {
        match self {
            Self::Success { data } => Ok(data),
            Self::Error { code, message } => {
                error!("微信小程序返回错误: code={}, message={}", code, message);

                Err((code, message).into())
            }
        }
    }
}