ureq/request.rs
1use std::convert::TryFrom;
2use std::fmt;
3use std::marker::PhantomData;
4
5use http::{Extensions, HeaderMap, HeaderName, HeaderValue};
6use http::{Method, Request, Response, Uri, Version};
7
8use crate::body::Body;
9use crate::config::typestate::RequestScope;
10use crate::config::{Config, ConfigBuilder, RequestLevelConfig};
11use crate::http;
12use crate::query::form_url_enc;
13use crate::query::{parse_query_params, QueryParam};
14use crate::send_body::AsSendBody;
15use crate::util::private::Private;
16use crate::util::HeaderMapExt;
17use crate::util::UriExt;
18use crate::{Agent, Error, SendBody};
19
20/// Transparent wrapper around [`http::request::Builder`].
21///
22/// The purpose is to provide the [`.call()`][RequestBuilder::call] and [`.send()`][RequestBuilder::send]
23/// and additional helpers for query parameters like [`.query()`][RequestBuilder::query] functions to
24/// make an API for sending requests.
25pub struct RequestBuilder<B> {
26 agent: Agent,
27 builder: http::request::Builder,
28 query_extra: Vec<QueryParam<'static>>,
29
30 // This is only used in case http::request::Builder contains an error
31 // (such as URL parsing error), and the user wants a `.config()`.
32 dummy_config: Option<Box<Config>>,
33
34 _ph: PhantomData<B>,
35}
36
37/// Typestate when [`RequestBuilder`] has no send body.
38///
39/// `RequestBuilder<WithoutBody>`
40///
41/// Methods: GET, DELETE, HEAD, OPTIONS, CONNECT, TRACE
42#[derive(Debug)]
43pub struct WithoutBody(());
44impl Private for WithoutBody {}
45
46/// Typestate when [`RequestBuilder`] needs to a send body.
47///
48/// `RequestBuilder<WithBody>`
49///
50/// Methods: POST, PUT, PATCH
51#[derive(Debug)]
52pub struct WithBody(());
53impl Private for WithBody {}
54
55impl<Any> RequestBuilder<Any> {
56 /// Get the HTTP Method for this request.
57 ///
58 /// By default this is `GET`. If builder has error, returns None.
59 ///
60 /// # Examples
61 ///
62 /// ```
63 /// use ureq::http::Method;
64 ///
65 /// let req = ureq::get("http://httpbin.org/get");
66 /// assert_eq!(req.method_ref(),Some(&Method::GET));
67 ///
68 /// let req = ureq::post("http://httpbin.org/post");
69 /// assert_eq!(req.method_ref(),Some(&Method::POST));
70 /// ```
71 pub fn method_ref(&self) -> Option<&Method> {
72 self.builder.method_ref()
73 }
74
75 /// Appends a header to this request builder.
76 ///
77 /// This function will append the provided key/value as a header to the
78 /// set of headers. It does not replace headers.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// let req = ureq::get("https://httpbin.org/get")
84 /// .header("X-Custom-Foo", "bar");
85 /// ```
86 pub fn header<K, V>(mut self, key: K, value: V) -> Self
87 where
88 HeaderName: TryFrom<K>,
89 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
90 HeaderValue: TryFrom<V>,
91 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
92 {
93 self.builder = self.builder.header(key, value);
94 self
95 }
96
97 /// Get header on this request builder.
98 ///
99 /// When builder has error returns `None`.
100 ///
101 /// # Example
102 ///
103 /// ```
104 /// let req = ureq::get("http://httpbin.org/get")
105 /// .header("Accept", "text/html")
106 /// .header("X-Custom-Foo", "bar");
107 /// let headers = req.headers_ref().unwrap();
108 /// assert_eq!( headers["Accept"], "text/html" );
109 /// assert_eq!( headers["X-Custom-Foo"], "bar" );
110 /// ```
111 pub fn headers_ref(&self) -> Option<&HeaderMap<HeaderValue>> {
112 self.builder.headers_ref()
113 }
114
115 /// Get headers on this request builder.
116 ///
117 /// When builder has error returns `None`.
118 ///
119 /// # Example
120 ///
121 /// ```
122 /// # use ureq::http::header::HeaderValue;
123 /// let mut req = ureq::get("http://httpbin.org/get");
124 /// {
125 /// let headers = req.headers_mut().unwrap();
126 /// headers.insert("Accept", HeaderValue::from_static("text/html"));
127 /// headers.insert("X-Custom-Foo", HeaderValue::from_static("bar"));
128 /// }
129 /// let headers = req.headers_ref().unwrap();
130 /// assert_eq!( headers["Accept"], "text/html" );
131 /// assert_eq!( headers["X-Custom-Foo"], "bar" );
132 /// ```
133 pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
134 self.builder.headers_mut()
135 }
136
137 /// Add a query parameter to the URL.
138 ///
139 /// Always appends a new parameter, also when using the name of
140 /// an already existing one. Both key and value are percent-encoded
141 /// according to the URL specification.
142 ///
143 /// # Examples
144 ///
145 /// ```
146 /// // Creates a URL with an encoded query parameter:
147 /// // https://httpbin.org/get?my_query=with%20value
148 /// let req = ureq::get("https://httpbin.org/get")
149 /// .query("my_query", "with value");
150 /// ```
151 pub fn query<K, V>(mut self, key: K, value: V) -> Self
152 where
153 K: AsRef<str>,
154 V: AsRef<str>,
155 {
156 self.query_extra
157 .push(QueryParam::new_key_value(key.as_ref(), value.as_ref()));
158 self
159 }
160
161 /// Add a query parameter to the URL without percent-encoding.
162 ///
163 /// Always appends a new parameter, also when using the name of
164 /// an already existing one. Neither key nor value are percent-encoded,
165 /// which allows you to use pre-encoded values or bypass encoding.
166 ///
167 /// **Important note**: When using this method, you must ensure that your
168 /// query parameters don't contain characters that would make the URI invalid,
169 /// such as spaces or control characters. You are responsible for any pre-encoding
170 /// needed for URI validity. If you're unsure, use the regular `query()` method instead.
171 ///
172 /// # Examples
173 ///
174 /// ```
175 /// // Creates a URL with a raw query parameter:
176 /// // https://httpbin.org/get?my_query=pre-encoded%20value
177 /// let req = ureq::get("https://httpbin.org/get")
178 /// .query_raw("my_query", "pre-encoded%20value");
179 /// ```
180 pub fn query_raw<K, V>(mut self, key: K, value: V) -> Self
181 where
182 K: AsRef<str>,
183 V: AsRef<str>,
184 {
185 self.query_extra
186 .push(QueryParam::new_key_value_raw(key.as_ref(), value.as_ref()));
187 self
188 }
189
190 /// Set multi query parameters.
191 ///
192 /// Both keys and values are percent-encoded according to the URL specification.
193 ///
194 /// For example, to set `?format=json&dest=%2Flogin`
195 ///
196 /// ```
197 /// let query = vec![
198 /// ("format", "json"),
199 /// ("dest", "/login"),
200 /// ];
201 ///
202 /// let response = ureq::get("http://httpbin.org/get")
203 /// .query_pairs(query)
204 /// .call()?;
205 /// # Ok::<_, ureq::Error>(())
206 /// ```
207 pub fn query_pairs<I, K, V>(mut self, iter: I) -> Self
208 where
209 I: IntoIterator<Item = (K, V)>,
210 K: AsRef<str>,
211 V: AsRef<str>,
212 {
213 self.query_extra.extend(
214 iter.into_iter()
215 .map(|(k, v)| QueryParam::new_key_value(k.as_ref(), v.as_ref())),
216 );
217 self
218 }
219 /// Set multi query parameters without percent-encoding.
220 ///
221 /// Neither keys nor values are percent-encoded, which allows you to use
222 /// pre-encoded values or bypass encoding.
223 ///
224 /// **Important note**: When using this method, you must ensure that your
225 /// query parameters don't contain characters that would make the URI invalid,
226 /// such as spaces or control characters. You are responsible for any pre-encoding
227 /// needed for URI validity. If you're unsure, use the regular `query_pairs()` method instead.
228 ///
229 /// For example, to set `?format=json&dest=/login` without encoding:
230 ///
231 /// ```
232 /// let query = vec![
233 /// ("format", "json"),
234 /// ("dest", "/login"),
235 /// ];
236 ///
237 /// let response = ureq::get("http://httpbin.org/get")
238 /// .query_pairs_raw(query)
239 /// .call()?;
240 /// # Ok::<_, ureq::Error>(())
241 /// ```
242 pub fn query_pairs_raw<I, K, V>(mut self, iter: I) -> Self
243 where
244 I: IntoIterator<Item = (K, V)>,
245 K: AsRef<str>,
246 V: AsRef<str>,
247 {
248 self.query_extra.extend(
249 iter.into_iter()
250 .map(|(k, v)| QueryParam::new_key_value_raw(k.as_ref(), v.as_ref())),
251 );
252 self
253 }
254 /// Overrides the URI for this request.
255 ///
256 /// Typically this is set via `ureq::get(<uri>)` or `Agent::get(<uri>)`. This
257 /// lets us change it.
258 ///
259 /// # Examples
260 ///
261 /// ```
262 /// let req = ureq::get("https://www.google.com/")
263 /// .uri("https://httpbin.org/get");
264 /// ```
265 pub fn uri<T>(mut self, uri: T) -> Self
266 where
267 Uri: TryFrom<T>,
268 <Uri as TryFrom<T>>::Error: Into<http::Error>,
269 {
270 self.builder = self.builder.uri(uri);
271 self
272 }
273
274 /// Get the URI for this request
275 ///
276 /// By default this is `/`.
277 ///
278 /// # Examples
279 ///
280 /// ```
281 /// let req = ureq::get("http://httpbin.org/get");
282 /// assert_eq!(req.uri_ref().unwrap(), "http://httpbin.org/get");
283 /// ```
284 pub fn uri_ref(&self) -> Option<&Uri> {
285 self.builder.uri_ref()
286 }
287
288 /// Set the HTTP version for this request.
289 ///
290 /// By default this is HTTP/1.1.
291 /// ureq only handles HTTP/1.1 and HTTP/1.0.
292 ///
293 /// # Examples
294 ///
295 /// ```
296 /// use ureq::http::Version;
297 ///
298 /// let req = ureq::get("https://www.google.com/")
299 /// .version(Version::HTTP_10);
300 /// ```
301 pub fn version(mut self, version: Version) -> Self {
302 self.builder = self.builder.version(version);
303 self
304 }
305
306 /// Get the HTTP version for this request
307 ///
308 /// By default this is HTTP/1.1.
309 ///
310 /// # Examples
311 ///
312 /// ```
313 /// use ureq::http::Version;
314 ///
315 /// let req = ureq::get("http://httpbin.org/get");
316 /// assert_eq!(req.version_ref().unwrap(), &Version::HTTP_11);
317 /// ```
318 pub fn version_ref(&self) -> Option<&Version> {
319 self.builder.version_ref()
320 }
321
322 /// Override agent level config on the request level.
323 ///
324 /// The agent config is copied and modified on request level.
325 ///
326 /// # Example
327 ///
328 /// ```
329 /// use ureq::Agent;
330 ///
331 /// let agent: Agent = Agent::config_builder()
332 /// .https_only(false)
333 /// .build()
334 /// .into();
335 ///
336 /// let request = agent.get("http://httpbin.org/get")
337 /// .config()
338 /// // override agent default for this request
339 /// .https_only(true)
340 /// .build();
341 ///
342 /// // Make the request
343 /// let result = request.call();
344 ///
345 /// // The https_only was set on request level
346 /// assert!(matches!(result.unwrap_err(), ureq::Error::RequireHttpsOnly(_)));
347 /// # Ok::<_, ureq::Error>(())
348 /// ```
349 pub fn config(self) -> ConfigBuilder<RequestScope<Any>> {
350 ConfigBuilder(RequestScope(self))
351 }
352
353 /// Adds an extension to this builder
354 ///
355 /// # Examples
356 ///
357 /// ```
358 /// let req = ureq::get("http://httpbin.org/get")
359 /// .extension("My Extension");
360 ///
361 /// assert_eq!(req.extensions_ref().unwrap().get::<&'static str>(),
362 /// Some(&"My Extension"));
363 /// ```
364 pub fn extension<T>(mut self, extension: T) -> Self
365 where
366 T: Clone + std::any::Any + Send + Sync + 'static,
367 {
368 self.builder = self.builder.extension(extension);
369 self
370 }
371
372 /// Get a reference to the extensions for this request builder.
373 ///
374 /// If the builder has an error, this returns `None`.
375 ///
376 /// # Example
377 ///
378 /// ```
379 /// let req = ureq::get("http://httpbin.org/get")
380 /// .extension("My Extension").extension(5u32);
381 /// let extensions = req.extensions_ref().unwrap();
382 /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension"));
383 /// assert_eq!(extensions.get::<u32>(), Some(&5u32));
384 /// ```
385 pub fn extensions_ref(&self) -> Option<&Extensions> {
386 self.builder.extensions_ref()
387 }
388
389 /// Get a mutable reference to the extensions for this request builder.
390 ///
391 /// If the builder has an error, this returns `None`.
392 ///
393 /// # Example
394 ///
395 /// ```
396 /// let mut req = ureq::get("http://httpbin.org/get");
397 /// let mut extensions = req.extensions_mut().unwrap();
398 /// extensions.insert(5u32);
399 /// assert_eq!(extensions.get::<u32>(), Some(&5u32));
400 /// ```
401 pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
402 self.builder.extensions_mut()
403 }
404
405 pub(crate) fn request_level_config(&mut self) -> &mut Config {
406 let Some(exts) = self.builder.extensions_mut() else {
407 // This means self.builder has an error such as URL parsing error.
408 // The error will surface on .call() (or .send()) and we fill in
409 // a dummy Config meanwhile.
410 return self
411 .dummy_config
412 .get_or_insert_with(|| Box::new(Config::default()));
413 };
414
415 if exts.get::<RequestLevelConfig>().is_none() {
416 exts.insert(self.agent.new_request_level_config());
417 }
418
419 // Unwrap is OK because of above check
420 let req_level: &mut RequestLevelConfig = exts.get_mut().unwrap();
421
422 &mut req_level.0
423 }
424}
425
426impl RequestBuilder<WithoutBody> {
427 pub(crate) fn new<T>(agent: Agent, method: Method, uri: T) -> Self
428 where
429 Uri: TryFrom<T>,
430 <Uri as TryFrom<T>>::Error: Into<http::Error>,
431 {
432 Self {
433 agent,
434 builder: Request::builder().method(method).uri(uri),
435 query_extra: vec![],
436 dummy_config: None,
437 _ph: PhantomData,
438 }
439 }
440
441 /// Sends the request and blocks the caller until we receive a response.
442 ///
443 /// It sends neither `Content-Length` nor `Transfer-Encoding`.
444 ///
445 /// ```
446 /// let res = ureq::get("http://httpbin.org/get")
447 /// .call()?;
448 /// # Ok::<_, ureq::Error>(())
449 /// ```
450 pub fn call(self) -> Result<Response<Body>, Error> {
451 let request = self.builder.body(())?;
452 do_call(self.agent, request, self.query_extra, SendBody::none())
453 }
454
455 /// Force sending a body.
456 ///
457 /// This is an escape hatch to interact with broken services.
458 ///
459 /// According to the spec, methods such as GET, DELETE and TRACE should
460 /// not have a body. Despite that there are broken API services and
461 /// servers that use it.
462 ///
463 /// Example using DELETE while sending a body.
464 ///
465 /// ```
466 /// let res = ureq::delete("http://httpbin.org/delete")
467 /// // this "unlocks" send() below
468 /// .force_send_body()
469 /// .send("DELETE with body is not correct")?;
470 /// # Ok::<_, ureq::Error>(())
471 /// ```
472 pub fn force_send_body(mut self) -> RequestBuilder<WithBody> {
473 if let Some(exts) = self.extensions_mut() {
474 exts.insert(ForceSendBody);
475 }
476
477 RequestBuilder {
478 agent: self.agent,
479 builder: self.builder,
480 query_extra: self.query_extra,
481 dummy_config: None,
482 _ph: PhantomData,
483 }
484 }
485}
486
487#[derive(Debug, Clone)]
488pub(crate) struct ForceSendBody;
489
490impl RequestBuilder<WithBody> {
491 pub(crate) fn new<T>(agent: Agent, method: Method, uri: T) -> Self
492 where
493 Uri: TryFrom<T>,
494 <Uri as TryFrom<T>>::Error: Into<http::Error>,
495 {
496 Self {
497 agent,
498 builder: Request::builder().method(method).uri(uri),
499 query_extra: vec![],
500 dummy_config: None,
501 _ph: PhantomData,
502 }
503 }
504
505 /// Set the content-type header.
506 ///
507 /// ```
508 /// let res = ureq::post("http://httpbin.org/post")
509 /// .content_type("text/html; charset=utf-8")
510 /// .send("<html><body>åäö</body></html>")?;
511 /// # Ok::<_, ureq::Error>(())
512 /// ```
513 pub fn content_type<V>(mut self, content_type: V) -> Self
514 where
515 HeaderValue: TryFrom<V>,
516 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
517 {
518 self.builder = self.builder.header("content-type", content_type);
519 self
520 }
521
522 /// Send body data and blocks the caller until we receive response.
523 ///
524 /// ```
525 /// let res = ureq::post("http://httpbin.org/post")
526 /// .send(&[0_u8; 1000])?;
527 /// # Ok::<_, ureq::Error>(())
528 /// ```
529 pub fn send(self, data: impl AsSendBody) -> Result<Response<Body>, Error> {
530 let request = self.builder.body(())?;
531 let mut data_ref = data;
532 do_call(self.agent, request, self.query_extra, data_ref.as_body())
533 }
534
535 /// Send an empty body.
536 ///
537 /// The method is POST, PUT or PATCH, which normally has a body. Using
538 /// this function makes it explicit you want to send an empty body despite
539 /// the method.
540 ///
541 /// This is equivalent to `.send(&[])`.
542 ///
543 /// ```
544 /// let res = ureq::post("http://httpbin.org/post")
545 /// .send_empty()?;
546 /// # Ok::<_, ureq::Error>(())
547 /// ```
548 pub fn send_empty(self) -> Result<Response<Body>, Error> {
549 self.send(&[])
550 }
551
552 /// Send form encoded data.
553 ///
554 /// Constructs a [form submission] with the content-type header
555 /// `application/x-www-form-urlencoded`. Keys and values will be URL encoded.
556 ///
557 /// ```
558 /// let form = [
559 /// ("name", "martin"),
560 /// ("favorite_bird", "blue-footed booby"),
561 /// ];
562 ///
563 /// let response = ureq::post("http://httpbin.org/post")
564 /// .send_form(form)?;
565 /// # Ok::<_, ureq::Error>(())
566 /// ```
567 ///
568 /// [form submission]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#url-encoded_form_submission
569 pub fn send_form<I, K, V>(self, iter: I) -> Result<Response<Body>, Error>
570 where
571 I: IntoIterator<Item = (K, V)>,
572 K: AsRef<str>,
573 V: AsRef<str>,
574 {
575 let iter = iter.into_iter();
576
577 // TODO(martin): can we calculate a size hint for capacity here?
578 let mut body = String::new();
579
580 for (k, v) in iter {
581 if !body.is_empty() {
582 body.push('&');
583 }
584 body.push_str(&form_url_enc(k.as_ref()));
585 body.push('=');
586 body.push_str(&form_url_enc(v.as_ref()));
587 }
588
589 let mut request = self.builder.body(())?;
590
591 if !request.headers().has_content_type() {
592 request.headers_mut().append(
593 http::header::CONTENT_TYPE,
594 HeaderValue::from_static("application/x-www-form-urlencoded"),
595 );
596 }
597
598 do_call(self.agent, request, self.query_extra, body.as_body())
599 }
600
601 /// Send body data as JSON.
602 ///
603 /// Requires the **json** feature.
604 ///
605 /// The data typically derives [`Serialize`](serde::Serialize) and is converted
606 /// to a string before sending (does allocate). Will set the content-type header
607 /// `application/json`.
608 ///
609 /// ```
610 /// use serde::Serialize;
611 ///
612 /// #[derive(Serialize)]
613 /// struct MyData {
614 /// thing: String,
615 /// }
616 ///
617 /// let body = MyData {
618 /// thing: "yo".to_string(),
619 /// };
620 ///
621 /// let res = ureq::post("http://httpbin.org/post")
622 /// .send_json(&body)?;
623 /// # Ok::<_, ureq::Error>(())
624 /// ```
625 #[cfg(feature = "json")]
626 pub fn send_json(self, data: impl serde::ser::Serialize) -> Result<Response<Body>, Error> {
627 let mut request = self.builder.body(())?;
628 let body = SendBody::from_json(&data)?;
629
630 if !request.headers().has_content_type() {
631 request.headers_mut().append(
632 http::header::CONTENT_TYPE,
633 HeaderValue::from_static("application/json; charset=utf-8"),
634 );
635 }
636
637 do_call(self.agent, request, self.query_extra, body)
638 }
639}
640
641fn do_call(
642 agent: Agent,
643 mut request: Request<()>,
644 query_extra: Vec<QueryParam<'static>>,
645 body: SendBody,
646) -> Result<Response<Body>, Error> {
647 if !query_extra.is_empty() {
648 request.uri().ensure_valid_url()?;
649 request = amend_request_query(request, query_extra.into_iter());
650 }
651 let response = agent.run_via_middleware(request, body)?;
652 Ok(response)
653}
654
655fn amend_request_query(
656 request: Request<()>,
657 query_extra: impl Iterator<Item = QueryParam<'static>>,
658) -> Request<()> {
659 let (mut parts, body) = request.into_parts();
660 let uri = parts.uri;
661 let mut path = uri.path().to_string();
662 let query_existing = parse_query_params(uri.query().unwrap_or(""));
663
664 let mut do_first = true;
665
666 fn append<'a>(
667 path: &mut String,
668 do_first: &mut bool,
669 iter: impl Iterator<Item = QueryParam<'a>>,
670 ) {
671 for q in iter {
672 if *do_first {
673 *do_first = false;
674 path.push('?');
675 } else {
676 path.push('&');
677 }
678 path.push_str(&q);
679 }
680 }
681
682 append(&mut path, &mut do_first, query_existing);
683 append(&mut path, &mut do_first, query_extra);
684
685 // Unwraps are OK, because we had a correct URI to begin with
686 let rebuild = Uri::builder()
687 .scheme(uri.scheme().unwrap().clone())
688 .authority(uri.authority().unwrap().clone())
689 .path_and_query(path)
690 .build()
691 .unwrap();
692
693 parts.uri = rebuild;
694
695 Request::from_parts(parts, body)
696}
697
698impl fmt::Debug for RequestBuilder<WithoutBody> {
699 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
700 f.debug_struct("RequestBuilder<WithoutBody>")
701 // unwraps are OK because we can't be in this state without having method+uri
702 .field("method", &self.builder.method_ref().unwrap())
703 .field("uri", &self.builder.uri_ref().unwrap())
704 .finish()
705 }
706}
707
708impl fmt::Debug for RequestBuilder<WithBody> {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 f.debug_struct("RequestBuilder<WithBody>")
711 // unwraps are OK because we can't be in this state without having method+uri
712 .field("method", &self.builder.method_ref().unwrap())
713 .field("uri", &self.builder.uri_ref().unwrap())
714 .finish()
715 }
716}
717
718#[cfg(test)]
719mod test {
720 use std::time::Duration;
721
722 use crate::get;
723 use crate::test::init_test_log;
724
725 use super::*;
726
727 #[test]
728 fn disallow_empty_host() {
729 let err = crate::get("file:///some/path").call().unwrap_err();
730 assert_eq!(err.to_string(), "http: invalid format");
731 assert!(matches!(err, Error::Http(_)));
732 }
733
734 #[test]
735 fn query_with_encoding() {
736 let request = Request::builder()
737 .uri("https://foo.bar/path")
738 .body(())
739 .unwrap();
740
741 // Test that single quotes and spaces are encoded
742 let amended = amend_request_query(
743 request,
744 vec![QueryParam::new_key_value("key", "value with 'quotes'")].into_iter(),
745 );
746
747 assert_eq!(
748 amended.uri(),
749 "https://foo.bar/path?key=value%20with%20%27quotes%27"
750 );
751 }
752
753 #[test]
754 fn query_raw_without_encoding() {
755 let request = Request::builder()
756 .uri("https://foo.bar/path")
757 .body(())
758 .unwrap();
759
760 // Test that raw values remain unencoded (using URI-valid characters)
761 let amended = amend_request_query(
762 request,
763 vec![QueryParam::new_key_value_raw("key", "value-with-'quotes'")].into_iter(),
764 );
765
766 assert_eq!(
767 amended.uri(),
768 "https://foo.bar/path?key=value-with-'quotes'"
769 );
770 }
771 #[test]
772 fn encoded_and_raw_combined() {
773 let request = Request::builder()
774 .uri("https://foo.bar/path")
775 .body(())
776 .unwrap();
777
778 // Test combination of encoded and unencoded parameters
779 let amended = amend_request_query(
780 request,
781 vec![
782 QueryParam::new_key_value("encoded", "value with spaces"),
783 QueryParam::new_key_value_raw("raw", "value-without-spaces"),
784 ]
785 .into_iter(),
786 );
787
788 assert_eq!(
789 amended.uri(),
790 "https://foo.bar/path?encoded=value%20with%20spaces&raw=value-without-spaces"
791 );
792 }
793 #[test]
794 fn debug_print_without_body() {
795 let call = crate::get("https://foo/bar");
796 assert_eq!(
797 format!("{:?}", call),
798 "RequestBuilder<WithoutBody> { method: GET, uri: https://foo/bar }"
799 );
800 }
801
802 #[test]
803 fn debug_print_with_body() {
804 let call = crate::post("https://foo/bar");
805 assert_eq!(
806 format!("{:?}", call),
807 "RequestBuilder<WithBody> { method: POST, uri: https://foo/bar }"
808 );
809 }
810
811 #[test]
812 fn config_after_broken_url() {
813 init_test_log();
814 get("http://x.y.z/ borked url")
815 .config()
816 .timeout_global(Some(Duration::from_millis(1)))
817 .build();
818 }
819
820 #[test]
821 fn add_params_to_request_without_query() {
822 let request = Request::builder()
823 .uri("https://foo.bar/path")
824 .body(())
825 .unwrap();
826
827 let amended = amend_request_query(
828 request,
829 vec![
830 QueryParam::new_key_value("x", "z"),
831 QueryParam::new_key_value("ab", "cde"),
832 ]
833 .into_iter(),
834 );
835
836 assert_eq!(amended.uri(), "https://foo.bar/path?x=z&ab=cde");
837 }
838
839 #[test]
840 fn add_params_to_request_with_query() {
841 let request = Request::builder()
842 .uri("https://foo.bar/path?x=z")
843 .body(())
844 .unwrap();
845
846 let amended = amend_request_query(
847 request,
848 vec![QueryParam::new_key_value("ab", "cde")].into_iter(),
849 );
850
851 assert_eq!(amended.uri(), "https://foo.bar/path?x=z&ab=cde");
852 }
853
854 #[test]
855 fn add_params_that_need_percent_encoding() {
856 let request = Request::builder()
857 .uri("https://foo.bar/path")
858 .body(())
859 .unwrap();
860
861 let amended = amend_request_query(
862 request,
863 vec![QueryParam::new_key_value("å ", "i åa ä e ö")].into_iter(),
864 );
865
866 assert_eq!(
867 amended.uri(),
868 "https://foo.bar/path?%C3%A5%20=i%20%C3%A5a%20%C3%A4%20e%20%C3%B6"
869 );
870 }
871}