flawless_http/
request.rs

1use std::time::Duration;
2
3use flawless::idempotent::Idempotence;
4
5use crate::transport::flawless::{Flawless, Idempotent};
6use crate::{response::Response, transport::HTTPTransport, Config, Error, IntoBody};
7
8/// An HTTP request.
9#[derive(Debug, Clone)]
10pub struct Request<T>
11where
12    T: HTTPTransport,
13{
14    config: Config,
15    transport: T,
16    method: String,
17    url: String,
18    headers: Vec<(String, String)>,
19    body: Vec<u8>,
20}
21
22impl<T: HTTPTransport> Request<T> {
23    /// Create a new request over a provided transport layer.
24    pub fn new<M, U>(transport: T, method: M, url: U) -> Request<T>
25    where
26        M: Into<String>,
27        U: Into<String>,
28    {
29        Request {
30            config: Config::default(),
31            transport,
32            method: method.into(),
33            url: url.into(),
34            headers: Vec::new(),
35            body: Vec::new(),
36        }
37    }
38
39    /// Perform the request.
40    pub fn send(self) -> Result<Response, Error> {
41        self.transport.send(self.config, self.method, self.url, self.headers, self.body)
42    }
43
44    //// Sets the duration of time a request should wait on a response before returning a timeout error.
45    ///
46    /// Default: 120s
47    ///
48    /// Timeout errors are of kind [`ErrorKind::Io`](crate::ErrorKind::Io). Call [`Error::message()`] for
49    /// more information.
50    ///
51    /// ```no_run
52    /// use std::time::Duration;
53    /// use flawless_http::{get, ErrorKind};
54    ///
55    /// let response = get("https://httpbin.org/delay/2")
56    ///                  .timeout(Duration::from_secs(1))
57    ///                  .send();
58    /// assert!(response.is_err());
59    /// assert_eq!(response.err().unwrap().kind(), ErrorKind::Io);
60    /// ```
61    pub fn timeout(mut self, duration: Duration) -> Self {
62        self.config.set_timeout(duration);
63        self
64    }
65
66    /// Sets the size limit of a response body.
67    ///
68    /// Default: 10_000_000 (~10Mb)
69    ///
70    /// In case a response returns a bigger body, it will be truncated.
71    ///
72    /// ```no_run
73    /// use flawless_http::get;
74    ///
75    /// let response = get("https://httpbin.org/get")
76    ///                  .response_body_limit(100)
77    ///                  .send();
78    /// assert!(response.is_ok());
79    /// assert_eq!(response.unwrap().text().unwrap().len(), 100);
80    /// ```
81    pub fn response_body_limit(mut self, limit: u64) -> Self {
82        self.config.set_response_body_limit(limit);
83        self
84    }
85
86    /// Gets header by name if it exists, otherwise returns `None`.
87    ///
88    /// If multiple headers exist with the same name, will return the first one. HTTP header names are NOT
89    /// case-sensitive and case will be ignored during lookup.
90    pub fn header<N: AsRef<str>>(&self, name: N) -> Option<&String> {
91        let name_l = name.as_ref().to_lowercase();
92        for header in self.headers.iter() {
93            let header_name_l = header.0.to_lowercase();
94            if name_l == header_name_l {
95                return Some(&header.1);
96            }
97        }
98
99        None
100    }
101
102    /// Sets a request header.
103    ///
104    /// If the header already exists, it will be overwritten. HTTP header names are NOT case-sensitive and
105    /// case will be ignored during comparison. dsfsd sdfsdffsdf sdfsd
106    /// ```no_run
107    /// use serde_json::Value;
108    /// use flawless_http::get;
109    ///
110    /// let response = get("https://httpbin.org/get")
111    ///                  .set_header("Accept", "text/plain")
112    ///                  .send();
113    /// let json: Value = response.unwrap().json().unwrap();
114    /// let accept_header = json["headers"]["Accept"].as_str();
115    /// assert_eq!(accept_header, Some("text/plain"));
116    /// ```
117    pub fn set_header<N, V>(mut self, name: N, value: V) -> Self
118    where
119        N: Into<String>,
120        V: Into<String>,
121    {
122        // Attempt to override existing header.
123        let name: String = name.into();
124        for header in self.headers.iter_mut() {
125            if header.0.to_lowercase() == name.to_lowercase() {
126                header.1 = value.into();
127                return self;
128            }
129        }
130        // If no header was overwritten, push it at the end of the array.
131        self.headers.push((name, value.into()));
132        self
133    }
134
135    /// Sets the body of a request.
136    ///
137    /// Depending on the argument, additional request headers might be set. If headers are already manually
138    /// set by the user, this call should not override them.
139    ///
140    /// # Text arguments
141    ///
142    /// In case a `String` or `&str` type is given to `body`, the `"Content-Length"` header is going to be set
143    /// to the byte length of the string.
144    ///
145    /// ```no_run
146    /// use flawless_http::post;
147    ///
148    /// let response = post("https://httpbin.org/post")
149    ///                  .body("Hello world!")
150    ///                  .send();
151    /// assert!(response.is_ok());
152    /// ```
153    ///
154    /// # Form arguments
155    ///
156    /// In case a slice of tuples of strings is passed in (`&[(&str, &str)]`), `body` will assume a for is
157    /// being submitted and URL encode it. It will set the header `"Content-Type"` to
158    /// `"application/x-www-form-urlencoded"`, and `"Content-Length"` to the size of the encoded content.
159    ///
160    /// ```no_run
161    /// use flawless_http::post;
162    ///
163    /// let response = post("https://httpbin.org/post")
164    ///                  .body([
165    ///                     ("Hello", "world!"),
166    ///                     ("second", "argument"),
167    ///                   ].as_ref())
168    ///                  .send();
169    /// assert!(response.is_ok());
170    /// ```
171    ///
172    /// # JSON arguments
173    ///
174    /// In case of a [`serde_json::Value`] type, `body` will assume that the content is of type JSON and set
175    /// the header `"Content-Type"` to `"application/json"`. It will also set `"Content-Length"` to the size
176    /// of the serialized JSON.
177    ///
178    /// ```no_run
179    /// use flawless_http::post;
180    /// use serde_json::json;
181    ///
182    /// let response = post("https://httpbin.org/post")
183    ///                  .body(json!({
184    ///                     "Hello": "world!",
185    ///                     "second": "argument",
186    ///                   }))
187    ///                  .send();
188    /// assert!(response.is_ok());
189    /// ```
190    pub fn body<B: IntoBody<T>>(self, body: B) -> Self {
191        let (mut request, body) = body.into(self);
192        request.body = body;
193        request
194    }
195}
196
197impl Idempotence for Request<Flawless> {
198    type Idempotent = Request<Idempotent>;
199
200    /// Mark the request as idempotent.
201    ///
202    /// Idempotent requests will automatically be retried in case of timeouts or errors.
203    fn idempotent(self) -> Self::Idempotent {
204        let mut config = self.config;
205        config.set_idempotent(true);
206        Request {
207            config,
208            transport: Idempotent,
209            method: self.method,
210            url: self.url,
211            headers: self.headers,
212            body: self.body,
213        }
214    }
215}
216
217/// Create a GET request.
218///
219/// ```no_run
220/// use flawless_http::get;
221///
222/// let response = get("https://httpbin.org/get").send();
223/// assert!(response.is_ok());
224/// ```
225pub fn get<U: Into<String>>(url: U) -> Request<Flawless> {
226    Request::new(Flawless, "GET", url)
227}
228
229/// Create a POST request.
230///
231/// ```no_run
232/// use flawless_http::post;
233///
234/// let response = post("https://httpbin.org/post").send();
235/// assert!(response.is_ok());
236/// ```
237pub fn post<U: Into<String>>(url: U) -> Request<Flawless> {
238    Request::new(Flawless, "POST", url)
239}
240
241/// Create a DELETE request.
242///
243/// ```no_run
244/// use flawless_http::delete;
245///
246/// let response = delete("https://httpbin.org/delete").send();
247/// assert!(response.is_ok());
248/// ```
249pub fn delete<U: Into<String>>(url: U) -> Request<Flawless> {
250    Request::new(Flawless, "DELETE", url)
251}
252
253/// Create a PUT request.
254///
255/// ```no_run
256/// use flawless_http::put;
257///
258/// let response = put("https://httpbin.org/put").send();
259/// assert!(response.is_ok());
260/// ```
261pub fn put<U: Into<String>>(url: U) -> Request<Flawless> {
262    Request::new(Flawless, "PUT", url)
263}
264
265/// Create a HEAD request.
266///
267/// ```no_run
268/// use flawless_http::head;
269///
270/// let response = head("https://httpbin.org/get").send();
271/// assert!(response.is_ok());
272/// ```
273pub fn head<U: Into<String>>(url: U) -> Request<Flawless> {
274    Request::new(Flawless, "HEAD", url)
275}
276
277/// Create a PATCH request.
278///
279/// ```no_run
280/// use flawless_http::patch;
281///
282/// let response = patch("https://httpbin.org/patch").send();
283/// assert!(response.is_ok());
284/// ```
285pub fn patch<U: Into<String>>(url: U) -> Request<Flawless> {
286    Request::new(Flawless, "PATCH", url)
287}