fix(rust): introduce dedicated downcast functions for anyhow (#10966)

The downcasting abilities of `anyhow` are pretty powerful.
Unfortunately, they can also be a bit tricky to get right. Whilst `is`
and `downcast` work fine for any errors that are within the `anyhow`
error chain, they don't check the chain of errors prior to that. In
other words, if we already have a nested `std::error::Error` with
several causes, `anyhow` cannot downcast to these causes directly.

In order to avoid this footgun, we create a thin-layer on top of the
`anyhow` crate with some downcasting functions that always try to do the
right thing.
This commit is contained in:
Thomas Eizinger
2025-11-25 15:14:17 +11:00
committed by GitHub
parent 48e0a89125
commit bcf4ccf817
20 changed files with 249 additions and 80 deletions

View File

@@ -6,7 +6,7 @@
use std::process::ExitCode;
use anyhow::{Context as _, Result, bail};
use anyhow::{Context as _, ErrorExt, Result, bail};
use clap::{Args, Parser};
use controller::Failure;
use firezone_gui_client::{controller, deep_link, elevation, gui, logging, settings};
@@ -169,28 +169,25 @@ fn try_main(
return Err(anyhow);
}
if anyhow.root_cause().is::<gui::AlreadyRunning>() {
if anyhow.any_is::<gui::AlreadyRunning>() {
return Ok(());
}
if anyhow.root_cause().is::<gui::NewInstanceHandshakeFailed>() {
if anyhow.any_is::<gui::NewInstanceHandshakeFailed>() {
show_error_dialog(
"Firezone is already running but not responding. Please force-stop it first.",
)?;
return Err(anyhow);
}
if anyhow
.root_cause()
.is::<firezone_gui_client::ipc::NotFound>()
{
if anyhow.any_is::<firezone_gui_client::ipc::NotFound>() {
show_error_dialog(
"Couldn't find Firezone Tunnel service. Is the service running?",
)?;
return Err(anyhow);
}
if anyhow.root_cause().is::<controller::FailedToReceiveHello>() {
if anyhow.any_is::<controller::FailedToReceiveHello>() {
show_error_dialog(
"The Firezone Tunnel service is not responding. If the issue persists, contact your administrator.",
)?;

View File

@@ -8,7 +8,7 @@ use crate::{
updates, uptime,
view::{GeneralSettingsForm, SessionViewModel},
};
use anyhow::{Context, Result, anyhow, bail};
use anyhow::{Context, ErrorExt as _, Result, anyhow, bail};
use connlib_model::ResourceView;
use firezone_logging::FilterReloadHandle;
use firezone_telemetry::Telemetry;
@@ -669,8 +669,7 @@ impl<I: GuiIntegration> Controller<I> {
Ok(()) => {}
Err(error)
if error
.root_cause()
.downcast_ref::<auth::Error>()
.any_downcast_ref::<auth::Error>()
.is_some_and(|e| matches!(e, auth::Error::NoInflightRequest)) =>
{
tracing::debug!("Ignoring deep-link; no local state");

View File

@@ -2,7 +2,7 @@ use crate::{
ipc::{self, SocketId},
logging,
};
use anyhow::{Context as _, Result, bail};
use anyhow::{Context as _, ErrorExt as _, Result, bail};
use atomicwrites::{AtomicFile, OverwriteBehavior};
use backoff::ExponentialBackoffBuilder;
use connlib_model::ResourceView;
@@ -399,7 +399,7 @@ impl<'a> Handler<'a> {
if let Some(e) = result
.as_ref()
.err()
.and_then(|e| e.root_cause().downcast_ref::<io::Error>())
.and_then(|e| e.any_downcast_ref::<io::Error>())
{
tracing::debug!("Still cannot connect to Firezone: {e}");
@@ -534,7 +534,7 @@ impl<'a> Handler<'a> {
if let Some(e) = result
.as_ref()
.err()
.and_then(|e| e.root_cause().downcast_ref::<io::Error>())
.and_then(|e| e.any_downcast_ref::<io::Error>())
{
tracing::debug!(
"Encountered IO error when connecting to portal, most likely we don't have Internet: {e}"