Skip to main content

auths_core/
error.rs

1//! Error types for agent and core operations.
2
3use thiserror::Error;
4
5/// Trait for error metadata providing structured error codes and actionable suggestions.
6///
7/// All Auths error types implement this trait to provide:
8/// - A unique error code for programmatic handling (e.g., "AUTHS_KEY_NOT_FOUND")
9/// - An optional human-readable suggestion for how to resolve the error
10pub trait AuthsErrorInfo {
11    /// Returns a unique error code string following the AUTHS_* naming convention.
12    ///
13    /// Error codes are stable identifiers that can be used for:
14    /// - Programmatic error handling in scripts
15    /// - Internationalization of error messages
16    /// - Logging and debugging
17    fn error_code(&self) -> &'static str;
18
19    /// Returns an optional actionable suggestion for resolving the error.
20    ///
21    /// Suggestions should be clear, concise commands or instructions
22    /// that help the user fix the problem.
23    fn suggestion(&self) -> Option<&'static str>;
24}
25
26/// Errors from the Auths agent and core operations.
27#[derive(Debug, Error)]
28pub enum AgentError {
29    /// The requested key was not found.
30    #[error("Key not found")]
31    KeyNotFound,
32
33    /// The provided passphrase is incorrect.
34    #[error("Incorrect passphrase")]
35    IncorrectPassphrase,
36
37    /// A passphrase is required but was not provided.
38    #[error("Missing Passphrase")]
39    MissingPassphrase,
40
41    /// A platform security framework error occurred.
42    #[error("Security error: {0}")]
43    SecurityError(String),
44
45    /// A cryptographic operation failed.
46    #[error("Crypto error: {0}")]
47    CryptoError(String),
48
49    /// Failed to deserialize a key.
50    #[error("Key deserialization error: {0}")]
51    KeyDeserializationError(String),
52
53    /// Signing operation failed.
54    #[error("Signing failed: {0}")]
55    SigningFailed(String),
56
57    /// A protocol error occurred.
58    #[error("Protocol error: {0}")]
59    Proto(String),
60
61    /// An I/O error occurred.
62    #[error("IO error: {0}")]
63    IO(#[from] std::io::Error),
64
65    /// A Git operation failed.
66    #[error("git error: {0}")]
67    GitError(String),
68
69    /// Invalid input was provided.
70    #[error("Invalid input: {0}")]
71    InvalidInput(String),
72
73    /// A mutex lock was poisoned.
74    #[error("Mutex lock poisoned: {0}")]
75    MutexError(String),
76
77    /// A storage operation failed.
78    #[error("Storage error: {0}")]
79    StorageError(String),
80
81    /// The user cancelled an interactive prompt.
82    #[error("User input cancelled")]
83    UserInputCancelled,
84
85    // --- Platform backend errors ---
86    /// Backend is not available on this platform or configuration
87    #[error("Keychain backend unavailable: {backend} - {reason}")]
88    BackendUnavailable {
89        /// Name of the failing backend.
90        backend: &'static str,
91        /// Reason the backend is unavailable.
92        reason: String,
93    },
94
95    /// Storage is locked and requires authentication
96    #[error("Storage is locked, authentication required")]
97    StorageLocked,
98
99    /// Backend initialization failed
100    #[error("Failed to initialize keychain backend: {backend} - {error}")]
101    BackendInitFailed {
102        /// Name of the failing backend.
103        backend: &'static str,
104        /// Initialization error message.
105        error: String,
106    },
107
108    /// Credential size exceeds platform limit
109    #[error("Credential too large for backend (max {max_bytes} bytes, got {actual_bytes})")]
110    CredentialTooLarge {
111        /// Maximum credential size in bytes.
112        max_bytes: usize,
113        /// Actual credential size in bytes.
114        actual_bytes: usize,
115    },
116
117    /// Agent is locked due to idle timeout
118    #[error("Agent is locked. Unlock with 'auths agent unlock' or restart the agent.")]
119    AgentLocked,
120
121    /// The passphrase does not meet strength requirements.
122    #[error("Passphrase too weak: {0}")]
123    WeakPassphrase(String),
124}
125
126impl AuthsErrorInfo for AgentError {
127    fn error_code(&self) -> &'static str {
128        match self {
129            Self::KeyNotFound => "AUTHS_KEY_NOT_FOUND",
130            Self::IncorrectPassphrase => "AUTHS_INCORRECT_PASSPHRASE",
131            Self::MissingPassphrase => "AUTHS_MISSING_PASSPHRASE",
132            Self::SecurityError(_) => "AUTHS_SECURITY_ERROR",
133            Self::CryptoError(_) => "AUTHS_CRYPTO_ERROR",
134            Self::KeyDeserializationError(_) => "AUTHS_KEY_DESERIALIZATION_ERROR",
135            Self::SigningFailed(_) => "AUTHS_SIGNING_FAILED",
136            Self::Proto(_) => "AUTHS_PROTOCOL_ERROR",
137            Self::IO(_) => "AUTHS_IO_ERROR",
138            Self::GitError(_) => "AUTHS_GIT_ERROR",
139            Self::InvalidInput(_) => "AUTHS_INVALID_INPUT",
140            Self::MutexError(_) => "AUTHS_MUTEX_ERROR",
141            Self::StorageError(_) => "AUTHS_STORAGE_ERROR",
142            Self::UserInputCancelled => "AUTHS_USER_CANCELLED",
143            Self::BackendUnavailable { .. } => "AUTHS_BACKEND_UNAVAILABLE",
144            Self::StorageLocked => "AUTHS_STORAGE_LOCKED",
145            Self::BackendInitFailed { .. } => "AUTHS_BACKEND_INIT_FAILED",
146            Self::CredentialTooLarge { .. } => "AUTHS_CREDENTIAL_TOO_LARGE",
147            Self::AgentLocked => "AUTHS_AGENT_LOCKED",
148            Self::WeakPassphrase(_) => "AUTHS_WEAK_PASSPHRASE",
149        }
150    }
151
152    fn suggestion(&self) -> Option<&'static str> {
153        match self {
154            Self::KeyNotFound => Some("Run `auths key list` to see available keys"),
155            Self::IncorrectPassphrase => Some("Check your passphrase and try again"),
156            Self::MissingPassphrase => {
157                Some("Provide a passphrase with --passphrase or set AUTHS_PASSPHRASE")
158            }
159            Self::BackendUnavailable { .. } => {
160                Some("Run `auths doctor` to diagnose keychain issues")
161            }
162            Self::StorageLocked => Some("Authenticate with your platform keychain"),
163            Self::BackendInitFailed { .. } => {
164                Some("Run `auths doctor` to diagnose keychain issues")
165            }
166            Self::GitError(_) => Some("Ensure you're in a Git repository"),
167            Self::AgentLocked => {
168                Some("Run `auths agent unlock` or restart with `auths agent start`")
169            }
170            Self::UserInputCancelled => {
171                Some("Run the command again and provide the required input")
172            }
173            Self::StorageError(_) => Some("Check file permissions and disk space"),
174            // These errors typically don't have actionable suggestions
175            Self::SecurityError(_)
176            | Self::CryptoError(_)
177            | Self::KeyDeserializationError(_)
178            | Self::SigningFailed(_)
179            | Self::Proto(_)
180            | Self::IO(_)
181            | Self::InvalidInput(_)
182            | Self::MutexError(_)
183            | Self::CredentialTooLarge { .. } => None,
184            Self::WeakPassphrase(_) => {
185                Some("Use at least 12 characters with uppercase, lowercase, and a digit or symbol")
186            }
187        }
188    }
189}
190
191/// Errors from trust resolution and identity pinning.
192#[derive(Debug, Error)]
193pub enum TrustError {
194    /// An I/O error occurred.
195    #[error("I/O error: {0}")]
196    Io(#[from] std::io::Error),
197    /// Invalid data encountered (corrupt pin, bad hex, wrong format).
198    #[error("{0}")]
199    InvalidData(String),
200    /// A required resource was not found.
201    #[error("not found: {0}")]
202    NotFound(String),
203    /// JSON serialization/deserialization failed.
204    #[error("serialization error: {0}")]
205    Serialization(#[from] serde_json::Error),
206    /// Attempted to create something that already exists.
207    #[error("already exists: {0}")]
208    AlreadyExists(String),
209    /// Advisory file lock could not be acquired.
210    #[error("lock acquisition failed: {0}")]
211    Lock(String),
212    /// Trust policy rejected the identity.
213    #[error("policy rejected: {0}")]
214    PolicyRejected(String),
215}
216
217impl From<AgentError> for ssh_agent_lib::error::AgentError {
218    fn from(err: AgentError) -> Self {
219        match err {
220            AgentError::KeyNotFound => Self::Failure,
221            AgentError::IncorrectPassphrase => Self::Failure,
222            _ => Self::Failure,
223        }
224    }
225}