1pub use responses::RawResponse;
9use std::{collections::HashMap, time::Duration};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum HttpMethod {
14 Get,
16 Post,
18 Put,
20 Delete,
22 Patch,
24 Head,
26 Options,
28}
29
30impl HttpMethod {
31 pub fn as_str(self) -> &'static str {
33 match self {
34 HttpMethod::Get => "GET",
35 HttpMethod::Post => "POST",
36 HttpMethod::Put => "PUT",
37 HttpMethod::Delete => "DELETE",
38 HttpMethod::Patch => "PATCH",
39 HttpMethod::Head => "HEAD",
40 HttpMethod::Options => "OPTIONS",
41 }
42 }
43}
44
45impl std::fmt::Display for HttpMethod {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "{}", self.as_str())
48 }
49}
50
51#[derive(Debug, Clone)]
53pub enum RequestData {
54 Json(serde_json::Value),
56 Text(String),
58 Binary(Vec<u8>),
60 Form(std::collections::HashMap<String, String>),
62}
63
64impl From<String> for RequestData {
66 fn from(value: String) -> Self {
67 RequestData::Text(value)
68 }
69}
70
71impl From<&str> for RequestData {
72 fn from(value: &str) -> Self {
73 RequestData::Text(value.to_string())
74 }
75}
76
77impl From<serde_json::Value> for RequestData {
79 fn from(value: serde_json::Value) -> Self {
80 RequestData::Json(value)
81 }
82}
83
84impl From<Vec<u8>> for RequestData {
86 fn from(value: Vec<u8>) -> Self {
87 RequestData::Binary(value)
88 }
89}
90
91impl From<std::collections::HashMap<String, String>> for RequestData {
93 fn from(value: std::collections::HashMap<String, String>) -> Self {
94 RequestData::Form(value)
95 }
96}
97
98pub use responses::{ApiResponseTrait, BaseResponse, ErrorInfo, Response, ResponseFormat};
100
101#[derive(Debug, Clone)]
103pub struct ApiRequest<R> {
104 pub(crate) method: HttpMethod,
105 pub(crate) url: String,
106 pub(crate) headers: HashMap<String, String>,
107 pub(crate) query: HashMap<String, String>,
108 pub(crate) body: Option<RequestData>,
109 pub(crate) file: Option<Vec<u8>>,
110 pub(crate) timeout: Option<Duration>,
111 pub(crate) supported_access_token_types: Option<Vec<crate::constants::AccessTokenType>>,
112 pub(crate) _phantom: std::marker::PhantomData<R>,
113}
114
115impl<R> ApiRequest<R> {
116 pub fn get(url: impl Into<String>) -> Self {
118 Self {
119 method: HttpMethod::Get,
120 url: url.into(),
121 headers: HashMap::new(),
122 query: HashMap::new(),
123 body: None,
124 file: None,
125 timeout: None,
126 supported_access_token_types: None,
127 _phantom: std::marker::PhantomData,
128 }
129 }
130
131 pub fn post(url: impl Into<String>) -> Self {
133 Self {
134 method: HttpMethod::Post,
135 url: url.into(),
136 headers: HashMap::new(),
137 query: HashMap::new(),
138 body: None,
139 file: None,
140 timeout: None,
141 supported_access_token_types: None,
142 _phantom: std::marker::PhantomData,
143 }
144 }
145
146 pub fn put(url: impl Into<String>) -> Self {
148 Self {
149 method: HttpMethod::Put,
150 url: url.into(),
151 headers: HashMap::new(),
152 query: HashMap::new(),
153 body: None,
154 file: None,
155 timeout: None,
156 supported_access_token_types: None,
157 _phantom: std::marker::PhantomData,
158 }
159 }
160
161 pub fn patch(url: impl Into<String>) -> Self {
163 Self {
164 method: HttpMethod::Patch,
165 url: url.into(),
166 headers: HashMap::new(),
167 query: HashMap::new(),
168 body: None,
169 file: None,
170 timeout: None,
171 supported_access_token_types: None,
172 _phantom: std::marker::PhantomData,
173 }
174 }
175
176 pub fn delete(url: impl Into<String>) -> Self {
178 Self {
179 method: HttpMethod::Delete,
180 url: url.into(),
181 headers: HashMap::new(),
182 query: HashMap::new(),
183 body: None,
184 file: None,
185 timeout: None,
186 supported_access_token_types: None,
187 _phantom: std::marker::PhantomData,
188 }
189 }
190
191 pub fn header<K, V>(mut self, key: K, value: V) -> Self
193 where
194 K: Into<String>,
195 V: Into<String>,
196 {
197 self.headers.insert(key.into(), value.into());
198 self
199 }
200
201 pub fn query<K, V>(mut self, key: K, value: V) -> Self
203 where
204 K: Into<String>,
205 V: Into<String>,
206 {
207 self.query.insert(key.into(), value.into());
208 self
209 }
210
211 pub fn query_opt<K, V>(mut self, key: K, value: Option<V>) -> Self
213 where
214 K: Into<String>,
215 V: Into<String>,
216 {
217 if let Some(v) = value {
218 self.query.insert(key.into(), v.into());
219 }
220 self
221 }
222
223 pub fn body(mut self, body: impl Into<RequestData>) -> Self {
225 self.body = Some(body.into());
226 self
227 }
228
229 pub fn file_content(mut self, file: Vec<u8>) -> Self {
231 self.file = Some(file);
232 self
233 }
234
235 pub fn json_body<T>(mut self, body: &T) -> Self
237 where
238 T: serde::Serialize,
239 {
240 match serde_json::to_value(body) {
241 Ok(json_value) => self.body = Some(RequestData::Json(json_value)),
242 Err(e) => {
243 tracing::warn!(error = %e, "json_body 序列化失败");
244 self.body = Some(RequestData::Json(serde_json::Value::Null));
245 }
246 }
247 self
248 }
249
250 pub fn timeout(mut self, duration: Duration) -> Self {
252 self.timeout = Some(duration);
253 self
254 }
255
256 pub fn with_supported_access_token_types(
258 mut self,
259 token_types: Vec<crate::constants::AccessTokenType>,
260 ) -> Self {
261 self.supported_access_token_types = Some(token_types);
262 self
263 }
264
265 pub fn build_url(&self) -> String {
267 if self.query.is_empty() {
268 self.url.clone()
269 } else {
270 let query_string = self
271 .query
272 .iter()
273 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
274 .collect::<Vec<_>>()
275 .join("&");
276 format!("{}?{}", self.url, query_string)
277 }
278 }
279
280 pub fn method(&self) -> &HttpMethod {
283 &self.method
284 }
285
286 pub fn api_path(&self) -> &str {
288 if let Some(start) = self.url.find(crate::constants::API_PATH_PREFIX) {
290 &self.url[start..]
291 } else {
292 &self.url
293 }
294 }
295
296 pub fn supported_access_token_types(&self) -> Vec<crate::constants::AccessTokenType> {
298 if let Some(token_types) = &self.supported_access_token_types {
299 return token_types.clone();
300 }
301
302 vec![
304 crate::constants::AccessTokenType::User,
305 crate::constants::AccessTokenType::Tenant,
306 ]
307 }
308
309 pub fn to_bytes(&self) -> Vec<u8> {
311 match &self.body {
312 Some(RequestData::Json(data)) => serde_json::to_vec(data).unwrap_or_default(),
313 Some(RequestData::Binary(data)) => data.clone(),
314 Some(RequestData::Form(data)) => data
315 .iter()
316 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
317 .collect::<Vec<_>>()
318 .join("&")
319 .into_bytes(),
320 Some(RequestData::Text(text)) => text.clone().into_bytes(),
321 None => vec![],
322 }
323 }
324
325 pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
327 &mut self.headers
328 }
329
330 pub fn query_mut(&mut self) -> &mut HashMap<String, String> {
332 &mut self.query
333 }
334
335 pub fn file(&self) -> Vec<u8> {
337 self.file.clone().unwrap_or_default()
338 }
339
340 pub fn request_option(mut self, option: crate::req_option::RequestOption) -> Self {
342 for (key, value) in option.header {
344 self = self.header(key, value);
345 }
346 self
347 }
348
349 pub fn query_param<K, V>(mut self, key: K, value: V) -> Self
351 where
352 K: Into<String>,
353 V: Into<String>,
354 {
355 self.query.insert(key.into(), value.into());
356 self
357 }
358
359 pub fn query_params<I, K, V>(mut self, params: I) -> Self
361 where
362 I: IntoIterator<Item = (K, V)>,
363 K: Into<String>,
364 V: Into<String>,
365 {
366 for (key, value) in params {
367 self.query.insert(key.into(), value.into());
368 }
369 self
370 }
371}
372
373impl<R> Default for ApiRequest<R> {
374 fn default() -> Self {
375 Self {
376 method: HttpMethod::Get,
377 url: String::default(),
378 headers: HashMap::new(),
379 query: HashMap::new(),
380 body: None,
381 file: None,
382 timeout: None,
383 supported_access_token_types: None,
384 _phantom: std::marker::PhantomData,
385 }
386 }
387}
388
389pub type ApiResponse<R> = Response<R>;
392
393pub mod helpers;
396pub mod prelude;
397pub mod responses;
398pub mod traits;
399
400pub use helpers::{ensure_success, extract_response_data, serialize_params};
403pub use traits::{AsyncApiClient, SyncApiClient};
404
405#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn test_patch_method() {
413 let request: ApiRequest<()> = ApiRequest::patch("https://example.com/api/resource");
415
416 assert_eq!(request.method, HttpMethod::Patch);
418
419 assert_eq!(request.url, "https://example.com/api/resource");
421
422 assert_eq!(request.method.as_str(), "PATCH");
424
425 println!("✅ Patch method test passed!");
426 }
427
428 #[test]
429 fn test_all_http_methods() {
430 let get_req: ApiRequest<()> = ApiRequest::get("https://example.com/api");
432 let post_req: ApiRequest<()> = ApiRequest::post("https://example.com/api");
433 let put_req: ApiRequest<()> = ApiRequest::put("https://example.com/api");
434 let patch_req: ApiRequest<()> = ApiRequest::patch("https://example.com/api");
435 let delete_req: ApiRequest<()> = ApiRequest::delete("https://example.com/api");
436
437 assert_eq!(get_req.method, HttpMethod::Get);
439 assert_eq!(post_req.method, HttpMethod::Post);
440 assert_eq!(put_req.method, HttpMethod::Put);
441 assert_eq!(patch_req.method, HttpMethod::Patch);
442 assert_eq!(delete_req.method, HttpMethod::Delete);
443
444 assert_eq!(get_req.method.as_str(), "GET");
446 assert_eq!(post_req.method.as_str(), "POST");
447 assert_eq!(put_req.method.as_str(), "PUT");
448 assert_eq!(patch_req.method.as_str(), "PATCH");
449 assert_eq!(delete_req.method.as_str(), "DELETE");
450
451 println!("✅ All HTTP methods test passed!");
452 }
453
454 #[test]
455 fn test_supported_access_token_types_override() {
456 let request: ApiRequest<()> = ApiRequest::post("/open-apis/authen/v1/access_token")
457 .with_supported_access_token_types(vec![crate::constants::AccessTokenType::App]);
458
459 assert_eq!(
460 request.supported_access_token_types(),
461 vec![crate::constants::AccessTokenType::App]
462 );
463 }
464}