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#[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
30pub 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 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 let mut form = Form::new();
260
261 let value = serde_json::to_value(&request)?;
263 if let serde_json::Value::Object(map) = value {
264 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}