use std::sync::Arc;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use crate::access_token::TokenManager;
use crate::config::Config;
use crate::error::{Result, WeChatError};
const WECHAT_API_BASE: &str = "https://api.weixin.qq.com/cgi-bin";
pub struct WeChatClient {
pub(crate) config: Config,
pub(crate) http: reqwest::Client,
pub(crate) token_manager: Arc<TokenManager>,
}
#[derive(Deserialize)]
struct ApiErrorResponse {
errcode: Option<i64>,
errmsg: Option<String>,
}
impl WeChatClient {
pub fn new(config: Config) -> Self {
let http = reqwest::Client::new();
let token_manager = Arc::new(TokenManager::new(&config, http.clone()));
Self {
config,
http,
token_manager,
}
}
pub async fn access_token(&self) -> Result<String> {
self.token_manager.get_token().await
}
pub(crate) async fn get<T: DeserializeOwned>(
&self,
path: &str,
query: &[(&str, &str)],
) -> Result<T> {
let token = self.access_token().await?;
let url = format!("{}{}", WECHAT_API_BASE, path);
let mut params = vec![("access_token", token.as_str())];
params.extend(query.iter().map(|(k, v)| (*k, *v)));
let resp = self.http.get(&url).query(¶ms).send().await?;
self.parse_response(resp).await
}
pub(crate) async fn post_json<T: DeserializeOwned>(
&self,
path: &str,
body: &impl serde::Serialize,
) -> Result<T> {
let token = self.access_token().await?;
let url = format!("{}{}?access_token={}", WECHAT_API_BASE, path, token);
let resp = self.http.post(&url).json(body).send().await?;
self.parse_response(resp).await
}
async fn parse_response<T: DeserializeOwned>(&self, resp: reqwest::Response) -> Result<T> {
let text = resp.text().await?;
if let Ok(err_resp) = serde_json::from_str::<ApiErrorResponse>(&text) {
if let Some(errcode) = err_resp.errcode {
if errcode != 0 {
return Err(WeChatError::Api {
errcode,
errmsg: err_resp.errmsg.unwrap_or_default(),
});
}
}
}
serde_json::from_str(&text).map_err(WeChatError::from)
}
}