reqwest_builder/
trait_impl.rs

1use crate::{
2    errors::ReqwestBuilderError,
3    serialization::{construct_url, serialize_to_form_params, serialize_to_header_map},
4    types::{QueryParams, RequestBody},
5};
6use serde::Serialize;
7use url::Url;
8
9/// Trait for converting request structures into reqwest builders
10///
11/// This trait provides a standardized way to convert request types into
12/// `reqwest_middleware::RequestBuilder` instances with proper configuration.
13pub trait IntoReqwestBuilder
14where
15    Self: Sized + Serialize,
16{
17    /// Associated type for request headers
18    type Headers: Serialize + Clone;
19
20    /// HTTP method for the request
21    fn method(&self) -> http::Method;
22
23    /// Endpoint path for the request
24    fn endpoint(&self) -> String;
25
26    /// Optional headers for the request
27    fn headers(&self) -> Option<Self::Headers> {
28        None
29    }
30
31    /// Request body type
32    fn body(&self) -> RequestBody {
33        RequestBody::Json
34    }
35
36    /// Optional query parameters
37    fn query_params(&self) -> Option<QueryParams> {
38        None
39    }
40
41    /// Create multipart form - override this for file uploads
42    fn create_multipart_form(&self) -> Option<reqwest::multipart::Form> {
43        None
44    }
45
46    /// Convert the request into a reqwest builder with proper error handling
47    ///
48    /// This is the preferred method for new code as it provides proper error handling.
49    fn into_reqwest_builder(
50        self,
51        client: &reqwest_middleware::ClientWithMiddleware,
52        base_url: &Url,
53    ) -> std::result::Result<reqwest_middleware::RequestBuilder, ReqwestBuilderError> {
54        // Construct URL with error handling
55        let url = construct_url(base_url, &self.endpoint());
56        let mut builder = client.request(self.method(), &url);
57
58        // Add query parameters if present
59        if let Some(params) = self.query_params() {
60            builder = builder.query(&params);
61        }
62
63        // Handle request body with error handling
64        builder = self.add_body_to_builder(builder)?;
65
66        // Add headers with error handling
67        if let Some(headers) = self.headers() {
68            let header_map = serialize_to_header_map(&headers)?;
69            builder = builder.headers(header_map);
70        }
71
72        Ok(builder)
73    }
74
75    /// Add body to the request builder with proper error handling
76    fn add_body_to_builder(
77        &self,
78        mut builder: reqwest_middleware::RequestBuilder,
79    ) -> std::result::Result<reqwest_middleware::RequestBuilder, ReqwestBuilderError> {
80        match self.body() {
81            RequestBody::Json => {
82                let json_str = serde_json::to_string(self).map_err(ReqwestBuilderError::from)?;
83                if json_str != "{}" {
84                    builder = builder.json(self);
85                }
86            }
87            RequestBody::Form => {
88                let params = serialize_to_form_params(self)?;
89                builder = builder.form(&params);
90            }
91            RequestBody::Multipart => {
92                if let Some(form) = self.create_multipart_form() {
93                    builder = builder.multipart(form);
94                }
95            }
96            RequestBody::None => {
97                // No body to add
98            }
99        }
100        Ok(builder)
101    }
102}
103
104// Helper function for the derive macro to handle query parameters
105// This works with both Option and non-Option types
106pub fn query_param_helper<T>(
107    value: &T,
108    param_name: &str,
109    params: &mut std::collections::HashMap<String, String>,
110) where
111    T: QueryParamValue,
112{
113    value.add_to_params(param_name, params);
114}
115
116// Trait to handle different types of query parameter values
117pub trait QueryParamValue {
118    fn add_to_params(
119        &self,
120        param_name: &str,
121        params: &mut std::collections::HashMap<String, String>,
122    );
123}
124
125// Implementation for Option types
126impl<T: std::fmt::Display> QueryParamValue for Option<T> {
127    fn add_to_params(
128        &self,
129        param_name: &str,
130        params: &mut std::collections::HashMap<String, String>,
131    ) {
132        if let Some(value) = self {
133            params.insert(param_name.to_string(), value.to_string());
134        }
135    }
136}
137
138// Implementations for common non-Option types
139/// TODO: We should use a better aproach to handle these types
140impl QueryParamValue for String {
141    fn add_to_params(
142        &self,
143        param_name: &str,
144        params: &mut std::collections::HashMap<String, String>,
145    ) {
146        params.insert(param_name.to_string(), self.clone());
147    }
148}
149
150impl QueryParamValue for &str {
151    fn add_to_params(
152        &self,
153        param_name: &str,
154        params: &mut std::collections::HashMap<String, String>,
155    ) {
156        params.insert(param_name.to_string(), self.to_string());
157    }
158}
159
160impl QueryParamValue for u32 {
161    fn add_to_params(
162        &self,
163        param_name: &str,
164        params: &mut std::collections::HashMap<String, String>,
165    ) {
166        params.insert(param_name.to_string(), self.to_string());
167    }
168}
169
170impl QueryParamValue for u64 {
171    fn add_to_params(
172        &self,
173        param_name: &str,
174        params: &mut std::collections::HashMap<String, String>,
175    ) {
176        params.insert(param_name.to_string(), self.to_string());
177    }
178}
179
180impl QueryParamValue for i32 {
181    fn add_to_params(
182        &self,
183        param_name: &str,
184        params: &mut std::collections::HashMap<String, String>,
185    ) {
186        params.insert(param_name.to_string(), self.to_string());
187    }
188}
189
190impl QueryParamValue for i64 {
191    fn add_to_params(
192        &self,
193        param_name: &str,
194        params: &mut std::collections::HashMap<String, String>,
195    ) {
196        params.insert(param_name.to_string(), self.to_string());
197    }
198}
199
200impl QueryParamValue for bool {
201    fn add_to_params(
202        &self,
203        param_name: &str,
204        params: &mut std::collections::HashMap<String, String>,
205    ) {
206        params.insert(param_name.to_string(), self.to_string());
207    }
208}