client_util/
request.rs

1#[cfg(feature = "multipart")]
2mod multipart;
3use bytes::Bytes;
4use futures_core::Stream;
5use http::request::Builder;
6use http::HeaderValue;
7use http::Request;
8use http::Response;
9use http::{header::CONTENT_TYPE, Uri};
10use http_body_util::StreamBody;
11use http_body_util::{combinators::UnsyncBoxBody, Empty, Full};
12#[cfg(feature = "multipart")]
13pub use multipart::*;
14#[cfg(feature = "serde")]
15use serde::Serialize;
16use std::future::Future;
17
18use crate::body::{empty, full};
19use crate::client::ClientBody;
20use crate::client::MaybeAbort;
21use crate::error::BodyError;
22use crate::error::ClientError;
23
24/// Extension trait for [`http::Request`].
25pub trait RequestExt<B>: Sized {
26    #[cfg(feature = "json")]
27    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
28    fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>>;
29    #[cfg(feature = "query")]
30    #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
31    fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Request<B>>;
32    #[cfg(feature = "multipart")]
33    #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
34    fn multipart(
35        self,
36        form: multipart::Form,
37    ) -> crate::Result<Request<UnsyncBoxBody<Bytes, BodyError>>>;
38    #[cfg(feature = "form")]
39    #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
40    fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>>;
41    #[cfg(feature = "stream")]
42    fn stream<S: Stream>(self, stream: S) -> crate::Result<Request<StreamBody<S>>>;
43    fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>>;
44    fn empty(self) -> crate::Result<Request<Empty<Bytes>>>;
45    fn collect_into_bytes(self) -> impl Future<Output = crate::Result<Request<Full<Bytes>>>> + Send
46    where
47        B: http_body::Body<Data = Bytes> + Send + 'static,
48        B::Error: std::error::Error + Send + Sync;
49    fn with_version(self, version: http::Version) -> Request<B>;
50    fn with_method(self, method: http::Method) -> Request<B>;
51    fn with_header<K>(self, key: K, value: http::header::HeaderValue) -> Request<B>
52    where
53        K: http::header::IntoHeaderName;
54    fn with_headers(self, header_map: http::header::HeaderMap) -> Request<B>;
55    #[cfg(feature = "auth")]
56    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
57    fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Request<B>
58    where
59        U: std::fmt::Display,
60        P: std::fmt::Display,
61        Self: Sized,
62    {
63        let header_value = crate::util::basic_auth(username, password);
64        self.with_header(http::header::AUTHORIZATION, header_value)
65    }
66
67    #[cfg(feature = "auth")]
68    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
69    fn bearer_auth<T>(self, token: T) -> Request<B>
70    where
71        T: std::fmt::Display,
72    {
73        let header_value = crate::util::bearer_auth(token);
74        self.with_header(http::header::AUTHORIZATION, header_value)
75    }
76
77    fn send<S, R>(self, client: S) -> impl Future<Output = crate::Result<S::Response>> + Send
78    where
79        B: http_body::Body<Data = Bytes> + Send + 'static,
80        B::Error: std::error::Error + Send + Sync,
81        S: tower_service::Service<Request<ClientBody>, Response = Response<R>> + Send,
82        R: http_body::Body,
83        <S as tower_service::Service<Request<ClientBody>>>::Error:
84            std::error::Error + Send + Sync + 'static,
85        <S as tower_service::Service<Request<ClientBody>>>::Future: Send;
86
87    #[cfg(feature = "rt-tokio")]
88    #[cfg_attr(docsrs, doc(cfg(feature = "rt-tokio")))]
89    /// Send the request to a service with a timeout layer.
90    fn send_timeout<S, R>(
91        self,
92        client: S,
93        timeout: std::time::Duration,
94    ) -> impl Future<Output = crate::Result<Response<MaybeAbort<R>>>> + Send
95    where
96        B: http_body::Body<Data = Bytes> + Send + 'static,
97        B::Error: std::error::Error + Send + Sync,
98        S: tower_service::Service<Request<ClientBody>, Response = Response<R>> + Send,
99        <S as tower_service::Service<Request<ClientBody>>>::Error:
100            std::error::Error + Send + Sync + 'static,
101        <S as tower_service::Service<Request<ClientBody>>>::Future: Send,
102        R: http_body::Body,
103        Self: Sized,
104    {
105        use tower::util::ServiceExt;
106        self.send(tower_http::timeout::Timeout::new(
107            client.map_response(|r| r.map(MaybeAbort::success)),
108            timeout,
109        ))
110    }
111}
112
113/// Extension trait for [`http::request::Builder`].
114pub trait RequestBuilderExt: Sized {
115    #[cfg(feature = "json")]
116    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
117    fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>>;
118    #[cfg(feature = "query")]
119    #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
120    fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Self>;
121    #[cfg(feature = "multipart")]
122    #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
123    fn multipart(self, form: multipart::Form) -> crate::Result<Request<crate::DynBody>>;
124    #[cfg(feature = "form")]
125    #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
126    fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>>;
127    fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>>;
128    fn empty(self) -> crate::Result<Request<Empty<Bytes>>>;
129    fn headers(self, header_map: http::header::HeaderMap) -> Self;
130    #[cfg(feature = "auth")]
131    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
132    fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self
133    where
134        U: std::fmt::Display,
135        P: std::fmt::Display,
136        Self: Sized;
137    #[cfg(feature = "auth")]
138    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
139    fn bearer_auth<T>(self, token: T) -> Self
140    where
141        T: std::fmt::Display;
142}
143
144impl RequestBuilderExt for Builder {
145    /// Consumes the builder, setting the request body as JSON.
146    #[cfg(feature = "json")]
147    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
148    fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>> {
149        let req = self
150            .body(())
151            .map_err(crate::Error::with_context("build request body"))?;
152        req.json(body)
153    }
154
155    /// Add query parameters to the request URI.
156    #[cfg(feature = "query")]
157    #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
158    fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Self> {
159        let new_uri = if let Some(uri) = self.uri_ref() {
160            build_query_uri(uri.clone(), query)?
161        } else {
162            Uri::default()
163        };
164        Ok(self.uri(new_uri))
165    }
166
167    /// Consumes the builder, setting the request body as multipart form data.
168    #[cfg(feature = "multipart")]
169    #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
170    fn multipart(self, form: multipart::Form) -> crate::Result<Request<crate::DynBody>> {
171        let req = self
172            .body(())
173            .map_err(crate::Error::with_context("build request body"))?;
174        req.multipart(form)
175    }
176
177    /// Consumes the builder, setting the request body as form data.
178    #[cfg(feature = "form")]
179    #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
180    fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>> {
181        let req = self
182            .body(())
183            .map_err(crate::Error::with_context("build request body"))?;
184        req.form(form)
185    }
186
187    /// Consumes the builder, setting the request body as plain text.
188    fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>> {
189        let req = self
190            .body(())
191            .map_err(crate::Error::with_context("build request body"))?;
192        req.plain_text(body)
193    }
194
195    /// Consumes the builder, setting the request body as empty.
196    fn empty(self) -> crate::Result<Request<Empty<Bytes>>> {
197        self.body(())
198            .map_err(crate::Error::with_context("build request body"))?
199            .empty()
200    }
201
202    /// Add basic authentication to the request.
203    #[cfg(feature = "auth")]
204    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
205    fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self
206    where
207        U: std::fmt::Display,
208        P: std::fmt::Display,
209        Self: Sized,
210    {
211        let header_value = crate::util::basic_auth(username, password);
212        self.header(http::header::AUTHORIZATION, header_value)
213    }
214
215    /// Add bearer authentication to the request.
216    #[cfg(feature = "auth")]
217    #[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
218    fn bearer_auth<T>(self, token: T) -> Self
219    where
220        T: std::fmt::Display,
221    {
222        let header_value = crate::util::bearer_auth(token);
223        self.header(http::header::AUTHORIZATION, header_value)
224    }
225
226    /// Extend multiple request headers.
227    fn headers(mut self, header_map: http::header::HeaderMap) -> Self {
228        if let Some(headers) = self.headers_mut() {
229            headers.extend(header_map);
230        }
231        self
232    }
233}
234
235impl<B> RequestExt<B> for Request<B>
236where
237    B: Send,
238{
239    /// Set the request body as JSON.
240    #[cfg(feature = "json")]
241    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
242    fn json<T: Serialize + ?Sized>(self, body: &T) -> crate::Result<Request<Full<Bytes>>> {
243        let json_body =
244            serde_json::to_vec(&body).map_err(crate::Error::with_context("serialize json body"))?;
245        let (mut parts, _) = self.into_parts();
246        parts.headers.insert(
247            CONTENT_TYPE,
248            HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
249        );
250        let request = Request::from_parts(parts, Full::new(Bytes::from(json_body)));
251        Ok(request)
252    }
253
254    /// Add query parameters to the request URI.
255    #[cfg(feature = "query")]
256    #[cfg_attr(docsrs, doc(cfg(feature = "query")))]
257    fn query<Q: Serialize + ?Sized>(self, query: &Q) -> crate::Result<Request<B>> {
258        use http::uri::PathAndQuery;
259        use std::str::FromStr;
260        let new_query = serde_urlencoded::to_string(query)
261            .map_err(crate::Error::with_context("serialize query string"))?;
262        if new_query.is_empty() {
263            return Ok(self);
264        }
265        let (mut parts, body) = self.into_parts();
266        let mut uri_parts = parts.uri.into_parts();
267        let new_uri = if let Some(pq) = uri_parts.path_and_query {
268            let mut new_pq_string = String::with_capacity(new_query.len() + pq.as_str().len() + 2);
269            new_pq_string.push_str(pq.path());
270            new_pq_string.push('?');
271            if let Some(old_query) = pq.query() {
272                new_pq_string.push_str(old_query);
273                new_pq_string.push('&');
274            }
275            new_pq_string.push_str(&new_query);
276            let new_pq = PathAndQuery::from_str(&new_pq_string)
277                .map_err(crate::Error::with_context("parse new path and query"))?;
278            uri_parts.path_and_query = Some(new_pq);
279            Uri::from_parts(uri_parts).map_err(crate::Error::with_context(
280                "reconstruct uri with new path and query",
281            ))?
282        } else {
283            Uri::builder()
284                .path_and_query(new_query)
285                .build()
286                .map_err(crate::Error::with_context("build new uri"))?
287        };
288        parts.uri = new_uri;
289        Ok(Request::from_parts(parts, body))
290    }
291
292    /// Set the request body as multipart form data.
293    #[cfg(feature = "multipart")]
294    #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
295    fn multipart(self, mut form: multipart::Form) -> crate::Result<Request<crate::DynBody>> {
296        let (mut parts, _) = self.into_parts();
297        let boundary = form.boundary();
298        parts.headers.insert(
299            CONTENT_TYPE,
300            HeaderValue::from_str(&format!(
301                "{}; boundary={}",
302                mime::MULTIPART_FORM_DATA,
303                boundary
304            ))
305            .map_err(crate::Error::with_context("build content type header"))?,
306        );
307        if let Some(length) = form.compute_length() {
308            parts.headers.insert(
309                http::header::CONTENT_LENGTH,
310                HeaderValue::from_str(&length.to_string())
311                    .map_err(crate::Error::with_context("build content length header"))?,
312            );
313        }
314        let body = form.stream();
315        Ok(Request::from_parts(parts, body))
316    }
317
318    /// Set the request body as form data.
319    #[cfg(feature = "form")]
320    #[cfg_attr(docsrs, doc(cfg(feature = "form")))]
321    fn form<T: Serialize + ?Sized>(self, form: &T) -> crate::Result<Request<Full<Bytes>>> {
322        let (mut parts, _) = self.into_parts();
323        let body = serde_urlencoded::to_string(form)
324            .map_err(crate::Error::with_context("serialize form"))?;
325        parts.headers.insert(
326            CONTENT_TYPE,
327            HeaderValue::from_static(mime::APPLICATION_WWW_FORM_URLENCODED.as_ref()),
328        );
329        Ok(Request::from_parts(parts, full(body)))
330    }
331
332    /// Set the request body as plain text.
333    #[inline]
334    fn plain_text(self, body: impl Into<Bytes>) -> crate::Result<Request<Full<Bytes>>> {
335        let (mut parts, _) = self.into_parts();
336        parts.headers.insert(
337            CONTENT_TYPE,
338            HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
339        );
340        Ok(Request::from_parts(parts, full(body)))
341    }
342
343    /// Set the request body as a stream.
344    #[cfg(feature = "stream")]
345    fn stream<S: Stream>(self, stream: S) -> crate::Result<Request<StreamBody<S>>> {
346        let (parts, _) = self.into_parts();
347        Ok(Request::from_parts(parts, StreamBody::new(stream)))
348    }
349
350    /// Set the request body as empty.
351    #[inline]
352    fn empty(self) -> crate::Result<Request<Empty<Bytes>>> {
353        let (parts, _) = self.into_parts();
354        Ok(Request::from_parts(parts, empty()))
355    }
356
357    /// Collect the request body stream into bytes. This is useful when you want to clone the request.
358    async fn collect_into_bytes(self) -> crate::Result<Request<Full<Bytes>>>
359    where
360        B: http_body::Body<Data = Bytes> + Send + 'static,
361        B::Error: std::error::Error + Send + Sync,
362    {
363        use http_body_util::BodyExt;
364        let (parts, body) = self.into_parts();
365        let body = body
366            .collect()
367            .await
368            .map_err(|e| {
369                crate::Error::new(
370                    crate::ErrorKind::Body(BodyError(Box::new(e))),
371                    "collect body stream",
372                )
373            })?
374            .to_bytes();
375        Ok(Request::from_parts(parts, full(body)))
376    }
377
378    /// Set the request HTTP version.
379    #[inline]
380    fn with_version(mut self, version: http::Version) -> Request<B> {
381        *self.version_mut() = version;
382        self
383    }
384
385    /// Set the request method.
386    #[inline]
387    fn with_method(mut self, method: http::Method) -> Request<B> {
388        *self.method_mut() = method;
389        self
390    }
391
392    /*
393    // I think we may don't need to modify schema?
394    fn schema(self, schema: Scheme) -> crate::Result<Request<B>> {
395        let (mut parts, body) = self.into_parts();
396        let mut uri_parts = parts.uri.into_parts();
397        uri_parts.scheme = Some(schema);
398        parts.uri = Uri::from_parts(uri_parts).map_err(crate::Error::with_context(
399            "reconstruct uri with new schema",
400        ))?;
401        Ok(Request::from_parts(parts, body))
402    }
403    */
404
405    /// Set a request header.
406    #[inline]
407    fn with_header<K>(mut self, key: K, value: http::header::HeaderValue) -> Request<B>
408    where
409        K: http::header::IntoHeaderName,
410    {
411        self.headers_mut().insert(key, value);
412        self
413    }
414
415    /// Extend multiple request headers.
416    #[inline]
417    fn with_headers(mut self, header_map: http::header::HeaderMap) -> Request<B> {
418        self.headers_mut().extend(header_map);
419        self
420    }
421
422    /// Send the request to a service.
423    ///
424    /// If you enabled any decompression feature, the response body will be automatically decompressed.
425    #[allow(unused_mut)]
426    async fn send<S, R>(self, mut client: S) -> crate::Result<S::Response>
427    where
428        B: http_body::Body<Data = Bytes> + Send + 'static,
429        B::Error: std::error::Error + Send + Sync,
430        S: tower_service::Service<Request<ClientBody>, Response = Response<R>> + Send,
431        R: http_body::Body,
432        <S as tower_service::Service<Request<ClientBody>>>::Error:
433            std::error::Error + Send + Sync + 'static,
434        <S as tower_service::Service<Request<ClientBody>>>::Future: Send,
435    {
436        use http_body_util::BodyExt;
437        #[allow(unused_imports)]
438        use tower_service::Service;
439        #[cfg(all(
440            any(
441                feature = "decompression-deflate",
442                feature = "decompression-gzip",
443                feature = "decompression-br",
444                feature = "decompression-zstd",
445            ),
446            feature = "rt-tokio"
447        ))]
448        let mut client = tower_http::decompression::Decompression::new(client);
449        let request = self.map(|b| UnsyncBoxBody::new(b.map_err(|e| BodyError(Box::new(e)))));
450        match client.call(request).await {
451            #[cfg(all(
452                any(
453                    feature = "decompression-deflate",
454                    feature = "decompression-gzip",
455                    feature = "decompression-br",
456                    feature = "decompression-zstd",
457                ),
458                feature = "rt-tokio"
459            ))]
460            Ok(response) => Ok(response.map(|b| b.into_inner())),
461            #[cfg(not(all(
462                any(
463                    feature = "decompression-deflate",
464                    feature = "decompression-gzip",
465                    feature = "decompression-br",
466                    feature = "decompression-zstd",
467                ),
468                feature = "rt-tokio"
469            )))]
470            Ok(response) => Ok(response),
471            Err(e) => {
472                let e = ClientError::from(e);
473                Err(crate::Error::new(
474                    crate::ErrorKind::Client(e),
475                    "send request",
476                ))
477            }
478        }
479    }
480}
481
482#[cfg(feature = "query")]
483fn build_query_uri<Q: Serialize + ?Sized>(uri: Uri, query: &Q) -> crate::Result<Uri> {
484    use std::str::FromStr;
485    let new_query = serde_urlencoded::to_string(query)
486        .map_err(crate::Error::with_context("serialize query string"))?;
487    if new_query.is_empty() {
488        return Ok(uri);
489    }
490    let mut uri_parts = uri.into_parts();
491    let new_uri = if let Some(pq) = uri_parts.path_and_query {
492        let mut new_pq_string = String::with_capacity(new_query.len() + pq.as_str().len() + 2);
493        new_pq_string.push_str(pq.path());
494        new_pq_string.push('?');
495        if let Some(old_query) = pq.query() {
496            new_pq_string.push_str(old_query);
497            new_pq_string.push('&');
498        }
499        new_pq_string.push_str(&new_query);
500        let new_pq = http::uri::PathAndQuery::from_str(&new_pq_string)
501            .map_err(crate::Error::with_context("parse new path and query"))?;
502        uri_parts.path_and_query = Some(new_pq);
503        Uri::from_parts(uri_parts).map_err(crate::Error::with_context(
504            "reconstruct uri with new path and query",
505        ))?
506    } else {
507        Uri::builder()
508            .path_and_query(new_query)
509            .build()
510            .map_err(crate::Error::with_context("build new uri"))?
511    };
512    Ok(new_uri)
513}
514
515/*
516    I copied and modified those tests from reqwest: https://github.com/seanmonstar/reqwest/blob/master/src/async_impl/request.rs
517*/
518#[cfg(test)]
519mod tests {
520
521    use super::*;
522    use std::collections::BTreeMap;
523
524    #[test]
525    fn add_query_append() -> crate::Result<()> {
526        let req = Request::get("https://google.com/")
527            .query(&[("foo", "bar")])?
528            .query(&[("qux", 3)])?
529            .empty()?;
530
531        assert_eq!(req.uri().query(), Some("foo=bar&qux=3"));
532        Ok(())
533    }
534
535    #[test]
536    fn add_query_append_same() -> crate::Result<()> {
537        let req = Request::get("https://google.com/")
538            .query(&[("foo", "a"), ("foo", "b")])?
539            .empty()?;
540
541        assert_eq!(req.uri().query(), Some("foo=a&foo=b"));
542        Ok(())
543    }
544
545    #[test]
546    fn add_query_struct() -> crate::Result<()> {
547        #[derive(serde::Serialize)]
548        struct Params {
549            foo: String,
550            qux: i32,
551        }
552
553        let params = Params {
554            foo: "bar".into(),
555            qux: 3,
556        };
557        let req = Request::get("https://google.com/")
558            .query(&params)?
559            .empty()?;
560
561        assert_eq!(req.uri().query(), Some("foo=bar&qux=3"));
562        Ok(())
563    }
564
565    #[test]
566    fn add_query_map() -> crate::Result<()> {
567        let mut params = BTreeMap::new();
568        params.insert("foo", "bar");
569        params.insert("qux", "three");
570
571        let req = Request::get("https://google.com/")
572            .query(&params)?
573            .empty()?;
574        assert_eq!(req.uri().query(), Some("foo=bar&qux=three"));
575        Ok(())
576    }
577
578    #[test]
579    fn test_replace_headers() {
580        use http::HeaderMap;
581
582        let mut headers = HeaderMap::new();
583        headers.insert("foo", "bar".parse().unwrap());
584        headers.append("foo", "baz".parse().unwrap());
585
586        let req = Request::get("https://hyper.rs")
587            .header("im-a", "keeper")
588            .header("foo", "pop me")
589            .headers(headers)
590            .empty()
591            .expect("request build");
592
593        assert_eq!(req.headers()["im-a"], "keeper");
594
595        let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
596        assert_eq!(foo.len(), 2);
597        assert_eq!(foo[0], "bar");
598        assert_eq!(foo[1], "baz");
599    }
600
601    #[test]
602    #[cfg(feature = "auth")]
603    fn test_basic_auth_sensitive_header() {
604        let some_url = "https://localhost/";
605
606        let req = Request::get(some_url)
607            .basic_auth("Aladdin", Some("open sesame"))
608            .empty()
609            .expect("request build");
610
611        assert_eq!(req.uri().to_string(), "https://localhost/");
612        assert_eq!(
613            req.headers()["authorization"],
614            "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
615        );
616        assert!(req.headers()["authorization"].is_sensitive());
617    }
618
619    #[test]
620    #[cfg(feature = "auth")]
621    fn test_bearer_auth_sensitive_header() {
622        let some_url = "https://localhost/";
623
624        let req = Request::get(some_url)
625            .bearer_auth("Hold my bear")
626            .empty()
627            .expect("request build");
628
629        assert_eq!(req.uri().to_string(), "https://localhost/");
630        assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
631        assert!(req.headers()["authorization"].is_sensitive());
632    }
633
634    #[test]
635    fn test_explicit_sensitive_header() {
636        let some_url = "https://localhost/";
637
638        let mut header = http::HeaderValue::from_static("in plain sight");
639        header.set_sensitive(true);
640
641        let req = Request::get(some_url)
642            .header("hiding", header)
643            .empty()
644            .expect("request build");
645
646        assert_eq!(req.uri().to_string(), "https://localhost/");
647        assert_eq!(req.headers()["hiding"], "in plain sight");
648        assert!(req.headers()["hiding"].is_sensitive());
649    }
650
651    #[test]
652    fn convert_from_http_request() {
653        let req = Request::builder()
654            .method("GET")
655            .uri("http://localhost/")
656            .header("User-Agent", "my-awesome-agent/1.0")
657            .body("test test test")
658            .unwrap();
659        let test_data = b"test test test";
660        assert_eq!(req.body().as_bytes(), &test_data[..]);
661        let headers = req.headers();
662        assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
663        assert_eq!(req.method(), http::Method::GET);
664        assert_eq!(req.uri().to_string(), "http://localhost/");
665    }
666
667    #[test]
668    fn set_http_request_version() {
669        let req = Request::builder()
670            .method("GET")
671            .uri("http://localhost/")
672            .header("User-Agent", "my-awesome-agent/1.0")
673            .version(http::Version::HTTP_11)
674            .body("test test test")
675            .unwrap();
676        let test_data = b"test test test";
677        assert_eq!(req.body().as_bytes(), &test_data[..]);
678        let headers = req.headers();
679        assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
680        assert_eq!(req.method(), http::Method::GET);
681        assert_eq!(req.uri().to_string(), "http://localhost/");
682        assert_eq!(req.version(), http::Version::HTTP_11);
683    }
684}