mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-03 10:11:15 +02:00
281 lines
7.6 KiB
Rust
281 lines
7.6 KiB
Rust
use bytes::Bytes;
|
|
use reqwest::{header::HeaderName, redirect::Policy, Method};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
|
|
|
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
|
|
|
/// Client builder.
|
|
#[derive(Default, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ClientBuilder {
|
|
/// Max number of redirections to follow
|
|
pub max_redirections: Option<usize>,
|
|
/// Connect timeout in seconds for the request
|
|
pub connect_timeout: Option<u64>,
|
|
}
|
|
|
|
impl ClientBuilder {
|
|
/// Creates a new client builder with the default options.
|
|
pub fn new() -> Self {
|
|
Default::default()
|
|
}
|
|
|
|
/// Sets the maximum number of redirections.
|
|
pub fn max_redirections(mut self, max_redirections: usize) -> Self {
|
|
self.max_redirections = Some(max_redirections);
|
|
self
|
|
}
|
|
|
|
/// Sets the connection timeout.
|
|
pub fn connect_timeout(mut self, connect_timeout: u64) -> Self {
|
|
self.connect_timeout = Some(connect_timeout);
|
|
self
|
|
}
|
|
|
|
/// Builds the ClientOptions.
|
|
pub fn build(self) -> crate::Result<Client> {
|
|
let mut client_builder = reqwest::Client::builder();
|
|
|
|
if let Some(max_redirections) = self.max_redirections {
|
|
client_builder = client_builder.redirect(Policy::limited(max_redirections))
|
|
}
|
|
|
|
if let Some(connect_timeout) = self.connect_timeout {
|
|
client_builder = client_builder.connect_timeout(Duration::from_secs(connect_timeout));
|
|
}
|
|
|
|
let client = client_builder.build()?;
|
|
Ok(Client(client))
|
|
}
|
|
}
|
|
|
|
/// The HTTP client.
|
|
#[derive(Clone)]
|
|
pub struct Client(reqwest::Client);
|
|
|
|
impl Client {
|
|
/// Executes an HTTP request
|
|
///
|
|
/// The response will be transformed to String,
|
|
/// If reading the response as binary, the byte array will be serialized using serde_json
|
|
pub async fn send(&self, request: HttpRequestBuilder) -> crate::Result<Response> {
|
|
let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
|
|
let mut request_builder = self.0.request(method, &request.url);
|
|
|
|
if let Some(query) = request.query {
|
|
request_builder = request_builder.query(&query);
|
|
}
|
|
|
|
if let Some(headers) = request.headers {
|
|
for (header, header_value) in headers.iter() {
|
|
request_builder =
|
|
request_builder.header(HeaderName::from_bytes(header.as_bytes())?, header_value);
|
|
}
|
|
}
|
|
|
|
if let Some(timeout) = request.timeout {
|
|
request_builder = request_builder.timeout(Duration::from_secs(timeout));
|
|
}
|
|
|
|
let response = if let Some(body) = request.body {
|
|
match body {
|
|
Body::Bytes(data) => request_builder.body(Bytes::from(data)).send().await?,
|
|
Body::Text(text) => request_builder.body(Bytes::from(text)).send().await?,
|
|
Body::Json(json) => request_builder.json(&json).send().await?,
|
|
Body::Form(form_body) => {
|
|
let mut form = Vec::new();
|
|
for (name, part) in form_body.0 {
|
|
match part {
|
|
FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)),
|
|
FormPart::File(file_path) => form.push((name, serde_json::to_string(&file_path)?)),
|
|
FormPart::Text(text) => form.push((name, text)),
|
|
}
|
|
}
|
|
request_builder.form(&form).send().await?
|
|
}
|
|
}
|
|
} else {
|
|
request_builder.send().await?
|
|
};
|
|
|
|
let response = response.error_for_status()?;
|
|
Ok(Response(
|
|
request.response_type.unwrap_or(ResponseType::Json),
|
|
response,
|
|
))
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
|
|
#[repr(u16)]
|
|
/// The request's response type
|
|
pub enum ResponseType {
|
|
/// Read the response as JSON
|
|
Json = 1,
|
|
/// Read the response as text
|
|
Text,
|
|
/// Read the response as binary
|
|
Binary,
|
|
}
|
|
|
|
/// FormBody data types.
|
|
#[derive(Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum FormPart {
|
|
/// A file path value.
|
|
File(PathBuf),
|
|
/// A string value.
|
|
Text(String),
|
|
/// A byte array value.
|
|
Bytes(Vec<u8>),
|
|
}
|
|
|
|
/// Form body definition.
|
|
#[derive(Deserialize)]
|
|
pub struct FormBody(HashMap<String, FormPart>);
|
|
|
|
impl FormBody {
|
|
/// Creates a new form body.
|
|
pub fn new(data: HashMap<String, FormPart>) -> Self {
|
|
Self(data)
|
|
}
|
|
}
|
|
|
|
/// A body for the request.
|
|
#[derive(Deserialize)]
|
|
#[serde(tag = "type", content = "payload")]
|
|
pub enum Body {
|
|
/// A multipart formdata body.
|
|
Form(FormBody),
|
|
/// A JSON body.
|
|
Json(Value),
|
|
/// A text string body.
|
|
Text(String),
|
|
/// A byte array body.
|
|
Bytes(Vec<u8>),
|
|
}
|
|
|
|
/// The builder for a HTTP request.
|
|
///
|
|
/// # Examples
|
|
/// ```no_run
|
|
/// use tauri_api::http::{ HttpRequestBuilder, ResponseType, ClientBuilder };
|
|
/// async fn run() {
|
|
/// let client = ClientBuilder::new()
|
|
/// .max_redirections(3)
|
|
/// .build()
|
|
/// .unwrap();
|
|
/// let mut request_builder = HttpRequestBuilder::new("GET", "http://example.com");
|
|
/// let request = request_builder.response_type(ResponseType::Text);
|
|
///
|
|
/// if let Ok(response) = client.send(request).await {
|
|
/// println!("got response");
|
|
/// } else {
|
|
/// println!("Something Happened!");
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct HttpRequestBuilder {
|
|
/// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
|
|
pub method: String,
|
|
/// The request URL
|
|
pub url: String,
|
|
/// The request query params
|
|
pub query: Option<HashMap<String, String>>,
|
|
/// The request headers
|
|
pub headers: Option<HashMap<String, String>>,
|
|
/// The request body
|
|
pub body: Option<Body>,
|
|
/// Timeout for the whole request
|
|
pub timeout: Option<u64>,
|
|
/// The response type (defaults to Json)
|
|
pub response_type: Option<ResponseType>,
|
|
}
|
|
|
|
impl HttpRequestBuilder {
|
|
/// Initializes a new instance of the HttpRequestrequest_builder.
|
|
pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
|
|
Self {
|
|
method: method.into(),
|
|
url: url.into(),
|
|
query: None,
|
|
headers: None,
|
|
body: None,
|
|
timeout: None,
|
|
response_type: None,
|
|
}
|
|
}
|
|
|
|
/// Sets the request params.
|
|
pub fn query(mut self, query: HashMap<String, String>) -> Self {
|
|
self.query = Some(query);
|
|
self
|
|
}
|
|
|
|
/// Sets the request headers.
|
|
pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
|
|
self.headers = Some(headers);
|
|
self
|
|
}
|
|
|
|
/// Sets the request body.
|
|
pub fn body(mut self, body: Body) -> Self {
|
|
self.body = Some(body);
|
|
self
|
|
}
|
|
|
|
/// Sets the general request timeout.
|
|
pub fn timeout(mut self, timeout: u64) -> Self {
|
|
self.timeout = Some(timeout);
|
|
self
|
|
}
|
|
|
|
/// Sets the type of the response. Interferes with the way we read the response.
|
|
pub fn response_type(mut self, response_type: ResponseType) -> Self {
|
|
self.response_type = Some(response_type);
|
|
self
|
|
}
|
|
}
|
|
|
|
/// The HTTP response.
|
|
pub struct Response(ResponseType, reqwest::Response);
|
|
|
|
impl Response {
|
|
/// Reads the response and returns its info.
|
|
pub async fn read(self) -> crate::Result<ResponseData> {
|
|
let url = self.1.url().to_string();
|
|
let mut headers = HashMap::new();
|
|
for (name, value) in self.1.headers() {
|
|
headers.insert(name.as_str().to_string(), value.to_str()?.to_string());
|
|
}
|
|
let status = self.1.status().as_u16();
|
|
|
|
let data = match self.0 {
|
|
ResponseType::Json => self.1.json().await?,
|
|
ResponseType::Text => Value::String(self.1.text().await?),
|
|
ResponseType::Binary => Value::String(serde_json::to_string(&self.1.bytes().await?)?),
|
|
};
|
|
|
|
Ok(ResponseData {
|
|
url,
|
|
status,
|
|
headers,
|
|
data,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// The response type.
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ResponseData {
|
|
url: String,
|
|
status: u16,
|
|
headers: HashMap<String, String>,
|
|
data: Value,
|
|
}
|