Skip to main content

quincy_gui/gui/
error.rs

1//! Error types for the Quincy GUI.
2//!
3//! This module defines structured error types using thiserror for better
4//! error handling and propagation through IPC and state machine.
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9/// GUI-specific error type that can be serialized for IPC communication.
10///
11/// This error type is used throughout the GUI to represent various error
12/// conditions that can occur during VPN operations. It is designed to be
13/// both human-readable and machine-parseable.
14#[derive(Debug, Clone, Error, Serialize, Deserialize)]
15pub enum GuiError {
16    /// Authentication with the VPN server failed.
17    #[error("Authentication failed: {reason}")]
18    AuthFailed { reason: String },
19
20    /// Network is unreachable or connection failed.
21    #[error("Network unreachable: {details}")]
22    NetworkUnreachable { details: String },
23
24    /// Configuration is invalid or malformed.
25    #[error("Configuration invalid: {field} - {reason}")]
26    ConfigInvalid { field: String, reason: String },
27
28    /// Connection was closed by the server or daemon.
29    #[error("Connection closed: {reason}")]
30    ConnectionClosed { reason: String },
31
32    /// IPC communication error.
33    #[error("IPC error: {0}")]
34    Ipc(String),
35
36    /// Daemon process error.
37    #[error("Daemon error: {0}")]
38    Daemon(String),
39
40    /// Timeout waiting for connection or operation.
41    #[error("Timeout: {0}")]
42    Timeout(String),
43
44    /// Permission denied or elevation required.
45    #[error("Permission denied: {0}")]
46    PermissionDenied(String),
47
48    /// Generic or unknown error.
49    #[error("{0}")]
50    Other(String),
51}
52
53impl GuiError {
54    /// Creates an authentication error.
55    pub fn auth_failed(reason: impl Into<String>) -> Self {
56        Self::AuthFailed {
57            reason: reason.into(),
58        }
59    }
60
61    /// Creates a network unreachable error.
62    pub fn network_unreachable(details: impl Into<String>) -> Self {
63        Self::NetworkUnreachable {
64            details: details.into(),
65        }
66    }
67
68    /// Creates a configuration invalid error.
69    pub fn config_invalid(field: impl Into<String>, reason: impl Into<String>) -> Self {
70        Self::ConfigInvalid {
71            field: field.into(),
72            reason: reason.into(),
73        }
74    }
75
76    /// Creates a connection closed error.
77    pub fn connection_closed(reason: impl Into<String>) -> Self {
78        Self::ConnectionClosed {
79            reason: reason.into(),
80        }
81    }
82
83    /// Creates an IPC error.
84    pub fn ipc(msg: impl Into<String>) -> Self {
85        Self::Ipc(msg.into())
86    }
87
88    /// Creates a daemon error.
89    pub fn daemon(msg: impl Into<String>) -> Self {
90        Self::Daemon(msg.into())
91    }
92
93    /// Creates a timeout error.
94    pub fn timeout(msg: impl Into<String>) -> Self {
95        Self::Timeout(msg.into())
96    }
97
98    /// Creates a permission denied error.
99    pub fn permission_denied(msg: impl Into<String>) -> Self {
100        Self::PermissionDenied(msg.into())
101    }
102
103    /// Creates a generic error.
104    pub fn other(msg: impl Into<String>) -> Self {
105        Self::Other(msg.into())
106    }
107}
108
109/// Conversion from standard errors to GuiError.
110impl From<std::io::Error> for GuiError {
111    fn from(err: std::io::Error) -> Self {
112        match err.kind() {
113            std::io::ErrorKind::PermissionDenied => Self::permission_denied(err.to_string()),
114            std::io::ErrorKind::TimedOut => Self::timeout(err.to_string()),
115            std::io::ErrorKind::ConnectionRefused
116            | std::io::ErrorKind::ConnectionReset
117            | std::io::ErrorKind::ConnectionAborted => Self::connection_closed(err.to_string()),
118            _ => Self::other(err.to_string()),
119        }
120    }
121}
122
123/// Conversion from quincy::QuincyError to GuiError.
124///
125/// Maps QuincyError variants to appropriate GuiError variants where possible,
126/// preserving the original error message without adding redundant prefixes.
127impl From<quincy::QuincyError> for GuiError {
128    fn from(err: quincy::QuincyError) -> Self {
129        // Use the error's display message directly without wrapping
130        // QuincyError already has well-formatted user-facing messages
131        Self::Other(err.to_string())
132    }
133}
134
135/// Conversion from serde_json errors to GuiError.
136impl From<serde_json::Error> for GuiError {
137    fn from(err: serde_json::Error) -> Self {
138        Self::Other(format!("JSON error: {}", err))
139    }
140}