refactor(gui-client): improve error handling of Tauri commands (#9380)

Introducing a dedicated `Error` type allows us to directly serialise
`anyhow::Error`s to the GUI. Those will then be reported to Sentry on
the TS side.
This commit is contained in:
Thomas Eizinger
2025-06-03 23:39:04 +02:00
committed by GitHub
parent 221ffc7e58
commit 7bf98e2fdc

View File

@@ -1,7 +1,8 @@
use std::{path::PathBuf, time::Duration};
use anyhow::{Context as _, Result, bail};
use anyhow::Context as _;
use firezone_logging::err_with_src;
use serde::Serialize;
use tauri::{Wry, ipc::Invoke};
use tauri_plugin_dialog::DialogExt as _;
@@ -24,39 +25,34 @@ pub fn generate_handler() -> impl Fn(Invoke<Wry>) -> bool + Send + Sync + 'stati
}
#[tauri::command]
async fn clear_logs(managed: tauri::State<'_, Managed>) -> Result<(), String> {
async fn clear_logs(managed: tauri::State<'_, Managed>) -> Result<()> {
let (tx, rx) = tokio::sync::oneshot::channel();
if let Err(error) = managed.ctlr_tx.send(ControllerRequest::ClearLogs(tx)).await {
// Tauri will only log errors to the JS console for us, so log this ourselves.
tracing::error!(
"Error while asking `Controller` to clear logs: {}",
err_with_src(&error)
);
return Err(error.to_string());
}
if let Err(error) = rx.await {
tracing::error!(
"Error while awaiting log-clearing operation: {}",
err_with_src(&error)
);
return Err(error.to_string());
}
managed
.ctlr_tx
.send(ControllerRequest::ClearLogs(tx))
.await
.context("Failed to send `ClearLogs` command")?;
rx.await
.context("Failed to await `ClearLogs` result")?
.map_err(anyhow::Error::msg)?;
Ok(())
}
#[tauri::command]
async fn export_logs(
app: tauri::AppHandle,
managed: tauri::State<'_, Managed>,
) -> Result<(), String> {
show_export_dialog(&app, managed.ctlr_tx.clone()).map_err(|e| e.to_string())
async fn export_logs(app: tauri::AppHandle, managed: tauri::State<'_, Managed>) -> Result<()> {
show_export_dialog(&app, managed.ctlr_tx.clone())?;
Ok(())
}
#[tauri::command]
async fn apply_advanced_settings(
managed: tauri::State<'_, Managed>,
settings: AdvancedSettings,
) -> Result<(), String> {
) -> Result<()> {
if managed.inner().inject_faults {
tokio::time::sleep(Duration::from_secs(2)).await;
}
@@ -65,13 +61,13 @@ async fn apply_advanced_settings(
.ctlr_tx
.send(ControllerRequest::ApplySettings(Box::new(settings)))
.await
.map_err(|e| e.to_string())?;
.context("Failed to send `ApplySettings` command")?;
Ok(())
}
#[tauri::command]
async fn reset_advanced_settings(managed: tauri::State<'_, Managed>) -> Result<(), String> {
async fn reset_advanced_settings(managed: tauri::State<'_, Managed>) -> Result<()> {
apply_advanced_settings(managed, AdvancedSettings::default()).await?;
Ok(())
@@ -83,9 +79,9 @@ fn show_export_dialog(app: &tauri::AppHandle, ctlr_tx: CtlrTx) -> Result<()> {
let datetime_string = now.format("%Y_%m_%d-%H-%M");
let stem = PathBuf::from(format!("firezone_logs_{datetime_string}"));
let filename = stem.with_extension("zip");
let Some(filename) = filename.to_str() else {
bail!("zip filename isn't valid Unicode");
};
let filename = filename
.to_str()
.context("zip filename isn't valid Unicode")?;
tauri_plugin_dialog::FileDialogBuilder::new(app.dialog().clone())
.add_filter("Zip", &["zip"])
@@ -112,37 +108,54 @@ fn show_export_dialog(app: &tauri::AppHandle, ctlr_tx: CtlrTx) -> Result<()> {
}
#[tauri::command]
async fn sign_in(managed: tauri::State<'_, Managed>) -> Result<(), String> {
async fn sign_in(managed: tauri::State<'_, Managed>) -> Result<()> {
managed
.ctlr_tx
.send(ControllerRequest::SignIn)
.await
.context("Failed to send `SignIn` command")
.map_err(|e| e.to_string())?;
.context("Failed to send `SignIn` command")?;
Ok(())
}
#[tauri::command]
async fn sign_out(managed: tauri::State<'_, Managed>) -> Result<(), String> {
async fn sign_out(managed: tauri::State<'_, Managed>) -> Result<()> {
managed
.ctlr_tx
.send(ControllerRequest::SignOut)
.await
.context("Failed to send `SignOut` command")
.map_err(|e| e.to_string())?;
.context("Failed to send `SignOut` command")?;
Ok(())
}
#[tauri::command]
async fn update_state(managed: tauri::State<'_, Managed>) -> Result<(), String> {
async fn update_state(managed: tauri::State<'_, Managed>) -> Result<()> {
managed
.ctlr_tx
.send(ControllerRequest::UpdateState)
.await
.context("Failed to send `UpdateState` command")
.map_err(|e| e.to_string())?;
.context("Failed to send `UpdateState` command")?;
Ok(())
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
struct Error(anyhow::Error);
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{:#}", self.0))
}
}
impl From<anyhow::Error> for Error {
fn from(value: anyhow::Error) -> Self {
Self(value)
}
}