1use attohttpc::{Method, RequestBuilder};
2use http::header::HeaderName;
3use serde::Deserialize;
4use serde_json::Value;
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use std::collections::HashMap;
7use std::fs::File;
8use std::time::Duration;
9
10#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
11#[repr(u16)]
12pub enum BodyType {
14 Form = 1,
16 File,
18 Auto,
23}
24
25#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
26#[repr(u16)]
27pub enum ResponseType {
29 Json = 1,
31 Text,
33 Binary,
35}
36
37#[derive(Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct HttpRequestOptions {
41 pub method: String,
43 pub url: String,
45 pub params: Option<HashMap<String, String>>,
47 pub headers: Option<HashMap<String, String>>,
49 pub body: Option<Value>,
51 pub follow_redirects: Option<bool>,
53 pub max_redirections: Option<u32>,
55 pub connect_timeout: Option<u64>,
57 pub read_timeout: Option<u64>,
59 pub timeout: Option<u64>,
61 pub allow_compression: Option<bool>,
63 pub body_type: Option<BodyType>,
65 pub response_type: Option<ResponseType>,
67}
68
69pub struct HttpRequestBuilder {
86 pub method: String,
88 pub url: String,
90 pub params: Option<HashMap<String, String>>,
92 pub headers: Option<HashMap<String, String>>,
94 pub body: Option<Value>,
96 pub follow_redirects: Option<bool>,
98 pub max_redirections: Option<u32>,
100 pub connect_timeout: Option<u64>,
102 pub read_timeout: Option<u64>,
104 pub timeout: Option<u64>,
106 pub allow_compression: Option<bool>,
108 pub body_type: Option<BodyType>,
110 pub response_type: Option<ResponseType>,
112}
113
114impl HttpRequestBuilder {
115 pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
117 Self {
118 method: method.into(),
119 url: url.into(),
120 params: None,
121 headers: None,
122 body: None,
123 follow_redirects: None,
124 max_redirections: None,
125 connect_timeout: None,
126 read_timeout: None,
127 timeout: None,
128 allow_compression: None,
129 body_type: None,
130 response_type: None,
131 }
132 }
133
134 pub fn params(mut self, params: HashMap<String, String>) -> Self {
136 self.params = Some(params);
137 self
138 }
139
140 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
142 self.headers = Some(headers);
143 self
144 }
145
146 pub fn body(mut self, body: Value) -> Self {
148 self.body = Some(body);
149 self
150 }
151
152 pub fn follow_redirects(mut self, follow_redirects: bool) -> Self {
154 self.follow_redirects = Some(follow_redirects);
155 self
156 }
157
158 pub fn max_redirections(mut self, max_redirections: u32) -> Self {
160 self.max_redirections = Some(max_redirections);
161 self
162 }
163
164 pub fn connect_timeout(mut self, connect_timeout: u64) -> Self {
166 self.connect_timeout = Some(connect_timeout);
167 self
168 }
169
170 pub fn read_timeout(mut self, read_timeout: u64) -> Self {
172 self.read_timeout = Some(read_timeout);
173 self
174 }
175
176 pub fn timeout(mut self, timeout: u64) -> Self {
178 self.timeout = Some(timeout);
179 self
180 }
181
182 pub fn allow_compression(mut self, allow_compression: bool) -> Self {
184 self.allow_compression = Some(allow_compression);
185 self
186 }
187
188 pub fn body_type(mut self, body_type: BodyType) -> Self {
190 self.body_type = Some(body_type);
191 self
192 }
193
194 pub fn response_type(mut self, response_type: ResponseType) -> Self {
196 self.response_type = Some(response_type);
197 self
198 }
199
200 pub fn build(self) -> HttpRequestOptions {
202 HttpRequestOptions {
203 method: self.method,
204 url: self.url,
205 params: self.params,
206 headers: self.headers,
207 body: self.body,
208 follow_redirects: self.follow_redirects,
209 max_redirections: self.max_redirections,
210 connect_timeout: self.connect_timeout,
211 read_timeout: self.read_timeout,
212 timeout: self.timeout,
213 allow_compression: self.allow_compression,
214 body_type: self.body_type,
215 response_type: self.response_type,
216 }
217 }
218}
219
220pub fn make_request(options: HttpRequestOptions) -> crate::Result<Value> {
225 let method = Method::from_bytes(options.method.to_uppercase().as_bytes())?;
226 let mut builder = RequestBuilder::new(method, options.url);
227 if let Some(params) = options.params {
228 for (param, param_value) in params.iter() {
229 builder = builder.param(param, param_value);
230 }
231 }
232
233 if let Some(headers) = options.headers {
234 for (header, header_value) in headers.iter() {
235 builder = builder.header(HeaderName::from_bytes(header.as_bytes())?, header_value);
236 }
237 }
238
239 if let Some(follow_redirects) = options.follow_redirects {
240 builder = builder.follow_redirects(follow_redirects);
241 }
242 if let Some(max_redirections) = options.max_redirections {
243 builder = builder.max_redirections(max_redirections);
244 }
245 if let Some(connect_timeout) = options.connect_timeout {
246 builder = builder.connect_timeout(Duration::from_secs(connect_timeout));
247 }
248 if let Some(read_timeout) = options.read_timeout {
249 builder = builder.read_timeout(Duration::from_secs(read_timeout));
250 }
251 if let Some(timeout) = options.timeout {
252 builder = builder.timeout(Duration::from_secs(timeout));
253 }
254 if let Some(allow_compression) = options.allow_compression {
255 builder = builder.allow_compression(allow_compression);
256 }
257 builder = builder
258 .danger_accept_invalid_certs(true)
259 .danger_accept_invalid_hostnames(true);
260
261 let response = if let Some(body) = options.body {
262 match options.body_type.unwrap_or(BodyType::Auto) {
263 BodyType::Form => builder.form(&body)?.send(),
264 BodyType::File => {
265 if let Some(path) = body.as_str() {
266 builder.file(File::open(path)?).send()
267 } else {
268 return Err(crate::Error::Path("Body must be the path to the file".into()).into());
269 }
270 }
271 BodyType::Auto => {
272 if body.is_object() {
273 builder.json(&body)?.send()
274 } else if let Some(text) = body.as_str() {
275 builder.text(&text).send()
276 } else if body.is_array() {
277 let u: Result<Vec<u8>, _> = serde_json::from_value(body.clone());
278 match u {
279 Ok(vec) => builder.bytes(&vec).send(),
280 Err(_) => builder.json(&body)?.send(),
281 }
282 } else {
283 builder.send()
284 }
285 }
286 }
287 } else {
288 builder.send()
289 };
290
291 let response = response?;
292 if response.is_success() {
293 let response_data = match options.response_type.unwrap_or(ResponseType::Json) {
294 ResponseType::Json => response.json::<Value>()?,
295 ResponseType::Text => Value::String(response.text()?),
296 ResponseType::Binary => Value::String(serde_json::to_string(&response.bytes()?)?),
297 };
298 Ok(response_data)
299 } else {
300 Err(crate::Error::Network(response.status()).into())
301 }
302}