1pub use responses::RawResponse;
11use std::{collections::HashMap, time::Duration};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum HttpMethod {
16 Get,
18 Post,
20 Put,
22 Delete,
24 Patch,
26 Head,
28 Options,
30}
31
32impl HttpMethod {
33 pub fn as_str(self) -> &'static str {
35 match self {
36 HttpMethod::Get => "GET",
37 HttpMethod::Post => "POST",
38 HttpMethod::Put => "PUT",
39 HttpMethod::Delete => "DELETE",
40 HttpMethod::Patch => "PATCH",
41 HttpMethod::Head => "HEAD",
42 HttpMethod::Options => "OPTIONS",
43 }
44 }
45}
46
47impl std::fmt::Display for HttpMethod {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}", self.as_str())
50 }
51}
52
53#[derive(Debug, Clone)]
55pub enum RequestData {
56 Json(serde_json::Value),
58 Text(String),
60 Binary(Vec<u8>),
62 Form(std::collections::HashMap<String, String>),
64}
65
66impl From<String> for RequestData {
68 fn from(value: String) -> Self {
69 RequestData::Text(value)
70 }
71}
72
73impl From<&str> for RequestData {
74 fn from(value: &str) -> Self {
75 RequestData::Text(value.to_string())
76 }
77}
78
79impl From<serde_json::Value> for RequestData {
81 fn from(value: serde_json::Value) -> Self {
82 RequestData::Json(value)
83 }
84}
85
86impl From<Vec<u8>> for RequestData {
88 fn from(value: Vec<u8>) -> Self {
89 RequestData::Binary(value)
90 }
91}
92
93impl From<std::collections::HashMap<String, String>> for RequestData {
95 fn from(value: std::collections::HashMap<String, String>) -> Self {
96 RequestData::Form(value)
97 }
98}
99
100pub use responses::{ApiResponseTrait, BaseResponse, ErrorInfo, Response, ResponseFormat};
102
103#[derive(Debug, Clone)]
105pub struct ApiRequest<R> {
106 pub(crate) method: HttpMethod,
107 pub(crate) url: String,
108 pub(crate) headers: HashMap<String, String>,
109 pub(crate) query: HashMap<String, String>,
110 pub(crate) body: Option<RequestData>,
111 pub(crate) file: Option<Vec<u8>>,
112 pub(crate) timeout: Option<Duration>,
113 pub(crate) _phantom: std::marker::PhantomData<R>,
114}
115
116impl<R> ApiRequest<R> {
117 pub fn get(url: impl Into<String>) -> Self {
119 Self {
120 method: HttpMethod::Get,
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 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 _phantom: std::marker::PhantomData,
142 }
143 }
144
145 pub fn put(url: impl Into<String>) -> Self {
147 Self {
148 method: HttpMethod::Put,
149 url: url.into(),
150 headers: HashMap::new(),
151 query: HashMap::new(),
152 body: None,
153 file: None,
154 timeout: None,
155 _phantom: std::marker::PhantomData,
156 }
157 }
158
159 pub fn patch(url: impl Into<String>) -> Self {
161 Self {
162 method: HttpMethod::Patch,
163 url: url.into(),
164 headers: HashMap::new(),
165 query: HashMap::new(),
166 body: None,
167 file: None,
168 timeout: None,
169 _phantom: std::marker::PhantomData,
170 }
171 }
172
173 pub fn delete(url: impl Into<String>) -> Self {
175 Self {
176 method: HttpMethod::Delete,
177 url: url.into(),
178 headers: HashMap::new(),
179 query: HashMap::new(),
180 body: None,
181 file: None,
182 timeout: None,
183 _phantom: std::marker::PhantomData,
184 }
185 }
186
187 pub fn header<K, V>(mut self, key: K, value: V) -> Self
189 where
190 K: Into<String>,
191 V: Into<String>,
192 {
193 self.headers.insert(key.into(), value.into());
194 self
195 }
196
197 pub fn query<K, V>(mut self, key: K, value: V) -> Self
199 where
200 K: Into<String>,
201 V: Into<String>,
202 {
203 self.query.insert(key.into(), value.into());
204 self
205 }
206
207 pub fn query_opt<K, V>(mut self, key: K, value: Option<V>) -> Self
209 where
210 K: Into<String>,
211 V: Into<String>,
212 {
213 if let Some(v) = value {
214 self.query.insert(key.into(), v.into());
215 }
216 self
217 }
218
219 pub fn body(mut self, body: impl Into<RequestData>) -> Self {
221 self.body = Some(body.into());
222 self
223 }
224
225 pub fn file_content(mut self, file: Vec<u8>) -> Self {
227 self.file = Some(file);
228 self
229 }
230
231 pub fn json_body<T>(mut self, body: &T) -> Self
233 where
234 T: serde::Serialize,
235 {
236 match serde_json::to_value(body) {
237 Ok(json_value) => self.body = Some(RequestData::Json(json_value)),
238 Err(e) => {
239 tracing::warn!(error = %e, "json_body 序列化失败");
240 self.body = Some(RequestData::Json(serde_json::Value::Null));
241 }
242 }
243 self
244 }
245
246 pub fn timeout(mut self, duration: Duration) -> Self {
248 self.timeout = Some(duration);
249 self
250 }
251
252 pub fn build_url(&self) -> String {
254 if self.query.is_empty() {
255 self.url.clone()
256 } else {
257 let query_string = self
258 .query
259 .iter()
260 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
261 .collect::<Vec<_>>()
262 .join("&");
263 format!("{}?{}", self.url, query_string)
264 }
265 }
266
267 pub fn method(&self) -> &HttpMethod {
270 &self.method
271 }
272
273 pub fn api_path(&self) -> &str {
275 if let Some(start) = self.url.find(crate::constants::API_PATH_PREFIX) {
277 &self.url[start..]
278 } else {
279 &self.url
280 }
281 }
282
283 pub fn supported_access_token_types(&self) -> Vec<crate::constants::AccessTokenType> {
285 vec![
287 crate::constants::AccessTokenType::User,
288 crate::constants::AccessTokenType::Tenant,
289 ]
290 }
291
292 pub fn to_bytes(&self) -> Vec<u8> {
294 match &self.body {
295 Some(RequestData::Json(data)) => serde_json::to_vec(data).unwrap_or_default(),
296 Some(RequestData::Binary(data)) => data.clone(),
297 Some(RequestData::Form(data)) => data
298 .iter()
299 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
300 .collect::<Vec<_>>()
301 .join("&")
302 .into_bytes(),
303 Some(RequestData::Text(text)) => text.clone().into_bytes(),
304 None => vec![],
305 }
306 }
307
308 pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
310 &mut self.headers
311 }
312
313 pub fn query_mut(&mut self) -> &mut HashMap<String, String> {
315 &mut self.query
316 }
317
318 pub fn file(&self) -> Vec<u8> {
320 self.file.clone().unwrap_or_default()
321 }
322
323 pub fn request_option(mut self, option: crate::req_option::RequestOption) -> Self {
325 for (key, value) in option.header {
327 self = self.header(key, value);
328 }
329 self
330 }
331
332 pub fn query_param<K, V>(mut self, key: K, value: V) -> Self
334 where
335 K: Into<String>,
336 V: Into<String>,
337 {
338 self.query.insert(key.into(), value.into());
339 self
340 }
341
342 pub fn query_params<I, K, V>(mut self, params: I) -> Self
344 where
345 I: IntoIterator<Item = (K, V)>,
346 K: Into<String>,
347 V: Into<String>,
348 {
349 for (key, value) in params {
350 self.query.insert(key.into(), value.into());
351 }
352 self
353 }
354}
355
356impl<R> Default for ApiRequest<R> {
357 fn default() -> Self {
358 Self {
359 method: HttpMethod::Get,
360 url: String::default(),
361 headers: HashMap::new(),
362 query: HashMap::new(),
363 body: None,
364 file: None,
365 timeout: None,
366 _phantom: std::marker::PhantomData,
367 }
368 }
369}
370
371pub type ApiResponse<R> = Response<R>;
374
375pub mod prelude;
380pub mod responses;
381pub mod traits;
382
383pub use traits::{AsyncApiClient, SyncApiClient};
388
389#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn test_patch_method() {
399 let request: ApiRequest<()> = ApiRequest::patch("https://example.com/api/resource");
401
402 assert_eq!(request.method, HttpMethod::Patch);
404
405 assert_eq!(request.url, "https://example.com/api/resource");
407
408 assert_eq!(request.method.as_str(), "PATCH");
410
411 println!("✅ Patch method test passed!");
412 }
413
414 #[test]
415 fn test_all_http_methods() {
416 let get_req: ApiRequest<()> = ApiRequest::get("https://example.com/api");
418 let post_req: ApiRequest<()> = ApiRequest::post("https://example.com/api");
419 let put_req: ApiRequest<()> = ApiRequest::put("https://example.com/api");
420 let patch_req: ApiRequest<()> = ApiRequest::patch("https://example.com/api");
421 let delete_req: ApiRequest<()> = ApiRequest::delete("https://example.com/api");
422
423 assert_eq!(get_req.method, HttpMethod::Get);
425 assert_eq!(post_req.method, HttpMethod::Post);
426 assert_eq!(put_req.method, HttpMethod::Put);
427 assert_eq!(patch_req.method, HttpMethod::Patch);
428 assert_eq!(delete_req.method, HttpMethod::Delete);
429
430 assert_eq!(get_req.method.as_str(), "GET");
432 assert_eq!(post_req.method.as_str(), "POST");
433 assert_eq!(put_req.method.as_str(), "PUT");
434 assert_eq!(patch_req.method.as_str(), "PATCH");
435 assert_eq!(delete_req.method.as_str(), "DELETE");
436
437 println!("✅ All HTTP methods test passed!");
438 }
439}