feat(gui): error helper to classify zbus errors and produce user messages

This commit is contained in:
michael
2026-04-27 09:11:06 -07:00
parent 8d6b80276e
commit d7e6d562dd
2 changed files with 89 additions and 0 deletions

87
gui/src/error.rs Normal file
View File

@@ -0,0 +1,87 @@
//! Map zbus errors to short, user-facing strings. The GUI surfaces these in
//! `adw::Toast` and inside the touch-prompt dialog's failure state.
//!
//! Pure logic; testable without a display server.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] // wired through keys_page.rs / enroll_dialog.rs in Tasks 4-6.
pub(crate) enum ErrorKind {
/// Daemon unreachable / bus dropped — caller should swap to disconnected banner.
Disconnected,
/// polkit denied. Daemon's message is usually fine to show verbatim.
AccessDenied,
/// Anything else — daemon returned a typed `Failed(msg)` or similar.
Other,
}
#[allow(dead_code)] // wired through keys_page.rs / enroll_dialog.rs in Tasks 4-6.
pub(crate) fn classify(err: &zbus::Error) -> ErrorKind {
match err {
zbus::Error::InputOutput(_) | zbus::Error::Address(_) | zbus::Error::Handshake(_) => {
ErrorKind::Disconnected
}
zbus::Error::FDO(boxed) => match boxed.as_ref() {
zbus::fdo::Error::AccessDenied(_) => ErrorKind::AccessDenied,
zbus::fdo::Error::ServiceUnknown(_) | zbus::fdo::Error::NameHasNoOwner(_) => {
ErrorKind::Disconnected
}
_ => ErrorKind::Other,
},
_ => ErrorKind::Other,
}
}
#[allow(dead_code)] // wired through keys_page.rs / enroll_dialog.rs in Tasks 4-6.
pub(crate) fn user_message(err: &zbus::Error) -> String {
match classify(err) {
ErrorKind::Disconnected => {
"Couldn't reach the authentication daemon. Make sure authforge-daemon is running."
.to_string()
}
ErrorKind::AccessDenied => match err {
zbus::Error::FDO(boxed) => match boxed.as_ref() {
zbus::fdo::Error::AccessDenied(msg) => msg.clone(),
_ => "Access denied.".to_string(),
},
_ => "Access denied.".to_string(),
},
ErrorKind::Other => err.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn service_unknown_classifies_as_disconnected() {
let e = zbus::Error::FDO(Box::new(zbus::fdo::Error::ServiceUnknown(
"no daemon".to_string(),
)));
assert_eq!(classify(&e), ErrorKind::Disconnected);
}
#[test]
fn access_denied_classifies_as_access_denied() {
let e = zbus::Error::FDO(Box::new(zbus::fdo::Error::AccessDenied(
"polkit said no".to_string(),
)));
assert_eq!(classify(&e), ErrorKind::AccessDenied);
}
#[test]
fn access_denied_user_message_is_daemon_message_verbatim() {
let e = zbus::Error::FDO(Box::new(zbus::fdo::Error::AccessDenied(
"polkit said no".to_string(),
)));
assert_eq!(user_message(&e), "polkit said no");
}
#[test]
fn disconnected_user_message_mentions_authforge_daemon() {
let e = zbus::Error::FDO(Box::new(zbus::fdo::Error::ServiceUnknown(
"no daemon".to_string(),
)));
assert!(user_message(&e).contains("authforge-daemon"));
}
}

View File

@@ -1,6 +1,8 @@
use adw::prelude::*; use adw::prelude::*;
use gtk::glib; use gtk::glib;
mod error;
const APP_ID: &str = "io.dangerousthings.AuthForge"; const APP_ID: &str = "io.dangerousthings.AuthForge";
fn main() -> glib::ExitCode { fn main() -> glib::ExitCode {