oauth2_passkey/coordination/
errors.rs

1//! Error types for the libauth crate
2
3use thiserror::Error;
4
5use crate::oauth2::OAuth2Error;
6use crate::passkey::PasskeyError;
7use crate::session::SessionError;
8use crate::userdb::UserError;
9use crate::utils::UtilError;
10
11/// Errors that can occur during authentication coordination
12#[derive(Error, Debug)]
13pub enum CoordinationError {
14    /// General coordination error
15    #[error("Coordination error: {0}")]
16    Coordination(String),
17
18    /// Validation error
19    #[error("Validation error: {0}")]
20    Validation(String),
21
22    /// Internal error
23    #[error("Internal error: {0}")]
24    Internal(String),
25
26    /// Database error
27    #[error("Database error: {0}")]
28    Database(String),
29
30    /// Authentication error
31    #[error("Authentication error: {0}")]
32    Authentication(String),
33
34    /// Session mismatch error - when user in session differs from user in context
35    #[error("Session mismatch: {0}")]
36    SessionMismatch(String),
37
38    /// Missing context token error
39    #[error("Context token is missing")]
40    MissingContextToken,
41
42    /// Unauthorized access error
43    #[error("Unauthorized access")]
44    Unauthorized,
45
46    /// Unexpectedly authorized access error
47    #[error("You are already authenticated")]
48    UnexpectedlyAuthorized,
49
50    /// No content error
51    #[error("No content")]
52    NoContent,
53
54    /// Invalid mode
55    #[error("Invalid mode")]
56    InvalidMode,
57
58    /// Conflict error
59    #[error("Conflict: {0}")]
60    Conflict(String),
61
62    /// Invalid state error
63    #[error("Invalid state: {0}")]
64    InvalidState(String),
65
66    /// Resource not found with context
67    #[error("Resource not found: {resource_type} {resource_id}")]
68    ResourceNotFound {
69        /// The type of resource that wasn't found (e.g., "user", "credential", etc.)
70        resource_type: String,
71        /// The identifier of the resource that wasn't found
72        resource_id: String,
73    },
74
75    /// Error from the user database operations
76    #[error("User error: {0}")]
77    UserError(UserError),
78
79    /// Error from OAuth2 operations
80    #[error("OAuth2 error: {0}")]
81    OAuth2Error(OAuth2Error),
82
83    /// Error from Passkey operations
84    #[error("Passkey error: {0}")]
85    PasskeyError(PasskeyError),
86
87    /// Error from Session operations
88    #[error("Session error: {0}")]
89    SessionError(SessionError),
90
91    /// Error from utils operations
92    #[error("Utils error: {0}")]
93    UtilsError(UtilError),
94
95    /// Invalid response mode
96    #[error("Invalid response mode: {0}")]
97    InvalidResponseMode(String),
98}
99
100impl CoordinationError {
101    /// Log the error and return self
102    ///
103    /// This method logs the error with appropriate context and returns self,
104    /// allowing for method chaining and explicit logging when needed.
105    ///
106    pub fn log(self) -> Self {
107        match &self {
108            Self::Coordination(msg) => tracing::error!("Coordination error: {}", msg),
109            Self::Validation(msg) => tracing::error!("Validation error: {}", msg),
110            Self::Internal(msg) => tracing::error!("Internal error: {}", msg),
111            Self::Database(msg) => tracing::error!("Database error: {}", msg),
112            Self::Authentication(msg) => tracing::error!("Authentication error: {}", msg),
113            Self::SessionMismatch(msg) => tracing::error!("Session mismatch: {}", msg),
114            Self::MissingContextToken => tracing::error!("Context token is missing"),
115            Self::Unauthorized => tracing::error!("Unauthorized access"),
116            Self::UnexpectedlyAuthorized => tracing::error!("Unexpectedly authorized access"),
117            Self::NoContent => tracing::error!("No content"),
118            Self::InvalidMode => tracing::error!("Invalid mode"),
119            Self::Conflict(message) => tracing::error!("Conflict: {}", message),
120            Self::InvalidState(message) => tracing::error!("Invalid state: {}", message),
121            Self::ResourceNotFound {
122                resource_type,
123                resource_id,
124            } => tracing::error!("Resource not found: {} {}", resource_type, resource_id),
125            Self::UserError(err) => tracing::error!("User error: {}", err),
126            Self::OAuth2Error(err) => tracing::error!("OAuth2 error: {}", err),
127            Self::PasskeyError(err) => tracing::error!("Passkey error: {}", err),
128            Self::SessionError(err) => tracing::error!("Session error: {}", err),
129            Self::UtilsError(err) => tracing::error!("Utils error: {}", err),
130            Self::InvalidResponseMode(message) => {
131                tracing::error!("Invalid response mode: {}", message)
132            }
133        }
134        self
135    }
136
137    /// Log the error with enhanced context including spans and return self
138    ///
139    /// This method provides structured error logging with additional context
140    /// from the current tracing span, making debugging easier in production.
141    pub fn log_with_context(self) -> Self {
142        match &self {
143            Self::Coordination(msg) => {
144                tracing::error!(error = %self, message = %msg, "Coordination error with context");
145            }
146            Self::Validation(msg) => {
147                tracing::error!(error = %self, message = %msg, "Validation error with context");
148            }
149            Self::Internal(msg) => {
150                tracing::error!(error = %self, message = %msg, "Internal error with context");
151            }
152            Self::Database(msg) => {
153                tracing::error!(error = %self, message = %msg, "Database error with context");
154            }
155            Self::Authentication(msg) => {
156                tracing::error!(error = %self, message = %msg, "Authentication error with context");
157            }
158            Self::SessionMismatch(msg) => {
159                tracing::error!(error = %self, message = %msg, "Session mismatch with context");
160            }
161            Self::MissingContextToken => {
162                tracing::error!(error = %self, "Context token missing with span context");
163            }
164            Self::Unauthorized => {
165                tracing::error!(error = %self, "Unauthorized access with span context");
166            }
167            Self::UnexpectedlyAuthorized => {
168                tracing::error!(error = %self, "Unexpectedly authorized with span context");
169            }
170            Self::NoContent => {
171                tracing::error!(error = %self, "No content with span context");
172            }
173            Self::InvalidMode => {
174                tracing::error!(error = %self, "Invalid mode with span context");
175            }
176            Self::InvalidState(msg) => {
177                tracing::error!(error = %self, message = %msg, "Invalid state with context");
178            }
179            Self::Conflict(msg) => {
180                tracing::error!(error = %self, message = %msg, "Conflict with context");
181            }
182            Self::ResourceNotFound {
183                resource_type,
184                resource_id,
185            } => {
186                tracing::error!(
187                    error = %self,
188                    resource_type = %resource_type,
189                    resource_id = %resource_id,
190                    "Resource not found with context"
191                );
192            }
193            // For wrapped errors, include the source error context
194            Self::UserError(err) => {
195                tracing::error!(error = %self, source_error = %err, "User error with context");
196            }
197            Self::OAuth2Error(err) => {
198                tracing::error!(error = %self, source_error = %err, "OAuth2 error with context");
199            }
200            Self::PasskeyError(err) => {
201                tracing::error!(error = %self, source_error = %err, "Passkey error with context");
202            }
203            Self::SessionError(err) => {
204                tracing::error!(error = %self, source_error = %err, "Session error with context");
205            }
206            Self::UtilsError(err) => {
207                tracing::error!(error = %self, source_error = %err, "Utils error with context");
208            }
209            Self::InvalidResponseMode(msg) => {
210                tracing::error!(error = %self, message = %msg, "Invalid response mode with context");
211            }
212        }
213        self
214    }
215
216    /// Create error with full debugging context including backtraces
217    ///
218    /// This method captures the current span context and any available
219    /// error backtrace information for comprehensive error debugging.
220    pub fn with_span_context(self) -> Self {
221        // If tracing-error is properly configured, this will include span context
222        if tracing::log::log_enabled!(tracing::log::Level::Error) {
223            let span_name = tracing::Span::current()
224                .metadata()
225                .map(|m| m.name())
226                .unwrap_or("unknown");
227            tracing::error!(
228                error = %self,
229                span_context = span_name,
230                "Error with full span context captured"
231            );
232        } else {
233            tracing::error!(
234                error = %self,
235                "Error captured without span context"
236            );
237        }
238        self
239    }
240}
241
242// Custom From implementations that automatically log errors
243
244impl From<OAuth2Error> for CoordinationError {
245    fn from(err: OAuth2Error) -> Self {
246        let error = Self::OAuth2Error(err);
247        tracing::error!("{}", error);
248        error
249    }
250}
251
252impl From<PasskeyError> for CoordinationError {
253    fn from(err: PasskeyError) -> Self {
254        let error = Self::PasskeyError(err);
255        tracing::error!("{}", error);
256        error
257    }
258}
259
260impl From<SessionError> for CoordinationError {
261    fn from(err: SessionError) -> Self {
262        let error = Self::SessionError(err);
263        tracing::error!("{}", error);
264        error
265    }
266}
267
268impl From<UserError> for CoordinationError {
269    fn from(err: UserError) -> Self {
270        let error = Self::UserError(err);
271        tracing::error!("{}", error);
272        error
273    }
274}
275
276impl From<UtilError> for CoordinationError {
277    fn from(err: UtilError) -> Self {
278        let error = Self::UtilsError(err);
279        tracing::error!("{}", error);
280        error
281    }
282}
283
284#[cfg(test)]
285mod tests;