1pub use responses::RawResponse;
11use std::{collections::HashMap, time::Duration};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum HttpMethod {
16 Get,
17 Post,
18 Put,
19 Delete,
20 Patch,
21 Head,
22 Options,
23}
24
25impl HttpMethod {
26 pub fn as_str(self) -> &'static str {
27 match self {
28 HttpMethod::Get => "GET",
29 HttpMethod::Post => "POST",
30 HttpMethod::Put => "PUT",
31 HttpMethod::Delete => "DELETE",
32 HttpMethod::Patch => "PATCH",
33 HttpMethod::Head => "HEAD",
34 HttpMethod::Options => "OPTIONS",
35 }
36 }
37}
38
39impl std::fmt::Display for HttpMethod {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}", self.as_str())
42 }
43}
44
45#[derive(Debug, Clone)]
47pub enum RequestData {
48 Json(serde_json::Value),
49 Text(String),
50 Binary(Vec<u8>),
51 Form(std::collections::HashMap<String, String>),
52}
53
54impl From<String> for RequestData {
56 fn from(value: String) -> Self {
57 RequestData::Text(value)
58 }
59}
60
61impl From<&str> for RequestData {
62 fn from(value: &str) -> Self {
63 RequestData::Text(value.to_string())
64 }
65}
66
67impl From<serde_json::Value> for RequestData {
69 fn from(value: serde_json::Value) -> Self {
70 RequestData::Json(value)
71 }
72}
73
74impl From<Vec<u8>> for RequestData {
76 fn from(value: Vec<u8>) -> Self {
77 RequestData::Binary(value)
78 }
79}
80
81impl From<std::collections::HashMap<String, String>> for RequestData {
83 fn from(value: std::collections::HashMap<String, String>) -> Self {
84 RequestData::Form(value)
85 }
86}
87
88pub use responses::{ApiResponseTrait, BaseResponse, ErrorInfo, Response, ResponseFormat};
90
91#[derive(Debug, Clone)]
93pub struct ApiRequest<R> {
94 pub(crate) method: HttpMethod,
95 pub(crate) url: String,
96 pub(crate) headers: HashMap<String, String>,
97 pub(crate) query: HashMap<String, String>,
98 pub(crate) body: Option<RequestData>,
99 pub(crate) file: Option<Vec<u8>>,
100 pub(crate) timeout: Option<Duration>,
101 pub(crate) _phantom: std::marker::PhantomData<R>,
102}
103
104impl<R> ApiRequest<R> {
105 pub fn get(url: impl Into<String>) -> Self {
106 Self {
107 method: HttpMethod::Get,
108 url: url.into(),
109 headers: HashMap::new(),
110 query: HashMap::new(),
111 body: None,
112 file: None,
113 timeout: None,
114 _phantom: std::marker::PhantomData,
115 }
116 }
117
118 pub fn post(url: impl Into<String>) -> Self {
119 Self {
120 method: HttpMethod::Post,
121 url: url.into(),
122 headers: HashMap::new(),
123 query: HashMap::new(),
124 body: None,
125 file: None,
126 timeout: None,
127 _phantom: std::marker::PhantomData,
128 }
129 }
130
131 pub fn put(url: impl Into<String>) -> Self {
132 Self {
133 method: HttpMethod::Put,
134 url: url.into(),
135 headers: HashMap::new(),
136 query: HashMap::new(),
137 body: None,
138 file: None,
139 timeout: None,
140 _phantom: std::marker::PhantomData,
141 }
142 }
143
144 pub fn patch(url: impl Into<String>) -> Self {
145 Self {
146 method: HttpMethod::Patch,
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 delete(url: impl Into<String>) -> Self {
158 Self {
159 method: HttpMethod::Delete,
160 url: url.into(),
161 headers: HashMap::new(),
162 query: HashMap::new(),
163 body: None,
164 file: None,
165 timeout: None,
166 _phantom: std::marker::PhantomData,
167 }
168 }
169
170 pub fn header<K, V>(mut self, key: K, value: V) -> Self
171 where
172 K: Into<String>,
173 V: Into<String>,
174 {
175 self.headers.insert(key.into(), value.into());
176 self
177 }
178
179 pub fn query<K, V>(mut self, key: K, value: V) -> Self
180 where
181 K: Into<String>,
182 V: Into<String>,
183 {
184 self.query.insert(key.into(), value.into());
185 self
186 }
187
188 pub fn query_opt<K, V>(mut self, key: K, value: Option<V>) -> Self
190 where
191 K: Into<String>,
192 V: Into<String>,
193 {
194 if let Some(v) = value {
195 self.query.insert(key.into(), v.into());
196 }
197 self
198 }
199
200 pub fn body(mut self, body: impl Into<RequestData>) -> Self {
201 self.body = Some(body.into());
202 self
203 }
204
205 pub fn file_content(mut self, file: Vec<u8>) -> Self {
207 self.file = Some(file);
208 self
209 }
210
211 pub fn json_body<T>(mut self, body: &T) -> Self
213 where
214 T: serde::Serialize,
215 {
216 match serde_json::to_value(body) {
217 Ok(json_value) => self.body = Some(RequestData::Json(json_value)),
218 Err(e) => {
219 tracing::warn!(error = %e, "json_body 序列化失败");
220 self.body = Some(RequestData::Json(serde_json::Value::Null));
221 }
222 }
223 self
224 }
225
226 pub fn timeout(mut self, duration: Duration) -> Self {
227 self.timeout = Some(duration);
228 self
229 }
230
231 pub fn build_url(&self) -> String {
232 if self.query.is_empty() {
233 self.url.clone()
234 } else {
235 let query_string = self
236 .query
237 .iter()
238 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
239 .collect::<Vec<_>>()
240 .join("&");
241 format!("{}?{}", self.url, query_string)
242 }
243 }
244
245 pub fn method(&self) -> &HttpMethod {
247 &self.method
248 }
249
250 pub fn api_path(&self) -> &str {
251 if let Some(start) = self.url.find(crate::constants::API_PATH_PREFIX) {
253 &self.url[start..]
254 } else {
255 &self.url
256 }
257 }
258
259 pub fn supported_access_token_types(&self) -> Vec<crate::constants::AccessTokenType> {
260 vec![
262 crate::constants::AccessTokenType::User,
263 crate::constants::AccessTokenType::Tenant,
264 ]
265 }
266
267 pub fn to_bytes(&self) -> Vec<u8> {
268 match &self.body {
269 Some(RequestData::Json(data)) => serde_json::to_vec(data).unwrap_or_default(),
270 Some(RequestData::Binary(data)) => data.clone(),
271 Some(RequestData::Form(data)) => data
272 .iter()
273 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
274 .collect::<Vec<_>>()
275 .join("&")
276 .into_bytes(),
277 Some(RequestData::Text(text)) => text.clone().into_bytes(),
278 None => vec![],
279 }
280 }
281
282 pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
284 &mut self.headers
285 }
286
287 pub fn query_mut(&mut self) -> &mut HashMap<String, String> {
289 &mut self.query
290 }
291
292 pub fn file(&self) -> Vec<u8> {
293 self.file.clone().unwrap_or_default()
294 }
295
296 pub fn request_option(mut self, option: crate::req_option::RequestOption) -> Self {
298 for (key, value) in option.header {
300 self = self.header(key, value);
301 }
302 self
303 }
304
305 pub fn query_param<K, V>(mut self, key: K, value: V) -> Self
307 where
308 K: Into<String>,
309 V: Into<String>,
310 {
311 self.query.insert(key.into(), value.into());
312 self
313 }
314
315 pub fn query_params<I, K, V>(mut self, params: I) -> Self
317 where
318 I: IntoIterator<Item = (K, V)>,
319 K: Into<String>,
320 V: Into<String>,
321 {
322 for (key, value) in params {
323 self.query.insert(key.into(), value.into());
324 }
325 self
326 }
327}
328
329impl<R> Default for ApiRequest<R> {
330 fn default() -> Self {
331 Self {
332 method: HttpMethod::Get,
333 url: String::default(),
334 headers: HashMap::new(),
335 query: HashMap::new(),
336 body: None,
337 file: None,
338 timeout: None,
339 _phantom: std::marker::PhantomData,
340 }
341 }
342}
343
344pub type ApiResponse<R> = Response<R>;
346
347pub mod prelude;
352pub mod responses;
353pub mod traits;
354
355pub use traits::{AsyncApiClient, SyncApiClient};
360
361#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_patch_method() {
371 let request: ApiRequest<()> = ApiRequest::patch("https://example.com/api/resource");
373
374 assert_eq!(request.method, HttpMethod::Patch);
376
377 assert_eq!(request.url, "https://example.com/api/resource");
379
380 assert_eq!(request.method.as_str(), "PATCH");
382
383 println!("✅ Patch method test passed!");
384 }
385
386 #[test]
387 fn test_all_http_methods() {
388 let get_req: ApiRequest<()> = ApiRequest::get("https://example.com/api");
390 let post_req: ApiRequest<()> = ApiRequest::post("https://example.com/api");
391 let put_req: ApiRequest<()> = ApiRequest::put("https://example.com/api");
392 let patch_req: ApiRequest<()> = ApiRequest::patch("https://example.com/api");
393 let delete_req: ApiRequest<()> = ApiRequest::delete("https://example.com/api");
394
395 assert_eq!(get_req.method, HttpMethod::Get);
397 assert_eq!(post_req.method, HttpMethod::Post);
398 assert_eq!(put_req.method, HttpMethod::Put);
399 assert_eq!(patch_req.method, HttpMethod::Patch);
400 assert_eq!(delete_req.method, HttpMethod::Delete);
401
402 assert_eq!(get_req.method.as_str(), "GET");
404 assert_eq!(post_req.method.as_str(), "POST");
405 assert_eq!(put_req.method.as_str(), "PUT");
406 assert_eq!(patch_req.method.as_str(), "PATCH");
407 assert_eq!(delete_req.method.as_str(), "DELETE");
408
409 println!("✅ All HTTP methods test passed!");
410 }
411}