Skip to main content

wechat_oa_sdk/
client.rs

1use std::sync::Arc;
2
3use serde::Deserialize;
4use serde::de::DeserializeOwned;
5
6use crate::access_token::TokenManager;
7use crate::config::Config;
8use crate::error::{Result, WeChatError};
9
10const WECHAT_API_BASE: &str = "https://api.weixin.qq.com/cgi-bin";
11
12/// The main entry point for the WeChat Official Account SDK.
13pub struct WeChatClient {
14    pub(crate) config: Config,
15    pub(crate) http: reqwest::Client,
16    pub(crate) token_manager: Arc<TokenManager>,
17}
18
19#[derive(Deserialize)]
20struct ApiErrorResponse {
21    errcode: Option<i64>,
22    errmsg: Option<String>,
23}
24
25impl WeChatClient {
26    /// Create a new `WeChatClient` with the given configuration.
27    pub fn new(config: Config) -> Self {
28        let http = reqwest::Client::new();
29        let token_manager = Arc::new(TokenManager::new(&config, http.clone()));
30        Self {
31            config,
32            http,
33            token_manager,
34        }
35    }
36
37    /// Get a valid access token (auto-refreshed if expired).
38    pub async fn access_token(&self) -> Result<String> {
39        self.token_manager.get_token().await
40    }
41
42    /// Make a GET request to a WeChat API endpoint with automatic token injection.
43    pub(crate) async fn get<T: DeserializeOwned>(
44        &self,
45        path: &str,
46        query: &[(&str, &str)],
47    ) -> Result<T> {
48        let token = self.access_token().await?;
49        let url = format!("{}{}", WECHAT_API_BASE, path);
50
51        let mut params = vec![("access_token", token.as_str())];
52        params.extend(query.iter().map(|(k, v)| (*k, *v)));
53
54        let resp = self.http.get(&url).query(&params).send().await?;
55        self.parse_response(resp).await
56    }
57
58    /// Make a POST request with a JSON body to a WeChat API endpoint.
59    pub(crate) async fn post_json<T: DeserializeOwned>(
60        &self,
61        path: &str,
62        body: &impl serde::Serialize,
63    ) -> Result<T> {
64        let token = self.access_token().await?;
65        let url = format!("{}{}?access_token={}", WECHAT_API_BASE, path, token);
66
67        let resp = self.http.post(&url).json(body).send().await?;
68        self.parse_response(resp).await
69    }
70
71    /// Parse a response, checking for WeChat API errors.
72    async fn parse_response<T: DeserializeOwned>(&self, resp: reqwest::Response) -> Result<T> {
73        let text = resp.text().await?;
74
75        // Check for API error first
76        if let Ok(err_resp) = serde_json::from_str::<ApiErrorResponse>(&text) {
77            if let Some(errcode) = err_resp.errcode {
78                if errcode != 0 {
79                    return Err(WeChatError::Api {
80                        errcode,
81                        errmsg: err_resp.errmsg.unwrap_or_default(),
82                    });
83                }
84            }
85        }
86
87        serde_json::from_str(&text).map_err(WeChatError::from)
88    }
89}