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 user: Option<Value>,
194}
195
196impl RequestBuilder {
197 pub fn new(method: Method, path: &str, default_headers: &HeaderMap) -> Self {
199 Self {
200 method,
201 path: path.to_string(),
202 headers: default_headers.clone(),
203 query_params: HashMap::new(),
204 body: None,
205 format: "json".to_string(),
206 user: None,
207 }
208 }
209 pub fn method(&self) -> Method {
211 self.method.clone()
212 }
213 pub fn path(&self) -> &str {
215 &self.path
216 }
217 pub fn with_format(mut self, format: &str) -> Self {
219 self.format = format.to_string();
220 self
221 }
222 pub fn header(mut self, name: &str, value: &str) -> Result<Self, ClientError> {
225 let header_name: http::header::HeaderName = name
226 .parse()
227 .map_err(|_| ClientError::RequestFailed(format!("Invalid header name: {}", name)))?;
228 self.headers
229 .insert(header_name, HeaderValue::from_str(value)?);
230 Ok(self)
231 }
232 pub fn query(mut self, key: &str, value: &str) -> Self {
234 self.query_params.insert(key.to_string(), value.to_string());
235 self
236 }
237 pub fn query_param(self, key: &str, value: &str) -> Self {
239 self.query(key, value)
240 }
241 pub fn json<T: Serialize>(mut self, data: &T) -> Result<Self, ClientError> {
254 let json = serde_json::to_vec(data)?;
255 self.body = Some(Bytes::from(json));
256 self.format = "json".to_string();
257 Ok(self)
258 }
259 pub fn form<T: Serialize>(mut self, data: &T) -> Result<Self, ClientError> {
272 let json_value = serde_json::to_value(data)?;
273 if let Value::Object(map) = json_value {
274 let form_data = map
275 .iter()
276 .map(|(k, v)| {
277 let value_str = match v {
278 Value::String(s) => s.clone(),
279 _ => v.to_string(),
280 };
281 format!(
282 "{}={}",
283 url::form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
284 url::form_urlencoded::byte_serialize(value_str.as_bytes())
285 .collect::<String>()
286 )
287 })
288 .collect::<Vec<_>>()
289 .join("&");
290 self.body = Some(Bytes::from(form_data));
291 self.format = "form".to_string();
292 Ok(self)
293 } else {
294 Err(ClientError::RequestFailed(
295 "Expected object for form data".to_string(),
296 ))
297 }
298 }
299 pub fn body(mut self, body: impl Into<Bytes>) -> Self {
310 self.body = Some(body.into());
311 self
312 }
313 #[deprecated(
326 since = "0.1.0-rc.16",
327 note = "use `client.auth().session()` or `client.auth().jwt()` instead"
328 )]
329 pub fn force_authenticate(mut self, user: Value) -> Self {
330 self.user = Some(user);
331 self
332 }
333 pub fn build(self) -> Result<Request<Full<Bytes>>, ClientError> {
345 let mut url = self.path.clone();
346
347 if !self.query_params.is_empty() {
349 let query_string = self
350 .query_params
351 .iter()
352 .map(|(k, v)| {
353 format!(
354 "{}={}",
355 url::form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
356 url::form_urlencoded::byte_serialize(v.as_bytes()).collect::<String>()
357 )
358 })
359 .collect::<Vec<_>>()
360 .join("&");
361 url = format!("{}?{}", url, query_string);
362 }
363
364 let mut request = Request::builder().method(self.method).uri(url);
365
366 for (name, value) in self.headers.iter() {
368 request = request.header(name, value);
369 }
370
371 if self.body.is_some() {
373 let content_type = match self.format.as_str() {
374 "json" => "application/json",
375 "form" => "application/x-www-form-urlencoded",
376 _ => "application/octet-stream",
377 };
378 request = request.header("Content-Type", content_type);
379 }
380
381 if self.user.is_some() {
383 request = request.header("X-Test-User", "authenticated");
384 }
385
386 let body = self.body.unwrap_or_default();
388 let req = request.body(Full::new(body))?;
389
390 Ok(req)
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use rstest::rstest;
398 use serde_json::json;
399
400 #[rstest]
405 fn test_factory_new() {
406 let factory = APIRequestFactory::new();
408
409 let request = factory.get("/api/users/").build().unwrap();
411
412 assert_eq!(request.method(), Method::GET);
414 }
415
416 #[rstest]
417 fn test_factory_default() {
418 let factory_new = APIRequestFactory::new();
420 let factory_default = APIRequestFactory::default();
421
422 let req_new = factory_new.get("/test").build().unwrap();
424 let req_default = factory_default.get("/test").build().unwrap();
425
426 assert_eq!(req_new.method(), req_default.method());
428 assert_eq!(req_new.uri(), req_default.uri());
429 }
430
431 #[rstest]
432 fn test_factory_with_format() {
433 let factory = APIRequestFactory::new().with_format("xml");
435
436 let request = factory.post("/api/data/").body("payload").build().unwrap();
438
439 assert_eq!(
441 request.headers().get("Content-Type").unwrap(),
442 "application/octet-stream"
443 );
444 }
445
446 #[rstest]
447 fn test_factory_with_header() {
448 let factory = APIRequestFactory::new()
450 .with_header("X-Custom", "value123")
451 .unwrap();
452
453 let request = factory.get("/api/items/").build().unwrap();
455
456 assert_eq!(request.headers().get("x-custom").unwrap(), "value123");
458 }
459
460 #[rstest]
461 fn test_factory_get() {
462 let factory = APIRequestFactory::new();
464
465 let request = factory.get("/api/users/").build().unwrap();
467
468 assert_eq!(request.method(), Method::GET);
470 }
471
472 #[rstest]
473 fn test_factory_post() {
474 let factory = APIRequestFactory::new();
476
477 let request = factory.post("/api/users/").build().unwrap();
479
480 assert_eq!(request.method(), Method::POST);
482 }
483
484 #[rstest]
485 fn test_factory_put() {
486 let factory = APIRequestFactory::new();
488
489 let request = factory.put("/api/users/1/").build().unwrap();
491
492 assert_eq!(request.method(), Method::PUT);
494 }
495
496 #[rstest]
497 fn test_factory_patch() {
498 let factory = APIRequestFactory::new();
500
501 let request = factory.patch("/api/users/1/").build().unwrap();
503
504 assert_eq!(request.method(), Method::PATCH);
506 }
507
508 #[rstest]
509 fn test_factory_delete() {
510 let factory = APIRequestFactory::new();
512
513 let request = factory.delete("/api/users/1/").build().unwrap();
515
516 assert_eq!(request.method(), Method::DELETE);
518 }
519
520 #[rstest]
521 fn test_factory_head() {
522 let factory = APIRequestFactory::new();
524
525 let request = factory.head("/api/users/").build().unwrap();
527
528 assert_eq!(request.method(), Method::HEAD);
530 }
531
532 #[rstest]
533 fn test_factory_options() {
534 let factory = APIRequestFactory::new();
536
537 let request = factory.options("/api/users/").build().unwrap();
539
540 assert_eq!(request.method(), Method::OPTIONS);
542 }
543
544 #[rstest]
545 fn test_factory_request_custom() {
546 let factory = APIRequestFactory::new();
548
549 let request = factory
551 .request(Method::TRACE, "/api/trace/")
552 .build()
553 .unwrap();
554
555 assert_eq!(request.method(), Method::TRACE);
557 }
558
559 #[rstest]
564 fn test_builder_json() {
565 let factory = APIRequestFactory::new();
567 let data = json!({"name": "test"});
568
569 let request = factory
571 .post("/api/users/")
572 .json(&data)
573 .unwrap()
574 .build()
575 .unwrap();
576
577 assert_eq!(
579 request.headers().get("Content-Type").unwrap(),
580 "application/json"
581 );
582 assert_eq!(request.method(), Method::POST);
583 }
584
585 #[rstest]
586 fn test_builder_form() {
587 let factory = APIRequestFactory::new();
589 let data = json!({"name": "test", "age": 30});
590
591 let request = factory
593 .post("/api/users/")
594 .form(&data)
595 .unwrap()
596 .build()
597 .unwrap();
598
599 assert_eq!(
601 request.headers().get("Content-Type").unwrap(),
602 "application/x-www-form-urlencoded"
603 );
604 }
605
606 #[rstest]
607 fn test_builder_raw_body() {
608 let factory = APIRequestFactory::new();
610
611 let request = factory
613 .post("/api/upload/")
614 .body("raw data")
615 .build()
616 .unwrap();
617
618 assert_eq!(request.method(), Method::POST);
620 assert_eq!(
621 request.headers().get("Content-Type").unwrap(),
622 "application/json"
623 );
624 }
625
626 #[rstest]
627 fn test_builder_query_single() {
628 let factory = APIRequestFactory::new();
630
631 let request = factory
633 .get("/api/users/")
634 .query("page", "1")
635 .build()
636 .unwrap();
637
638 assert_eq!(request.uri().to_string(), "/api/users/?page=1");
640 }
641
642 #[rstest]
643 fn test_builder_query_multiple() {
644 let factory = APIRequestFactory::new();
646
647 let request = factory
649 .get("/api/users/")
650 .query("page", "1")
651 .query_param("limit", "10")
652 .build()
653 .unwrap();
654
655 let uri = request.uri().to_string();
657 assert!(uri.contains("page=1"));
658 assert!(uri.contains("limit=10"));
659 assert!(uri.contains('&'));
660 }
661
662 #[rstest]
663 #[allow(deprecated)]
664 fn test_builder_force_authenticate() {
665 let factory = APIRequestFactory::new();
667 let user = json!({"id": 1, "username": "testuser"});
668
669 let request = factory
671 .get("/api/profile/")
672 .force_authenticate(user)
673 .build()
674 .unwrap();
675
676 assert_eq!(
678 request.headers().get("X-Test-User").unwrap(),
679 "authenticated"
680 );
681 }
682
683 #[rstest]
684 fn test_builder_method_getter() {
685 let factory = APIRequestFactory::new();
687
688 let builder = factory.get("/test");
690
691 assert_eq!(builder.method(), Method::GET);
693 }
694
695 #[rstest]
696 fn test_builder_path_getter() {
697 let factory = APIRequestFactory::new();
699
700 let builder = factory.get("/api/items/");
702
703 assert_eq!(builder.path(), "/api/items/");
705 }
706
707 #[rstest]
708 fn test_builder_with_format() {
709 let factory = APIRequestFactory::new();
711
712 let request = factory
714 .post("/api/data/")
715 .with_format("form")
716 .body("key=val")
717 .build()
718 .unwrap();
719
720 assert_eq!(
722 request.headers().get("Content-Type").unwrap(),
723 "application/x-www-form-urlencoded"
724 );
725 }
726
727 #[rstest]
732 fn test_factory_with_header_invalid_name() {
733 let result = APIRequestFactory::new().with_header("invalid header!", "value");
735
736 assert!(result.is_err());
738 }
739
740 #[rstest]
741 fn test_builder_form_non_object() {
742 let factory = APIRequestFactory::new();
744 let data = json!([1, 2, 3]);
745
746 let result = factory.post("/api/users/").form(&data);
748
749 assert!(result.is_err());
751 }
752
753 #[rstest]
754 fn test_builder_header_invalid_name() {
755 let factory = APIRequestFactory::new();
757
758 let result = factory.get("/test").header("bad header!", "value");
760
761 assert!(result.is_err());
763 }
764
765 #[rstest]
770 fn test_builder_no_body_no_content_type() {
771 let factory = APIRequestFactory::new();
773
774 let request = factory.get("/api/users/").build().unwrap();
776
777 assert!(request.headers().get("Content-Type").is_none());
779 }
780
781 #[rstest]
782 fn test_builder_json_empty_object() {
783 let factory = APIRequestFactory::new();
785 let data = json!({});
786
787 let request = factory
789 .post("/api/data/")
790 .json(&data)
791 .unwrap()
792 .build()
793 .unwrap();
794
795 assert_eq!(
797 request.headers().get("Content-Type").unwrap(),
798 "application/json"
799 );
800 }
801
802 #[rstest]
803 fn test_builder_query_special_chars() {
804 let factory = APIRequestFactory::new();
806
807 let request = factory
809 .get("/api/search/")
810 .query("q", "hello world&foo=bar")
811 .build()
812 .unwrap();
813
814 let uri = request.uri().to_string();
816 assert!(uri.contains("hello+world"));
817 assert!(!uri.contains("hello world&foo=bar"));
818 }
819
820 #[rstest]
821 fn test_builder_unknown_format() {
822 let factory = APIRequestFactory::new().with_format("xml");
824
825 let request = factory.post("/api/data/").body("<xml/>").build().unwrap();
827
828 assert_eq!(
830 request.headers().get("Content-Type").unwrap(),
831 "application/octet-stream"
832 );
833 }
834}