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 = [path, "?", &query_string].concat();
156 PathAndQuery::try_from(pq_str).expect("invalid path and query after encoding")
158 };
159
160 parts.path_and_query = Some(new_path_and_query);
161 let uri = http::Uri::from_parts(parts).expect("invalid URI parts after setting query");
163
164 Ok(self.uri(uri))
165 }
166}
167
168mod private {
169 pub trait Sealed {}
170
171 impl Sealed for http::request::Builder {}
172}
173
174#[cfg(all(test, feature = "query"))]
175mod query_tests {
176 use pretty_assertions::assert_eq;
177 use tower_http::BoxError;
178
179 use super::*;
180
181 #[test]
182 fn test_query_basic() -> Result<(), BoxError> {
183 let request = http::Request::builder()
184 .uri("http://example.com/path")
185 .query(&[("key", "value")])?
186 .body(())?;
187
188 assert_eq!(request.uri().query(), Some("key=value"));
189 Ok(())
190 }
191
192 #[test]
193 fn test_query_without_uri() -> Result<(), BoxError> {
194 let request = http::Request::builder()
195 .query(&[("key", "value")])?
196 .body(())?;
197
198 assert_eq!(request.uri().query(), Some("key=value"));
199 Ok(())
200 }
201
202 #[test]
203 fn test_query_overwrites_existing() -> Result<(), BoxError> {
204 let request = http::Request::builder()
205 .uri("http://example.com/path?existing=1")
206 .query(&[("key", "value")])?
207 .body(())?;
208
209 assert_eq!(request.uri().query(), Some("key=value"));
211 Ok(())
212 }
213
214 #[test]
215 fn test_query_last_call_wins() -> Result<(), BoxError> {
216 let request = http::Request::builder()
217 .uri("http://example.com/path")
218 .query(&[("first", "1")])?
219 .query(&[("second", "2")])?
220 .body(())?;
221
222 assert_eq!(request.uri().query(), Some("second=2"));
224 Ok(())
225 }
226
227 #[test]
228 fn test_query_duplicate_keys() -> Result<(), BoxError> {
229 let request = http::Request::builder()
230 .uri("http://example.com/path")
231 .query(&[("foo", "a"), ("foo", "b")])?
232 .body(())?;
233
234 assert_eq!(request.uri().query(), Some("foo=a&foo=b"));
235 Ok(())
236 }
237
238 #[test]
239 fn test_query_struct() -> Result<(), BoxError> {
240 #[derive(serde::Serialize)]
241 struct Params {
242 page: u32,
243 limit: u32,
244 }
245
246 let request = http::Request::builder()
247 .uri("http://example.com/path")
248 .query(&Params { page: 2, limit: 10 })?
249 .body(())?;
250
251 assert_eq!(request.uri().query(), Some("page=2&limit=10"));
252 Ok(())
253 }
254
255 #[test]
256 fn test_query_special_characters_are_encoded() -> Result<(), BoxError> {
257 let request = http::Request::builder()
258 .uri("http://example.com/path")
259 .query(&[("key", "hello world")])?
260 .body(())?;
261
262 assert_eq!(request.uri().query(), Some("key=hello+world"));
263 Ok(())
264 }
265
266 #[test]
267 fn test_query_encode_error() {
268 let _error: serde_urlencoded::ser::Error = http::Request::builder().query(&42).unwrap_err();
270 }
271}