tower_http_client/client/
request_ext.rs1use private::Sealed;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8#[error(transparent)]
9pub enum SetBodyError<S> {
10 Body(http::Error),
12 Encode(S),
14}
15
16pub trait RequestBuilderExt: Sized + Sealed {
18 #[must_use]
24 #[cfg(feature = "typed-header")]
25 #[cfg_attr(docsrs, doc(cfg(feature = "typed-header")))]
26 fn typed_header<T>(self, header: T) -> Self
27 where
28 T: headers::Header;
29
30 #[cfg(feature = "json")]
39 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
40 fn json<T: serde::Serialize + ?Sized>(
41 self,
42 value: &T,
43 ) -> Result<http::Request<bytes::Bytes>, SetBodyError<serde_json::Error>>;
44
45 #[cfg(feature = "form")]
54 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
55 fn form<T: serde::Serialize + ?Sized>(
56 self,
57 form: &T,
58 ) -> Result<http::Request<bytes::Bytes>, SetBodyError<serde_urlencoded::ser::Error>>;
59
60 #[cfg(feature = "query")]
80 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
81 fn query<T: serde::Serialize + ?Sized>(
82 self,
83 query: &T,
84 ) -> Result<Self, serde_urlencoded::ser::Error>;
85}
86
87impl RequestBuilderExt for http::request::Builder {
88 #[cfg(feature = "typed-header")]
89 #[cfg_attr(docsrs, doc(cfg(feature = "typed-header")))]
90 fn typed_header<T>(mut self, header: T) -> Self
91 where
92 T: headers::Header,
93 {
94 use headers::HeaderMapExt;
95
96 if let Some(headers) = self.headers_mut() {
97 headers.typed_insert(header);
98 }
99 self
100 }
101
102 #[cfg(feature = "json")]
103 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
104 fn json<T: serde::Serialize + ?Sized>(
105 mut self,
106 value: &T,
107 ) -> Result<http::Request<bytes::Bytes>, SetBodyError<serde_json::Error>> {
108 use http::{HeaderValue, header::CONTENT_TYPE};
109
110 if let Some(headers) = self.headers_mut() {
111 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
112 }
113
114 let bytes = bytes::Bytes::from(serde_json::to_vec(value).map_err(SetBodyError::Encode)?);
115 self.body(bytes).map_err(SetBodyError::Body)
116 }
117
118 #[cfg(feature = "form")]
119 #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
120 fn form<T: serde::Serialize + ?Sized>(
121 mut self,
122 form: &T,
123 ) -> Result<http::Request<bytes::Bytes>, SetBodyError<serde_urlencoded::ser::Error>> {
124 use http::{HeaderValue, header::CONTENT_TYPE};
125
126 let string = serde_urlencoded::to_string(form).map_err(SetBodyError::Encode)?;
127 if let Some(headers) = self.headers_mut() {
128 headers.insert(
129 CONTENT_TYPE,
130 HeaderValue::from_static("application/x-www-form-urlencoded"),
131 );
132 }
133
134 self.body(bytes::Bytes::from(string))
135 .map_err(SetBodyError::Body)
136 }
137
138 #[cfg(feature = "query")]
139 #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
140 fn query<T: serde::Serialize + ?Sized>(
141 self,
142 query: &T,
143 ) -> Result<Self, serde_urlencoded::ser::Error> {
144 use http::uri::PathAndQuery;
145
146 let mut parts = self.uri_ref().cloned().unwrap_or_default().into_parts();
147 let new_path_and_query = {
148 let path = parts
150 .path_and_query
151 .as_ref()
152 .map_or_else(|| "/", |pq| pq.path());
153
154 let query_string = serde_urlencoded::to_string(query)?;
155 let pq_str = if query_string.is_empty() {
156 path.to_owned()
157 } else {
158 [path, "?", &query_string].concat()
159 };
160 PathAndQuery::try_from(pq_str).expect("invalid path and query after encoding")
162 };
163
164 parts.path_and_query = Some(new_path_and_query);
165 let uri = http::Uri::from_parts(parts).expect("invalid URI parts after setting query");
167
168 Ok(self.uri(uri))
169 }
170}
171
172mod private {
173 pub trait Sealed {}
174
175 impl Sealed for http::request::Builder {}
176}
177
178#[cfg(all(test, feature = "query"))]
179mod query_tests {
180 use pretty_assertions::assert_eq;
181 use tower_http::BoxError;
182
183 use super::*;
184
185 #[test]
186 fn test_query_basic() -> Result<(), BoxError> {
187 let request = http::Request::builder()
188 .uri("http://example.com/path")
189 .query(&[("key", "value")])?
190 .body(())?;
191
192 assert_eq!(request.uri().query(), Some("key=value"));
193 Ok(())
194 }
195
196 #[test]
197 fn test_query_without_uri() -> Result<(), BoxError> {
198 let request = http::Request::builder()
199 .query(&[("key", "value")])?
200 .body(())?;
201
202 assert_eq!(request.uri().query(), Some("key=value"));
203 Ok(())
204 }
205
206 #[test]
207 fn test_query_overwrites_existing() -> Result<(), BoxError> {
208 let request = http::Request::builder()
209 .uri("http://example.com/path?existing=1")
210 .query(&[("key", "value")])?
211 .body(())?;
212
213 assert_eq!(request.uri().query(), Some("key=value"));
215 Ok(())
216 }
217
218 #[test]
219 fn test_query_last_call_wins() -> Result<(), BoxError> {
220 let request = http::Request::builder()
221 .uri("http://example.com/path")
222 .query(&[("first", "1")])?
223 .query(&[("second", "2")])?
224 .body(())?;
225
226 assert_eq!(request.uri().query(), Some("second=2"));
228 Ok(())
229 }
230
231 #[test]
232 fn test_query_duplicate_keys() -> Result<(), BoxError> {
233 let request = http::Request::builder()
234 .uri("http://example.com/path")
235 .query(&[("foo", "a"), ("foo", "b")])?
236 .body(())?;
237
238 assert_eq!(request.uri().query(), Some("foo=a&foo=b"));
239 Ok(())
240 }
241
242 #[test]
243 fn test_query_struct() -> Result<(), BoxError> {
244 #[derive(serde::Serialize)]
245 struct Params {
246 page: u32,
247 limit: u32,
248 }
249
250 let request = http::Request::builder()
251 .uri("http://example.com/path")
252 .query(&Params { page: 2, limit: 10 })?
253 .body(())?;
254
255 assert_eq!(request.uri().query(), Some("page=2&limit=10"));
256 Ok(())
257 }
258
259 #[test]
260 fn test_query_special_characters_are_encoded() -> Result<(), BoxError> {
261 let request = http::Request::builder()
262 .uri("http://example.com/path")
263 .query(&[("key", "hello world")])?
264 .body(())?;
265
266 assert_eq!(request.uri().query(), Some("key=hello+world"));
267 Ok(())
268 }
269
270 #[test]
271 fn test_query_encode_error() {
272 let _error: serde_urlencoded::ser::Error = http::Request::builder().query(&42).unwrap_err();
274 }
275
276 #[test]
277 fn test_query_empty_serialization_clears_query() -> Result<(), BoxError> {
278 #[derive(serde::Serialize)]
279 struct Empty {}
280
281 let request = http::Request::builder()
282 .uri("/hello?old=1")
283 .query(&Empty {})?
284 .body(())?;
285
286 assert_eq!(request.uri().path(), "/hello");
288 assert_eq!(request.uri().query(), None);
289 Ok(())
290 }
291}