meegle/
client.rs

1use mime_guess::from_path;
2use reqwest::multipart::{Form, Part};
3use reqwest::Client as HttpClient;
4use serde::Serialize;
5use serde_json::Value;
6use std::time::Duration;
7
8use crate::{error::ApiError, token::TokenManager};
9
10// 客户端配置
11#[derive(Clone)]
12pub struct ClientConfig {
13    pub(crate) plugin_id: String,
14    pub(crate) plugin_secret: String,
15    pub(crate) base_url: String,
16    timeout: Duration,
17}
18
19impl Default for ClientConfig {
20    fn default() -> Self {
21        Self {
22            plugin_id: String::new(),
23            plugin_secret: String::new(),
24            base_url: "https://project.feishu.cn".to_string(),
25            timeout: Duration::from_secs(30),
26        }
27    }
28}
29
30// API 客户端
31pub struct Client {
32    pub(crate) config: ClientConfig,
33    pub(crate) http_client: HttpClient,
34    pub(crate) token_manager: TokenManager,
35}
36
37#[derive(serde::Deserialize, Debug)]
38struct ApiResponse<T> {
39    data: Option<T>,
40    err_msg: String,
41    err_code: i32,
42    err: Value,
43}
44
45#[derive(Debug)]
46pub enum AuthType {
47    Plugin { user_key: String },
48    User { token: String },
49}
50
51impl Client {
52    pub fn new(
53        plugin_id: impl Into<String>,
54        plugin_secret: impl Into<String>,
55        base_url: Option<impl Into<String>>,
56    ) -> Self {
57        let config = ClientConfig {
58            plugin_id: plugin_id.into(),
59            plugin_secret: plugin_secret.into(),
60            base_url: base_url
61                .map_or_else(|| "https://project.feishu.cn".to_string(), |url| url.into()),
62            ..Default::default()
63        };
64
65        let http_client = HttpClient::builder()
66            .timeout(config.timeout)
67            .build()
68            .expect("Failed to create HTTP client");
69
70        Self {
71            http_client,
72            token_manager: TokenManager::new(
73                config.plugin_id.clone(),
74                config.plugin_secret.clone(),
75                config.base_url.clone(),
76            ),
77            config,
78        }
79    }
80
81    async fn build_request(
82        &self,
83        method: reqwest::Method,
84        path: impl Into<String>,
85        auth: AuthType,
86    ) -> Result<reqwest::RequestBuilder, ApiError> {
87        let url = format!("{}/{}/{}", self.config.base_url, "open_api", path.into());
88        let mut request = self
89            .http_client
90            .request(method, &url)
91            .header("Content-Type", "application/json");
92
93        match auth {
94            AuthType::Plugin { user_key } => {
95                let plugin_token = self
96                    .token_manager
97                    .require_plugin_token()
98                    .await
99                    .map_err(|e| ApiError::Other(e.to_string()))?;
100                request = request
101                    .header("X-Plugin-Token", plugin_token)
102                    .header("X-User-Key", user_key);
103            }
104            AuthType::User { token } => request = request.header("X-Plugin-Token", token),
105        }
106
107        Ok(request)
108    }
109
110    async fn handle_response<R: for<'de> serde::Deserialize<'de>>(
111        response: reqwest::Response,
112    ) -> Result<R, ApiError> {
113        let response: ApiResponse<R> = response.json().await?;
114
115        if response.err_code != 0 {
116            return Err(ApiError::ResponseError {
117                code: response.err_code,
118                message: format!("{} ({})", response.err_msg, response.err),
119            });
120        }
121
122        match response.data {
123            Some(data) => Ok(data),
124            None => Ok(serde_json::from_value(Value::Null).unwrap()),
125        }
126    }
127
128    async fn handle_binary_response(response: reqwest::Response) -> Result<Vec<u8>, ApiError> {
129        let status = response.status();
130        if !status.is_success() {
131            let error_text = response.text().await?;
132            return Err(ApiError::ResponseError {
133                code: status.as_u16() as i32,
134                message: error_text,
135            });
136        }
137
138        Ok(response.bytes().await?.to_vec())
139    }
140
141    pub(crate) async fn get<R: for<'de> serde::Deserialize<'de>>(
142        &self,
143        path: impl Into<String>,
144        auth: AuthType,
145    ) -> Result<R, ApiError> {
146        let request = self.build_request(reqwest::Method::GET, path, auth).await?;
147        let response = request.send().await?;
148        Self::handle_response(response).await
149    }
150
151    pub(crate) async fn get_with_query<R: for<'de> serde::Deserialize<'de>>(
152        &self,
153        path: impl Into<String>,
154        query: &Vec<(&str, String)>,
155        auth: AuthType,
156    ) -> Result<R, ApiError> {
157        let request = self.build_request(reqwest::Method::GET, path, auth).await?;
158        let response = request.query(query).send().await?;
159        Self::handle_response(response).await
160    }
161
162    pub(crate) async fn post<
163        T: Serialize + std::fmt::Debug,
164        R: for<'de> serde::Deserialize<'de>,
165    >(
166        &self,
167        path: impl Into<String>,
168        body: T,
169        auth: AuthType,
170    ) -> Result<R, ApiError> {
171        let request = self
172            .build_request(reqwest::Method::POST, path, auth)
173            .await?;
174        let response = request.json(&body).send().await?;
175        Self::handle_response(response).await
176    }
177
178    pub(crate) async fn put<T: Serialize + std::fmt::Debug, R: for<'de> serde::Deserialize<'de>>(
179        &self,
180        path: impl Into<String>,
181        body: T,
182        auth: AuthType,
183    ) -> Result<R, ApiError> {
184        let request = self.build_request(reqwest::Method::PUT, path, auth).await?;
185        let response = request.json(&body).send().await?;
186        Self::handle_response(response).await
187    }
188
189    pub(crate) async fn patch<
190        T: Serialize + std::fmt::Debug,
191        R: for<'de> serde::Deserialize<'de>,
192    >(
193        &self,
194        path: impl Into<String>,
195        body: T,
196        auth: AuthType,
197    ) -> Result<R, ApiError> {
198        let request = self
199            .build_request(reqwest::Method::PATCH, path, auth)
200            .await?;
201        let response = request.json(&body).send().await?;
202        Self::handle_response(response).await
203    }
204
205    pub(crate) async fn delete<R: for<'de> serde::Deserialize<'de>>(
206        &self,
207        path: impl Into<String>,
208        auth: AuthType,
209    ) -> Result<R, ApiError> {
210        self.delete_with_body(path, {}, auth).await
211    }
212
213    pub(crate) async fn delete_with_body<
214        T: Serialize + std::fmt::Debug,
215        R: for<'de> serde::Deserialize<'de>,
216    >(
217        &self,
218        path: impl Into<String>,
219        body: T,
220        auth: AuthType,
221    ) -> Result<R, ApiError> {
222        let request = self
223            .build_request(reqwest::Method::DELETE, path, auth)
224            .await?;
225        let response = request.json(&body).send().await?;
226        Self::handle_response(response).await
227    }
228
229    pub(crate) async fn post_multipart<
230        T: Serialize + std::fmt::Debug,
231        R: for<'de> serde::Deserialize<'de>,
232    >(
233        &self,
234        path: impl Into<String>,
235        request: T,
236        auth: AuthType,
237    ) -> Result<R, ApiError> {
238        let url = format!("{}/{}/{}", self.config.base_url, "open_api", path.into());
239        let mut request_builder = self.http_client.request(reqwest::Method::POST, &url);
240
241        // Add auth headers
242        match auth {
243            AuthType::Plugin { user_key } => {
244                let plugin_token = self
245                    .token_manager
246                    .require_plugin_token()
247                    .await
248                    .map_err(|e| ApiError::Other(e.to_string()))?;
249                request_builder = request_builder
250                    .header("X-Plugin-Token", plugin_token)
251                    .header("X-User-Key", user_key);
252            }
253            AuthType::User { token } => {
254                request_builder = request_builder.header("X-Plugin-Token", token);
255            }
256        }
257
258        // Convert request to Form
259        let mut form = Form::new();
260
261        // Serialize request to get fields
262        let value = serde_json::to_value(&request)?;
263        if let serde_json::Value::Object(map) = value {
264            // Get filename first
265            let filename = map
266                .get("filename")
267                .and_then(|v| v.as_str())
268                .map(|s| s.to_string())
269                .unwrap_or_else(|| "file".to_string());
270
271            for (key, value) in map {
272                match value {
273                    serde_json::Value::Array(bytes) if key == "file" => {
274                        let bytes: Vec<u8> = bytes
275                            .into_iter()
276                            .map(|v| v.as_u64().unwrap_or(0) as u8)
277                            .collect();
278
279                        let mime_type = from_path(&filename).first_or_octet_stream().to_string();
280
281                        let part = Part::bytes(bytes)
282                            .file_name(filename.clone())
283                            .mime_str(&mime_type)?;
284                        form = form.part("file", part);
285                    }
286                    _ => {
287                        if !value.is_null() && key != "filename" {
288                            let text = match value {
289                                serde_json::Value::String(s) => s,
290                                serde_json::Value::Number(n) => n.to_string(),
291                                serde_json::Value::Bool(b) => b.to_string(),
292                                _ => value.to_string(),
293                            };
294                            form = form.text(key, text);
295                        }
296                    }
297                }
298            }
299        }
300
301        let response = request_builder.multipart(form).send().await?;
302
303        Self::handle_response(response).await
304    }
305
306    pub(crate) async fn post_binary<T: Serialize + std::fmt::Debug>(
307        &self,
308        path: impl Into<String>,
309        body: T,
310        auth: AuthType,
311    ) -> Result<Vec<u8>, ApiError> {
312        let request = self
313            .build_request(reqwest::Method::POST, path, auth)
314            .await?;
315        let response = request.json(&body).send().await?;
316        Self::handle_binary_response(response).await
317    }
318}