1use bytes::Bytes;
6use http::{HeaderMap, HeaderValue, Method, Request};
7use http_body_util::Full;
8use serde::Serialize;
9use serde_json::Value;
10use std::collections::HashMap;
11
12use crate::client::ClientError;
13
14pub struct APIRequestFactory {
16 default_format: String,
17 default_headers: HeaderMap,
18}
19
20impl APIRequestFactory {
21 pub fn new() -> Self {
32 Self {
33 default_format: "json".to_string(),
34 default_headers: HeaderMap::new(),
35 }
36 }
37 pub fn with_format(mut self, format: impl Into<String>) -> Self {
39 self.default_format = format.into();
40 self
41 }
42 pub fn with_header(
44 mut self,
45 name: impl AsRef<str>,
46 value: impl AsRef<str>,
47 ) -> Result<Self, ClientError> {
48 let header_name: http::header::HeaderName = name.as_ref().parse().map_err(|_| {
49 ClientError::RequestFailed(format!("Invalid header name: {}", name.as_ref()))
50 })?;
51 self.default_headers
52 .insert(header_name, HeaderValue::from_str(value.as_ref())?);
53 Ok(self)
54 }
55 pub fn get(&self, path: &str) -> RequestBuilder {
67 RequestBuilder::new(Method::GET, path, &self.default_headers)
68 }
69 pub fn post(&self, path: &str) -> RequestBuilder {
83 RequestBuilder::new(Method::POST, path, &self.default_headers)
84 .with_format(&self.default_format)
85 }
86 pub fn put(&self, path: &str) -> RequestBuilder {
100 RequestBuilder::new(Method::PUT, path, &self.default_headers)
101 .with_format(&self.default_format)
102 }
103 pub fn patch(&self, path: &str) -> RequestBuilder {
117 RequestBuilder::new(Method::PATCH, path, &self.default_headers)
118 .with_format(&self.default_format)
119 }
120 pub fn delete(&self, path: &str) -> RequestBuilder {
132 RequestBuilder::new(Method::DELETE, path, &self.default_headers)
133 }
134 pub fn head(&self, path: &str) -> RequestBuilder {
146 RequestBuilder::new(Method::HEAD, path, &self.default_headers)
147 }
148 pub fn options(&self, path: &str) -> RequestBuilder {
160 RequestBuilder::new(Method::OPTIONS, path, &self.default_headers)
161 }
162 pub fn request(&self, method: Method, path: &str) -> RequestBuilder {
175 RequestBuilder::new(method, path, &self.default_headers)
176 }
177}
178
179impl Default for APIRequestFactory {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185pub struct RequestBuilder {
187 method: Method,
188 path: String,
189 headers: HeaderMap,
190 query_params: HashMap<String, String>,
191 body: Option<Bytes>,
192 format: String,
193}
194
195impl RequestBuilder {
196 pub fn new(method: Method, path: &str, default_headers: &HeaderMap) -> Self {
198 Self {
199 method,
200 path: path.to_string(),
201 headers: default_headers.clone(),
202 query_params: HashMap::new(),
203 body: None,
204 format: "json".to_string(),
205 }
206 }
207 pub fn method(&self) -> Method {
209 self.method.clone()
210 }
211 pub fn path(&self) -> &str {
213 &self.path
214 }
215 pub fn with_format(mut self, format: &str) -> Self {
217 self.format = format.to_string();
218 self
219 }
220 pub fn header(mut self, name: &str, value: &str) -> Result<Self, ClientError> {
223 let header_name: http::header::HeaderName = name
224 .parse()
225 .map_err(|_| ClientError::RequestFailed(format!("Invalid header name: {}", name)))?;
226 self.headers
227 .insert(header_name, HeaderValue::from_str(value)?);
228 Ok(self)
229 }
230 pub fn query(mut self, key: &str, value: &str) -> Self {
232 self.query_params.insert(key.to_string(), value.to_string());
233 self
234 }
235 pub fn query_param(self, key: &str, value: &str) -> Self {
237 self.query(key, value)
238 }
239 pub fn json<T: Serialize>(mut self, data: &T) -> Result<Self, ClientError> {
252 let json = serde_json::to_vec(data)?;
253 self.body = Some(Bytes::from(json));
254 self.format = "json".to_string();
255 Ok(self)
256 }
257 pub fn form<T: Serialize>(mut self, data: &T) -> Result<Self, ClientError> {
270 let json_value = serde_json::to_value(data)?;
271 if let Value::Object(map) = json_value {
272 let form_data = map
273 .iter()
274 .map(|(k, v)| {
275 let value_str = match v {
276 Value::String(s) => s.clone(),
277 _ => v.to_string(),
278 };
279 format!(
280 "{}={}",
281 url::form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
282 url::form_urlencoded::byte_serialize(value_str.as_bytes())
283 .collect::<String>()
284 )
285 })
286 .collect::<Vec<_>>()
287 .join("&");
288 self.body = Some(Bytes::from(form_data));
289 self.format = "form".to_string();
290 Ok(self)
291 } else {
292 Err(ClientError::RequestFailed(
293 "Expected object for form data".to_string(),
294 ))
295 }
296 }
297 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
308 self.body = Some(body.into());
309 self
310 }
311 pub fn build(self) -> Result<Request<Full<Bytes>>, ClientError> {
323 let mut url = self.path.clone();
324
325 if !self.query_params.is_empty() {
327 let query_string = self
328 .query_params
329 .iter()
330 .map(|(k, v)| {
331 format!(
332 "{}={}",
333 url::form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
334 url::form_urlencoded::byte_serialize(v.as_bytes()).collect::<String>()
335 )
336 })
337 .collect::<Vec<_>>()
338 .join("&");
339 url = format!("{}?{}", url, query_string);
340 }
341
342 let mut request = Request::builder().method(self.method).uri(url);
343
344 for (name, value) in self.headers.iter() {
346 request = request.header(name, value);
347 }
348
349 if self.body.is_some() {
351 let content_type = match self.format.as_str() {
352 "json" => "application/json",
353 "form" => "application/x-www-form-urlencoded",
354 _ => "application/octet-stream",
355 };
356 request = request.header("Content-Type", content_type);
357 }
358
359 let body = self.body.unwrap_or_default();
361 let req = request.body(Full::new(body))?;
362
363 Ok(req)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use rstest::rstest;
371 use serde_json::json;
372
373 #[rstest]
378 fn test_factory_new() {
379 let factory = APIRequestFactory::new();
381
382 let request = factory.get("/api/users/").build().unwrap();
384
385 assert_eq!(request.method(), Method::GET);
387 }
388
389 #[rstest]
390 fn test_factory_default() {
391 let factory_new = APIRequestFactory::new();
393 let factory_default = APIRequestFactory::default();
394
395 let req_new = factory_new.get("/test").build().unwrap();
397 let req_default = factory_default.get("/test").build().unwrap();
398
399 assert_eq!(req_new.method(), req_default.method());
401 assert_eq!(req_new.uri(), req_default.uri());
402 }
403
404 #[rstest]
405 fn test_factory_with_format() {
406 let factory = APIRequestFactory::new().with_format("xml");
408
409 let request = factory.post("/api/data/").body("payload").build().unwrap();
411
412 assert_eq!(
414 request.headers().get("Content-Type").unwrap(),
415 "application/octet-stream"
416 );
417 }
418
419 #[rstest]
420 fn test_factory_with_header() {
421 let factory = APIRequestFactory::new()
423 .with_header("X-Custom", "value123")
424 .unwrap();
425
426 let request = factory.get("/api/items/").build().unwrap();
428
429 assert_eq!(request.headers().get("x-custom").unwrap(), "value123");
431 }
432
433 #[rstest]
434 fn test_factory_get() {
435 let factory = APIRequestFactory::new();
437
438 let request = factory.get("/api/users/").build().unwrap();
440
441 assert_eq!(request.method(), Method::GET);
443 }
444
445 #[rstest]
446 fn test_factory_post() {
447 let factory = APIRequestFactory::new();
449
450 let request = factory.post("/api/users/").build().unwrap();
452
453 assert_eq!(request.method(), Method::POST);
455 }
456
457 #[rstest]
458 fn test_factory_put() {
459 let factory = APIRequestFactory::new();
461
462 let request = factory.put("/api/users/1/").build().unwrap();
464
465 assert_eq!(request.method(), Method::PUT);
467 }
468
469 #[rstest]
470 fn test_factory_patch() {
471 let factory = APIRequestFactory::new();
473
474 let request = factory.patch("/api/users/1/").build().unwrap();
476
477 assert_eq!(request.method(), Method::PATCH);
479 }
480
481 #[rstest]
482 fn test_factory_delete() {
483 let factory = APIRequestFactory::new();
485
486 let request = factory.delete("/api/users/1/").build().unwrap();
488
489 assert_eq!(request.method(), Method::DELETE);
491 }
492
493 #[rstest]
494 fn test_factory_head() {
495 let factory = APIRequestFactory::new();
497
498 let request = factory.head("/api/users/").build().unwrap();
500
501 assert_eq!(request.method(), Method::HEAD);
503 }
504
505 #[rstest]
506 fn test_factory_options() {
507 let factory = APIRequestFactory::new();
509
510 let request = factory.options("/api/users/").build().unwrap();
512
513 assert_eq!(request.method(), Method::OPTIONS);
515 }
516
517 #[rstest]
518 fn test_factory_request_custom() {
519 let factory = APIRequestFactory::new();
521
522 let request = factory
524 .request(Method::TRACE, "/api/trace/")
525 .build()
526 .unwrap();
527
528 assert_eq!(request.method(), Method::TRACE);
530 }
531
532 #[rstest]
537 fn test_builder_json() {
538 let factory = APIRequestFactory::new();
540 let data = json!({"name": "test"});
541
542 let request = factory
544 .post("/api/users/")
545 .json(&data)
546 .unwrap()
547 .build()
548 .unwrap();
549
550 assert_eq!(
552 request.headers().get("Content-Type").unwrap(),
553 "application/json"
554 );
555 assert_eq!(request.method(), Method::POST);
556 }
557
558 #[rstest]
559 fn test_builder_form() {
560 let factory = APIRequestFactory::new();
562 let data = json!({"name": "test", "age": 30});
563
564 let request = factory
566 .post("/api/users/")
567 .form(&data)
568 .unwrap()
569 .build()
570 .unwrap();
571
572 assert_eq!(
574 request.headers().get("Content-Type").unwrap(),
575 "application/x-www-form-urlencoded"
576 );
577 }
578
579 #[rstest]
580 fn test_builder_raw_body() {
581 let factory = APIRequestFactory::new();
583
584 let request = factory
586 .post("/api/upload/")
587 .body("raw data")
588 .build()
589 .unwrap();
590
591 assert_eq!(request.method(), Method::POST);
593 assert_eq!(
594 request.headers().get("Content-Type").unwrap(),
595 "application/json"
596 );
597 }
598
599 #[rstest]
600 fn test_builder_query_single() {
601 let factory = APIRequestFactory::new();
603
604 let request = factory
606 .get("/api/users/")
607 .query("page", "1")
608 .build()
609 .unwrap();
610
611 assert_eq!(request.uri().to_string(), "/api/users/?page=1");
613 }
614
615 #[rstest]
616 fn test_builder_query_multiple() {
617 let factory = APIRequestFactory::new();
619
620 let request = factory
622 .get("/api/users/")
623 .query("page", "1")
624 .query_param("limit", "10")
625 .build()
626 .unwrap();
627
628 let uri = request.uri().to_string();
630 assert!(uri.contains("page=1"));
631 assert!(uri.contains("limit=10"));
632 assert!(uri.contains('&'));
633 }
634
635 #[rstest]
636 fn test_builder_method_getter() {
637 let factory = APIRequestFactory::new();
639
640 let builder = factory.get("/test");
642
643 assert_eq!(builder.method(), Method::GET);
645 }
646
647 #[rstest]
648 fn test_builder_path_getter() {
649 let factory = APIRequestFactory::new();
651
652 let builder = factory.get("/api/items/");
654
655 assert_eq!(builder.path(), "/api/items/");
657 }
658
659 #[rstest]
660 fn test_builder_with_format() {
661 let factory = APIRequestFactory::new();
663
664 let request = factory
666 .post("/api/data/")
667 .with_format("form")
668 .body("key=val")
669 .build()
670 .unwrap();
671
672 assert_eq!(
674 request.headers().get("Content-Type").unwrap(),
675 "application/x-www-form-urlencoded"
676 );
677 }
678
679 #[rstest]
684 fn test_factory_with_header_invalid_name() {
685 let result = APIRequestFactory::new().with_header("invalid header!", "value");
687
688 assert!(result.is_err());
690 }
691
692 #[rstest]
693 fn test_builder_form_non_object() {
694 let factory = APIRequestFactory::new();
696 let data = json!([1, 2, 3]);
697
698 let result = factory.post("/api/users/").form(&data);
700
701 assert!(result.is_err());
703 }
704
705 #[rstest]
706 fn test_builder_header_invalid_name() {
707 let factory = APIRequestFactory::new();
709
710 let result = factory.get("/test").header("bad header!", "value");
712
713 assert!(result.is_err());
715 }
716
717 #[rstest]
722 fn test_builder_no_body_no_content_type() {
723 let factory = APIRequestFactory::new();
725
726 let request = factory.get("/api/users/").build().unwrap();
728
729 assert!(request.headers().get("Content-Type").is_none());
731 }
732
733 #[rstest]
734 fn test_builder_json_empty_object() {
735 let factory = APIRequestFactory::new();
737 let data = json!({});
738
739 let request = factory
741 .post("/api/data/")
742 .json(&data)
743 .unwrap()
744 .build()
745 .unwrap();
746
747 assert_eq!(
749 request.headers().get("Content-Type").unwrap(),
750 "application/json"
751 );
752 }
753
754 #[rstest]
755 fn test_builder_query_special_chars() {
756 let factory = APIRequestFactory::new();
758
759 let request = factory
761 .get("/api/search/")
762 .query("q", "hello world&foo=bar")
763 .build()
764 .unwrap();
765
766 let uri = request.uri().to_string();
768 assert!(uri.contains("hello+world"));
769 assert!(!uri.contains("hello world&foo=bar"));
770 }
771
772 #[rstest]
773 fn test_builder_unknown_format() {
774 let factory = APIRequestFactory::new().with_format("xml");
776
777 let request = factory.post("/api/data/").body("<xml/>").build().unwrap();
779
780 assert_eq!(
782 request.headers().get("Content-Type").unwrap(),
783 "application/octet-stream"
784 );
785 }
786}