tauri_api/
http.rs

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)]
12/// The request's body type
13pub enum BodyType {
14  /// Send request body as application/x-www-form-urlencoded
15  Form = 1,
16  /// Send request body (which is a path to a file) as application/octet-stream
17  File,
18  /// Detects the body type automatically
19  /// - if the body is a byte array, send is as bytes (application/octet-stream)
20  /// - if the body is an object or array, send it as JSON (application/json with UTF-8 charset)
21  /// - if the body is a string, send it as text (text/plain with UTF-8 charset)
22  Auto,
23}
24
25#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
26#[repr(u16)]
27/// The request's response type
28pub enum ResponseType {
29  /// Read the response as JSON
30  Json = 1,
31  /// Read the response as text
32  Text,
33  /// Read the response as binary
34  Binary,
35}
36
37#[derive(Deserialize)]
38#[serde(rename_all = "camelCase")]
39/// The configuration object of an HTTP request
40pub struct HttpRequestOptions {
41  /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
42  pub method: String,
43  /// The request URL
44  pub url: String,
45  /// The request query params
46  pub params: Option<HashMap<String, String>>,
47  /// The request headers
48  pub headers: Option<HashMap<String, String>>,
49  /// The request body
50  pub body: Option<Value>,
51  /// Whether to follow redirects or not
52  pub follow_redirects: Option<bool>,
53  /// Max number of redirections to follow
54  pub max_redirections: Option<u32>,
55  /// Connect timeout for the request
56  pub connect_timeout: Option<u64>,
57  /// Read timeout for the request
58  pub read_timeout: Option<u64>,
59  /// Timeout for the whole request
60  pub timeout: Option<u64>,
61  /// Whether the request will announce that it accepts compression
62  pub allow_compression: Option<bool>,
63  /// The body type (defaults to Auto)
64  pub body_type: Option<BodyType>,
65  /// The response type (defaults to Json)
66  pub response_type: Option<ResponseType>,
67}
68
69/// The builder for HttpRequestOptions.
70///
71/// # Examples
72/// ```
73/// # use tauri_api::http::{ HttpRequestBuilder, HttpRequestOptions, make_request, ResponseType };
74/// let mut builder = HttpRequestBuilder::new("GET", "http://example.com");
75/// let option = builder.response_type(ResponseType::Text)
76///                     .follow_redirects(false)
77///                     .build();
78///
79/// if let Ok(response) = make_request(option) {
80///   println!("Response: {}", response);
81/// } else {
82///   println!("Something Happened!");
83/// }
84/// ```
85pub struct HttpRequestBuilder {
86  /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
87  pub method: String,
88  /// The request URL
89  pub url: String,
90  /// The request query params
91  pub params: Option<HashMap<String, String>>,
92  /// The request headers
93  pub headers: Option<HashMap<String, String>>,
94  /// The request body
95  pub body: Option<Value>,
96  /// Whether to follow redirects or not
97  pub follow_redirects: Option<bool>,
98  /// Max number of redirections to follow
99  pub max_redirections: Option<u32>,
100  /// Connect timeout for the request
101  pub connect_timeout: Option<u64>,
102  /// Read timeout for the request
103  pub read_timeout: Option<u64>,
104  /// Timeout for the whole request
105  pub timeout: Option<u64>,
106  /// Whether the request will announce that it accepts compression
107  pub allow_compression: Option<bool>,
108  /// The body type (defaults to Auto)
109  pub body_type: Option<BodyType>,
110  /// The response type (defaults to Json)
111  pub response_type: Option<ResponseType>,
112}
113
114impl HttpRequestBuilder {
115  /// Initializes a new instance of the HttpRequestBuilder.
116  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  /// Sets the request params.
135  pub fn params(mut self, params: HashMap<String, String>) -> Self {
136    self.params = Some(params);
137    self
138  }
139
140  /// Sets the request headers.
141  pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
142    self.headers = Some(headers);
143    self
144  }
145
146  /// Sets the request body.
147  pub fn body(mut self, body: Value) -> Self {
148    self.body = Some(body);
149    self
150  }
151
152  /// Sets whether the request should follow redirects or not.
153  pub fn follow_redirects(mut self, follow_redirects: bool) -> Self {
154    self.follow_redirects = Some(follow_redirects);
155    self
156  }
157
158  /// Sets the maximum number of redirections.
159  pub fn max_redirections(mut self, max_redirections: u32) -> Self {
160    self.max_redirections = Some(max_redirections);
161    self
162  }
163
164  /// Sets the connection timeout.
165  pub fn connect_timeout(mut self, connect_timeout: u64) -> Self {
166    self.connect_timeout = Some(connect_timeout);
167    self
168  }
169
170  /// Sets the read timeout.
171  pub fn read_timeout(mut self, read_timeout: u64) -> Self {
172    self.read_timeout = Some(read_timeout);
173    self
174  }
175
176  /// Sets the general request timeout.
177  pub fn timeout(mut self, timeout: u64) -> Self {
178    self.timeout = Some(timeout);
179    self
180  }
181
182  /// Sets whether the request allows compressed responses or not.
183  pub fn allow_compression(mut self, allow_compression: bool) -> Self {
184    self.allow_compression = Some(allow_compression);
185    self
186  }
187
188  /// Sets the type of the request body.
189  pub fn body_type(mut self, body_type: BodyType) -> Self {
190    self.body_type = Some(body_type);
191    self
192  }
193
194  /// Sets the type of the response. Interferes with the way we read the response.
195  pub fn response_type(mut self, response_type: ResponseType) -> Self {
196    self.response_type = Some(response_type);
197    self
198  }
199
200  /// Builds the HttpRequestOptions.
201  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
220/// Executes an HTTP request
221///
222/// The response will be transformed to String,
223/// If reading the response as binary, the byte array will be serialized using serde_json
224pub 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}