tauri_plugin_http_ext/
lib.rs

1mod error;
2
3use std::collections::HashMap;
4
5use http::Method;
6use reqwest::{header::HeaderMap, Certificate, Client, Identity};
7use serde::{Deserialize, Serialize};
8use serde_json::Value as JsonValue;
9use serde_repr::Deserialize_repr;
10use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
11use tauri::{Manager, Runtime, State as TauriState, async_runtime::RwLock};
12
13use error::Result;
14
15#[tauri::command]
16async fn send(state: TauriState<'_, State>, client_name: String, request: Request) -> Result<Response> {
17    let state = state.read().await;
18    let client = state.get(&client_name).unwrap();
19
20    let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
21
22    let mut builder = client.request(method, &request.url);
23
24    if let Some(query) = request.query {
25        builder = builder.query(&query);
26    }
27
28    if let Some(headers) = request.headers {
29        builder = builder.headers(HeaderMap::try_from(&headers)?);
30    }
31
32    if let Some(body) = request.body {
33        builder = match body {
34            Body::Text(text) => builder.body(text),
35            Body::Json(json) => builder.json(&json),
36            Body::Form => builder,
37        }
38    }
39
40    let result = builder.send().await?;
41    let response = async {
42        let status = result.status().as_u16();
43
44        let mut headers: HashMap<String, String> = HashMap::new();
45        for (key, value) in result.headers() {
46            headers.insert(key.to_string(), String::from_utf8(value.as_bytes().to_vec())?);
47        }
48
49        let body: JsonValue = match request.response_type.unwrap_or(ResponseType::Json) {
50            ResponseType::Text => JsonValue::String(result.text().await?),
51            ResponseType::Json => result.json().await?,
52            ResponseType::Binary => serde_json::to_value(&*result.bytes().await?)?,
53        };
54
55        Ok(Response {
56            status,
57            headers,
58            body,
59        })
60    };
61
62    Ok((response.await as Result<Response>)?)
63}
64
65pub type Clients = HashMap<String, Client>;
66pub type State = RwLock<Clients>;
67
68pub struct Builder {
69    clients: HashMap<String, Client>,
70}
71
72impl Builder {
73    pub fn new() -> Self {
74        Self {
75            clients: HashMap::new(),
76        }
77    }
78
79    pub fn add_client(mut self, name: &str, config: ClientConfig) -> Self {
80        let mut client = Client::builder();
81
82        if let Some(tls) = config.tls {
83            client = client
84                // .tls_built_in_root_certs(false)
85                .use_rustls_tls()
86                .add_root_certificate(Certificate::from_pem(tls.cert).unwrap());
87
88            if let Some(key) = tls.key {
89                client = client.identity(Identity::from_pem(key).unwrap());
90            }
91        }
92
93        self.clients.insert(name.to_string(), client.build().unwrap());
94        self
95    }
96
97    pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
98        let plugin = PluginBuilder::new("mtls")
99            .invoke_handler(tauri::generate_handler![send])
100            .setup(
101                move |app| {
102                    app.manage(RwLock::new(self.clients.clone())); Ok(())
103                }
104            )
105            .build();
106
107        plugin
108    }
109}
110
111#[derive(Debug)]
112pub struct ClientConfig {
113    pub tls: Option<ClientTlsConfig>,
114}
115
116#[derive(Debug)]
117pub struct ClientTlsConfig {
118    pub cert: &'static [u8],
119    pub key: Option<&'static [u8]>,
120}
121
122impl ClientConfig {
123    pub fn new() -> Self {
124        Self {
125            tls: None,
126        }
127    }
128
129    pub fn tls(mut self, cert: &'static [u8], key: Option<&'static [u8]>) -> Self {
130        let tls = ClientTlsConfig {
131            cert,
132            key,
133        };
134
135        self.tls = Some(tls);
136        self
137    }
138}
139
140#[derive(Debug, Deserialize)]
141#[serde(rename_all = "camelCase")]
142struct Request {
143    pub method: String,
144    pub url: String,
145    pub query: Option<HashMap<String, String>>,
146    pub headers: Option<HashMap<String, String>>,
147    pub body: Option<Body>,
148    pub response_type: Option<ResponseType>,
149}
150
151#[derive(Debug, Serialize)]
152struct Response {
153    pub status: u16,
154    pub headers: HashMap<String, String>,
155    pub body: JsonValue,
156}
157
158#[derive(Debug, Deserialize)]
159#[serde(tag = "type", content = "payload")]
160enum Body {
161    Form,
162    Json(JsonValue),
163    Text(String),
164}
165
166#[derive(Debug, Deserialize_repr)]
167#[repr(u16)]
168enum ResponseType {
169    Json = 1,
170    Text,
171    Binary,
172}