Skip to main content

passless_core/
error.rs

1//! Error types for Passless
2//!
3//! This module defines all error types used in the Passless authenticator.
4//! We use `thiserror` for structured error handling with proper error context.
5
6use std::io;
7use std::path::PathBuf;
8
9use thiserror::Error;
10
11/// Result type alias using PasslessError
12pub type Result<T> = std::result::Result<T, Error>;
13
14/// Passless-specific errors
15///
16/// These errors represent domain and infrastructure failures.
17/// They can be converted to keylib::Error when needed for compatibility.
18#[derive(Error, Debug)]
19pub enum Error {
20    /// Storage-related errors
21    #[error("Storage error: {0}")]
22    Storage(String),
23
24    /// Configuration errors
25    #[error("Configuration error: {0}")]
26    Config(String),
27
28    /// UHID device errors
29    #[error("UHID error: {0}")]
30    Uhid(String),
31
32    /// Credential management errors
33    #[error("Credential management error: {0}")]
34    CredentialManagement(String),
35
36    /// User verification failed
37    #[error("User verification failed: {0}")]
38    UserVerificationFailed(String),
39
40    /// Operation cancelled by user
41    #[error("Operation cancelled by user")]
42    Cancelled,
43
44    /// Generic IO error
45    #[error("IO error: {0}")]
46    Io(#[from] io::Error),
47
48    /// Serialization/deserialization error
49    #[error("Serialization error: {0}")]
50    Serialization(String),
51
52    /// Invalid data format
53    #[error("Invalid data: {0}")]
54    InvalidData(String),
55
56    /// Another daemon instance is already running with the same backend state
57    #[error("another Passless instance is already using backend state at {path}")]
58    AlreadyRunning { path: PathBuf },
59
60    /// Generic other error
61    #[error("{0}")]
62    Other(String),
63}
64
65impl Error {
66    /// Format error for command-line output
67    ///
68    /// Provides clean, user-friendly error messages without Rust Debug formatting
69    pub fn format_cli(&self) -> String {
70        match self {
71            Error::Storage(msg) => format!("Storage error: {}", msg),
72            Error::Config(msg) => format!("Configuration error: {}", msg),
73            Error::Uhid(msg) => format!("UHID error: {}", msg),
74            Error::CredentialManagement(msg) => format!("Credential management error: {}", msg),
75            Error::UserVerificationFailed(msg) => format!("User verification failed: {}", msg),
76            Error::Cancelled => "Operation cancelled by user".to_string(),
77            Error::Io(err) => format!("IO error: {}", err),
78            Error::Serialization(msg) => format!("Serialization error: {}", msg),
79            Error::InvalidData(msg) => format!("Invalid data: {}", msg),
80            Error::AlreadyRunning { path } => format!(
81                "another Passless instance is already using backend state:\n\
82                 \x20 {}\n\
83                 \n\
84                 Stop the existing process or disable the duplicate systemd user service.\n\
85                 \n\
86                 To diagnose:\n\
87                 \x20 systemctl --user status passless\n\
88                 \x20 pgrep -a passless\n\
89                 \x20 journalctl --user -u passless",
90                path.display()
91            ),
92            Error::Other(msg) => msg.clone(),
93        }
94    }
95}
96
97/// Convert soft_fido2::Error to Error for error handling
98impl From<soft_fido2::Error> for Error {
99    fn from(err: soft_fido2::Error) -> Self {
100        Error::CredentialManagement(format!("{:?}", err))
101    }
102}
103
104/// Convert Error to soft_fido2::Error for compatibility
105impl From<Error> for soft_fido2::Error {
106    fn from(err: Error) -> Self {
107        match err {
108            Error::Storage(ref msg) if msg.contains("not found") => soft_fido2::Error::DoesNotExist,
109            Error::Storage(ref msg) if msg.contains("No more credentials") => {
110                soft_fido2::Error::DoesNotExist
111            }
112            _ => soft_fido2::Error::Other,
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_soft_fido2_error_conversion() {
123        let err: Error = soft_fido2::Error::DoesNotExist.into();
124        assert!(matches!(err, Error::CredentialManagement(_)));
125
126        let err: soft_fido2::Error = Error::Storage("not found".to_string()).into();
127        assert!(matches!(err, soft_fido2::Error::DoesNotExist));
128    }
129}