reqwest_builder/
trait_impl.rs

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