wechat_api_rs/
client.rs

1use crate::{WeChatError, Result};
2use reqwest::{Client as HttpClient, header};
3use serde::{Deserialize, Serialize};
4use std::time::Duration;
5use tracing::{debug, error};
6
7/// WeChat HTTP client configuration
8#[derive(Debug, Clone)]
9pub struct ClientConfig {
10    pub app_id: String,
11    pub app_secret: String,
12    pub timeout: Duration,
13    pub user_agent: String,
14}
15
16impl Default for ClientConfig {
17    fn default() -> Self {
18        Self {
19            app_id: String::new(),
20            app_secret: String::new(),
21            timeout: Duration::from_secs(30),
22            user_agent: "wechat-sdk-rust/0.1.0".to_string(),
23        }
24    }
25}
26
27/// WeChat HTTP client
28#[derive(Debug, Clone)]
29pub struct Client {
30    config: ClientConfig,
31    http_client: HttpClient,
32}
33
34impl Client {
35    /// Create a new client with configuration
36    pub fn new(config: ClientConfig) -> Result<Self> {
37        let mut headers = header::HeaderMap::new();
38        headers.insert(
39            header::USER_AGENT,
40            header::HeaderValue::from_str(&config.user_agent)
41                .map_err(|_| WeChatError::Config("Invalid user agent".to_string()))?,
42        );
43
44        let http_client = HttpClient::builder()
45            .timeout(config.timeout)
46            .default_headers(headers)
47            .build()
48            .map_err(|e| WeChatError::Http(e.to_string()))?;
49
50        Ok(Self {
51            config,
52            http_client,
53        })
54    }
55
56    /// Get the app ID
57    pub fn app_id(&self) -> &str {
58        &self.config.app_id
59    }
60
61    /// Get the app secret
62    pub fn app_secret(&self) -> &str {
63        &self.config.app_secret
64    }
65
66    /// Get the HTTP client
67    pub fn http_client(&self) -> &HttpClient {
68        &self.http_client
69    }
70
71    /// Send a GET request
72    pub async fn get<T>(&self, url: &str) -> Result<T>
73    where
74        T: for<'de> Deserialize<'de>,
75    {
76        debug!("GET request to: {}", url);
77        
78        let response = self.http_client
79            .get(url)
80            .send()
81            .await
82            .map_err(|e| WeChatError::Http(e.to_string()))?;
83
84        self.handle_response(response).await
85    }
86
87    /// Send a POST request with JSON body
88    pub async fn post_json<B, T>(&self, url: &str, body: &B) -> Result<T>
89    where
90        B: Serialize,
91        T: for<'de> Deserialize<'de>,
92    {
93        debug!("POST JSON request to: {}", url);
94        
95        let response = self.http_client
96            .post(url)
97            .json(body)
98            .send()
99            .await
100            .map_err(|e| WeChatError::Http(e.to_string()))?;
101
102        self.handle_response(response).await
103    }
104
105    /// Handle HTTP response and parse JSON
106    async fn handle_response<T>(&self, response: reqwest::Response) -> Result<T>
107    where
108        T: for<'de> Deserialize<'de>,
109    {
110        let status = response.status();
111        let text = response.text().await.map_err(WeChatError::Reqwest)?;
112
113        debug!("Response status: {}, body: {}", status, text);
114
115        if !status.is_success() {
116            error!("HTTP error: {} - {}", status, text);
117            return Err(WeChatError::Http(format!("HTTP {}: {}", status, text)));
118        }
119
120        // Try to parse as WeChat API response first
121        if let Ok(api_response) = serde_json::from_str::<ApiResponse>(&text) {
122            if let Some(errcode) = api_response.errcode {
123                if errcode != 0 {
124                    return Err(WeChatError::Api {
125                        code: errcode,
126                        message: api_response.errmsg.unwrap_or_else(|| "Unknown error".to_string()),
127                    });
128                }
129            }
130        }
131
132        // Parse as the expected type
133        serde_json::from_str(&text).map_err(WeChatError::Json)
134    }
135}
136
137/// Builder for Client
138pub struct ClientBuilder {
139    config: ClientConfig,
140}
141
142impl ClientBuilder {
143    pub fn new() -> Self {
144        Self {
145            config: ClientConfig::default(),
146        }
147    }
148
149    pub fn app_id<S: Into<String>>(mut self, app_id: S) -> Self {
150        self.config.app_id = app_id.into();
151        self
152    }
153
154    pub fn app_secret<S: Into<String>>(mut self, app_secret: S) -> Self {
155        self.config.app_secret = app_secret.into();
156        self
157    }
158
159    pub fn timeout(mut self, timeout: Duration) -> Self {
160        self.config.timeout = timeout;
161        self
162    }
163
164    pub fn user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
165        self.config.user_agent = user_agent.into();
166        self
167    }
168
169    pub fn build(self) -> Result<Client> {
170        if self.config.app_id.is_empty() {
171            return Err(WeChatError::Config("app_id is required".to_string()));
172        }
173        if self.config.app_secret.is_empty() {
174            return Err(WeChatError::Config("app_secret is required".to_string()));
175        }
176        
177        Client::new(self.config)
178    }
179}
180
181impl Default for ClientBuilder {
182    fn default() -> Self {
183        Self::new()
184    }
185}
186
187/// Generic WeChat API response
188#[derive(Debug, Deserialize)]
189struct ApiResponse {
190    errcode: Option<i32>,
191    errmsg: Option<String>,
192}