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}