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) _phantom: std::marker::PhantomData<R>,
112}
113
114impl<R> ApiRequest<R> {
115 pub fn get(url: impl Into<String>) -> Self {
117 Self {
118 method: HttpMethod::Get,
119 url: url.into(),
120 headers: HashMap::new(),
121 query: HashMap::new(),
122 body: None,
123 file: None,
124 timeout: None,
125 _phantom: std::marker::PhantomData,
126 }
127 }
128
129 pub fn post(url: impl Into<String>) -> Self {
131 Self {
132 method: HttpMethod::Post,
133 url: url.into(),
134 headers: HashMap::new(),
135 query: HashMap::new(),
136 body: None,
137 file: None,
138 timeout: None,
139 _phantom: std::marker::PhantomData,
140 }
141 }
142
143 pub fn put(url: impl Into<String>) -> Self {
145 Self {
146 method: HttpMethod::Put,
147 url: url.into(),
148 headers: HashMap::new(),
149 query: HashMap::new(),
150 body: None,
151 file: None,
152 timeout: None,
153 _phantom: std::marker::PhantomData,
154 }
155 }
156
157 pub fn patch(url: impl Into<String>) -> Self {
159 Self {
160 method: HttpMethod::Patch,
161 url: url.into(),
162 headers: HashMap::new(),
163 query: HashMap::new(),
164 body: None,
165 file: None,
166 timeout: None,
167 _phantom: std::marker::PhantomData,
168 }
169 }
170
171 pub fn delete(url: impl Into<String>) -> Self {
173 Self {
174 method: HttpMethod::Delete,
175 url: url.into(),
176 headers: HashMap::new(),
177 query: HashMap::new(),
178 body: None,
179 file: None,
180 timeout: None,
181 _phantom: std::marker::PhantomData,
182 }
183 }
184
185 pub fn header<K, V>(mut self, key: K, value: V) -> Self
187 where
188 K: Into<String>,
189 V: Into<String>,
190 {
191 self.headers.insert(key.into(), value.into());
192 self
193 }
194
195 pub fn query<K, V>(mut self, key: K, value: V) -> Self
197 where
198 K: Into<String>,
199 V: Into<String>,
200 {
201 self.query.insert(key.into(), value.into());
202 self
203 }
204
205 pub fn query_opt<K, V>(mut self, key: K, value: Option<V>) -> Self
207 where
208 K: Into<String>,
209 V: Into<String>,
210 {
211 if let Some(v) = value {
212 self.query.insert(key.into(), v.into());
213 }
214 self
215 }
216
217 pub fn body(mut self, body: impl Into<RequestData>) -> Self {
219 self.body = Some(body.into());
220 self
221 }
222
223 pub fn file_content(mut self, file: Vec<u8>) -> Self {
225 self.file = Some(file);
226 self
227 }
228
229 pub fn json_body<T>(mut self, body: &T) -> Self
231 where
232 T: serde::Serialize,
233 {
234 match serde_json::to_value(body) {
235 Ok(json_value) => self.body = Some(RequestData::Json(json_value)),
236 Err(e) => {
237 tracing::warn!(error = %e, "json_body 序列化失败");
238 self.body = Some(RequestData::Json(serde_json::Value::Null));
239 }
240 }
241 self
242 }
243
244 pub fn timeout(mut self, duration: Duration) -> Self {
246 self.timeout = Some(duration);
247 self
248 }
249
250 pub fn build_url(&self) -> String {
252 if self.query.is_empty() {
253 self.url.clone()
254 } else {
255 let query_string = self
256 .query
257 .iter()
258 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
259 .collect::<Vec<_>>()
260 .join("&");
261 format!("{}?{}", self.url, query_string)
262 }
263 }
264
265 pub fn method(&self) -> &HttpMethod {
268 &self.method
269 }
270
271 pub fn api_path(&self) -> &str {
273 if let Some(start) = self.url.find(crate::constants::API_PATH_PREFIX) {
275 &self.url[start..]
276 } else {
277 &self.url
278 }
279 }
280
281 pub fn supported_access_token_types(&self) -> Vec<crate::constants::AccessTokenType> {
283 vec![
285 crate::constants::AccessTokenType::User,
286 crate::constants::AccessTokenType::Tenant,
287 ]
288 }
289
290 pub fn to_bytes(&self) -> Vec<u8> {
292 match &self.body {
293 Some(RequestData::Json(data)) => serde_json::to_vec(data).unwrap_or_default(),
294 Some(RequestData::Binary(data)) => data.clone(),
295 Some(RequestData::Form(data)) => data
296 .iter()
297 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
298 .collect::<Vec<_>>()
299 .join("&")
300 .into_bytes(),
301 Some(RequestData::Text(text)) => text.clone().into_bytes(),
302 None => vec![],
303 }
304 }
305
306 pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
308 &mut self.headers
309 }
310
311 pub fn query_mut(&mut self) -> &mut HashMap<String, String> {
313 &mut self.query
314 }
315
316 pub fn file(&self) -> Vec<u8> {
318 self.file.clone().unwrap_or_default()
319 }
320
321 pub fn request_option(mut self, option: crate::req_option::RequestOption) -> Self {
323 for (key, value) in option.header {
325 self = self.header(key, value);
326 }
327 self
328 }
329
330 pub fn query_param<K, V>(mut self, key: K, value: V) -> Self
332 where
333 K: Into<String>,
334 V: Into<String>,
335 {
336 self.query.insert(key.into(), value.into());
337 self
338 }
339
340 pub fn query_params<I, K, V>(mut self, params: I) -> Self
342 where
343 I: IntoIterator<Item = (K, V)>,
344 K: Into<String>,
345 V: Into<String>,
346 {
347 for (key, value) in params {
348 self.query.insert(key.into(), value.into());
349 }
350 self
351 }
352}
353
354impl<R> Default for ApiRequest<R> {
355 fn default() -> Self {
356 Self {
357 method: HttpMethod::Get,
358 url: String::default(),
359 headers: HashMap::new(),
360 query: HashMap::new(),
361 body: None,
362 file: None,
363 timeout: None,
364 _phantom: std::marker::PhantomData,
365 }
366 }
367}
368
369pub type ApiResponse<R> = Response<R>;
372
373pub mod prelude;
376pub mod responses;
377pub mod traits;
378
379pub use traits::{AsyncApiClient, SyncApiClient};
382
383#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_patch_method() {
391 let request: ApiRequest<()> = ApiRequest::patch("https://example.com/api/resource");
393
394 assert_eq!(request.method, HttpMethod::Patch);
396
397 assert_eq!(request.url, "https://example.com/api/resource");
399
400 assert_eq!(request.method.as_str(), "PATCH");
402
403 println!("✅ Patch method test passed!");
404 }
405
406 #[test]
407 fn test_all_http_methods() {
408 let get_req: ApiRequest<()> = ApiRequest::get("https://example.com/api");
410 let post_req: ApiRequest<()> = ApiRequest::post("https://example.com/api");
411 let put_req: ApiRequest<()> = ApiRequest::put("https://example.com/api");
412 let patch_req: ApiRequest<()> = ApiRequest::patch("https://example.com/api");
413 let delete_req: ApiRequest<()> = ApiRequest::delete("https://example.com/api");
414
415 assert_eq!(get_req.method, HttpMethod::Get);
417 assert_eq!(post_req.method, HttpMethod::Post);
418 assert_eq!(put_req.method, HttpMethod::Put);
419 assert_eq!(patch_req.method, HttpMethod::Patch);
420 assert_eq!(delete_req.method, HttpMethod::Delete);
421
422 assert_eq!(get_req.method.as_str(), "GET");
424 assert_eq!(post_req.method.as_str(), "POST");
425 assert_eq!(put_req.method.as_str(), "PUT");
426 assert_eq!(patch_req.method.as_str(), "PATCH");
427 assert_eq!(delete_req.method.as_str(), "DELETE");
428
429 println!("✅ All HTTP methods test passed!");
430 }
431}