Skip to main content

auths_sdk/domains/identity/
error.rs

1use auths_core::error::AuthsErrorInfo;
2use thiserror::Error;
3
4/// Errors from identity setup operations (developer, CI, agent).
5///
6/// Usage:
7/// ```ignore
8/// match sdk_result {
9///     Err(SetupError::IdentityAlreadyExists { did }) => { /* reuse or abort */ }
10///     Err(e) => return Err(e.into()),
11///     Ok(result) => { /* success */ }
12/// }
13/// ```
14#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum SetupError {
17    /// An identity already exists at the configured path.
18    #[error("identity already exists: {did}")]
19    IdentityAlreadyExists {
20        /// The DID of the existing identity.
21        did: String,
22    },
23
24    /// The platform keychain is unavailable or inaccessible.
25    #[error("keychain unavailable ({backend}): {reason}")]
26    KeychainUnavailable {
27        /// The keychain backend name (e.g. "macOS Keychain").
28        backend: String,
29        /// The reason the keychain is unavailable.
30        reason: String,
31    },
32
33    /// A cryptographic operation failed.
34    #[error("crypto error: {0}")]
35    CryptoError(#[source] auths_core::AgentError),
36
37    /// A storage operation failed.
38    #[error("storage error: {0}")]
39    StorageError(#[source] crate::error::SdkStorageError),
40
41    /// Setting a git configuration key failed.
42    #[error("git config error: {0}")]
43    GitConfigError(#[source] crate::ports::git_config::GitConfigError),
44
45    /// Setup configuration parameters are invalid.
46    #[error("invalid setup config: {0}")]
47    InvalidSetupConfig(String),
48
49    /// Remote registry registration failed.
50    #[error("registration failed: {0}")]
51    RegistrationFailed(#[source] RegistrationError),
52
53    /// Platform identity verification failed.
54    #[error("platform verification failed: {0}")]
55    PlatformVerificationFailed(String),
56}
57
58/// Errors from identity rotation operations.
59///
60/// Usage:
61/// ```ignore
62/// match rotate_result {
63///     Err(RotationError::KelHistoryFailed(msg)) => { /* no prior events */ }
64///     Err(e) => return Err(e.into()),
65///     Ok(result) => { /* success */ }
66/// }
67/// ```
68#[derive(Debug, Error)]
69#[non_exhaustive]
70pub enum RotationError {
71    /// The identity was not found at the expected path.
72    #[error("identity not found at {path}")]
73    IdentityNotFound {
74        /// The filesystem path where the identity was expected.
75        path: std::path::PathBuf,
76    },
77
78    /// The requested key alias was not found in the keychain.
79    #[error("key not found: {0}")]
80    KeyNotFound(String),
81
82    /// Decrypting the key material failed (e.g. wrong passphrase).
83    #[error("key decryption failed: {0}")]
84    KeyDecryptionFailed(String),
85
86    /// Reading or validating the KEL history failed.
87    #[error("KEL history error: {0}")]
88    KelHistoryFailed(String),
89
90    /// The rotation operation failed.
91    #[error("rotation failed: {0}")]
92    RotationFailed(String),
93
94    /// KEL event was written but the new key could not be persisted to the keychain.
95    /// Recovery: re-run rotation with the same new key to replay the keychain write.
96    #[error(
97        "rotation event committed to KEL but keychain write failed — manual recovery required: {0}"
98    )]
99    PartialRotation(String),
100
101    /// Rotation requires a software-backed key but the current key is hardware-backed (SE/HSM).
102    #[error(
103        "rotation requires a software-backed key; alias '{alias}' is hardware-backed (Secure Enclave) and cannot export the raw key material rotation needs"
104    )]
105    HardwareKeyNotRotatable {
106        /// The alias whose backend refused to export.
107        alias: String,
108    },
109}
110
111/// Errors from remote registry operations.
112///
113/// Usage:
114/// ```ignore
115/// match register_result {
116///     Err(RegistrationError::AlreadyRegistered) => { /* skip */ }
117///     Err(RegistrationError::QuotaExceeded) => { /* retry later */ }
118///     Err(e) => return Err(e.into()),
119///     Ok(outcome) => { /* success */ }
120/// }
121/// ```
122#[derive(Debug, Error)]
123#[non_exhaustive]
124pub enum RegistrationError {
125    /// The identity is already registered at the target registry.
126    #[error("identity already registered at this registry")]
127    AlreadyRegistered,
128
129    /// The registration rate limit has been exceeded.
130    #[error("registration quota exceeded — try again later")]
131    QuotaExceeded,
132
133    /// A network error occurred during registration.
134    #[error("network error: {0}")]
135    NetworkError(#[source] auths_core::ports::network::NetworkError),
136
137    /// The local DID format is invalid.
138    #[error("invalid DID format: {did}")]
139    InvalidDidFormat {
140        /// The DID that failed validation.
141        did: String,
142    },
143
144    /// Loading the local identity failed.
145    #[error("identity load error: {0}")]
146    IdentityLoadError(#[source] auths_id::error::StorageError),
147
148    /// Reading from the local registry failed.
149    #[error("registry read error: {0}")]
150    RegistryReadError(#[source] auths_id::storage::registry::backend::RegistryError),
151
152    /// Serialization of identity data failed.
153    #[error("serialization error: {0}")]
154    SerializationError(#[source] serde_json::Error),
155}
156
157impl From<auths_core::AgentError> for SetupError {
158    fn from(err: auths_core::AgentError) -> Self {
159        SetupError::CryptoError(err)
160    }
161}
162
163impl From<RegistrationError> for SetupError {
164    fn from(err: RegistrationError) -> Self {
165        SetupError::RegistrationFailed(err)
166    }
167}
168
169impl From<auths_core::ports::network::NetworkError> for RegistrationError {
170    fn from(err: auths_core::ports::network::NetworkError) -> Self {
171        RegistrationError::NetworkError(err)
172    }
173}
174
175impl AuthsErrorInfo for SetupError {
176    fn error_code(&self) -> &'static str {
177        match self {
178            Self::IdentityAlreadyExists { .. } => "AUTHS-E5001",
179            Self::KeychainUnavailable { .. } => "AUTHS-E5002",
180            Self::CryptoError(e) => e.error_code(),
181            Self::StorageError(e) => e.error_code(),
182            Self::GitConfigError(_) => "AUTHS-E5004",
183            Self::InvalidSetupConfig(_) => "AUTHS-E5007",
184            Self::RegistrationFailed(e) => e.error_code(),
185            Self::PlatformVerificationFailed(_) => "AUTHS-E5006",
186        }
187    }
188
189    fn suggestion(&self) -> Option<&'static str> {
190        match self {
191            Self::IdentityAlreadyExists { .. } => {
192                Some("Use `auths id show` to inspect the existing identity")
193            }
194            Self::KeychainUnavailable { .. } => {
195                Some("Run `auths doctor` to diagnose keychain issues")
196            }
197            Self::CryptoError(e) => e.suggestion(),
198            Self::StorageError(e) => e.suggestion(),
199            Self::GitConfigError(_) => {
200                Some("Ensure Git is configured: git config --global user.name/email")
201            }
202            Self::InvalidSetupConfig(_) => Some("Check identity setup configuration parameters"),
203            Self::RegistrationFailed(e) => e.suggestion(),
204            Self::PlatformVerificationFailed(_) => Some(
205                "Platform identity verification failed; check your platform credentials and network connectivity",
206            ),
207        }
208    }
209}
210
211impl AuthsErrorInfo for RotationError {
212    fn error_code(&self) -> &'static str {
213        match self {
214            Self::IdentityNotFound { .. } => "AUTHS-E5301",
215            Self::KeyNotFound(_) => "AUTHS-E5302",
216            Self::KeyDecryptionFailed(_) => "AUTHS-E5303",
217            Self::KelHistoryFailed(_) => "AUTHS-E5304",
218            Self::RotationFailed(_) => "AUTHS-E5305",
219            Self::PartialRotation(_) => "AUTHS-E5306",
220            Self::HardwareKeyNotRotatable { .. } => "AUTHS-E5307",
221        }
222    }
223
224    fn suggestion(&self) -> Option<&'static str> {
225        match self {
226            Self::IdentityNotFound { .. } => Some("Run `auths init` to create an identity first"),
227            Self::KeyNotFound(_) => Some("Run `auths key list` to see available keys"),
228            Self::KeyDecryptionFailed(_) => Some("Check your passphrase and try again"),
229            Self::KelHistoryFailed(_) => Some("Run `auths doctor` to check KEL integrity"),
230            Self::RotationFailed(_) => Some(
231                "Key rotation failed; verify your current key is accessible with `auths key list`",
232            ),
233            Self::PartialRotation(_) => {
234                Some("Re-run the rotation with the same new key to complete the keychain write")
235            }
236            Self::HardwareKeyNotRotatable { .. } => Some(
237                "Hardware-backed keys (Secure Enclave / HSM) cannot be rotated in-place; provision a software-backed identity or rotate by creating a new identity",
238            ),
239        }
240    }
241}
242
243impl AuthsErrorInfo for RegistrationError {
244    fn error_code(&self) -> &'static str {
245        match self {
246            Self::AlreadyRegistered => "AUTHS-E5401",
247            Self::QuotaExceeded => "AUTHS-E5402",
248            Self::NetworkError(e) => e.error_code(),
249            Self::InvalidDidFormat { .. } => "AUTHS-E5403",
250            Self::IdentityLoadError(_) => "AUTHS-E5404",
251            Self::RegistryReadError(_) => "AUTHS-E5405",
252            Self::SerializationError(_) => "AUTHS-E5406",
253        }
254    }
255
256    fn suggestion(&self) -> Option<&'static str> {
257        match self {
258            Self::AlreadyRegistered => Some(
259                "This identity is already registered; use `auths id show` to see registration details",
260            ),
261            Self::QuotaExceeded => Some("Wait a few minutes and try again"),
262            Self::NetworkError(e) => e.suggestion(),
263            Self::InvalidDidFormat { .. } => {
264                Some("Run `auths doctor` to check local identity data")
265            }
266            Self::IdentityLoadError(_) => Some("Run `auths doctor` to check local identity data"),
267            Self::RegistryReadError(_) => Some("Run `auths doctor` to check local identity data"),
268            Self::SerializationError(_) => Some("Run `auths doctor` to check local identity data"),
269        }
270    }
271}