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    /// Database error
19    #[error("Database error: {0}")]
20    Database(String),
21
22    /// Authentication error
23    #[error("Authentication error: {0}")]
24    Authentication(String),
25
26    /// Session mismatch error - when user in session differs from user in context
27    #[error("Session mismatch: {0}")]
28    SessionMismatch(String),
29
30    /// Missing context token error
31    #[error("Context token is missing")]
32    MissingContextToken,
33
34    /// Unauthorized access error
35    #[error("Unauthorized access")]
36    Unauthorized,
37
38    /// Unexpectedly authorized access error
39    #[error("You are already authenticated")]
40    UnexpectedlyAuthorized,
41
42    /// No content error
43    #[error("No content")]
44    NoContent,
45
46    /// Invalid mode
47    #[error("Invalid mode")]
48    InvalidMode,
49
50    /// Conflict error
51    #[error("Conflict: {0}")]
52    Conflict(String),
53
54    /// Invalid state error
55    #[error("Invalid state: {0}")]
56    InvalidState(String),
57
58    /// Resource not found with context
59    #[error("Resource not found: {resource_type} {resource_id}")]
60    ResourceNotFound {
61        /// The type of resource that wasn't found (e.g., "user", "credential", etc.)
62        resource_type: String,
63        /// The identifier of the resource that wasn't found
64        resource_id: String,
65    },
66
67    /// Error from the user database operations
68    #[error("User error: {0}")]
69    UserError(UserError),
70
71    /// Error from OAuth2 operations
72    #[error("OAuth2 error: {0}")]
73    OAuth2Error(OAuth2Error),
74
75    /// Error from Passkey operations
76    #[error("Passkey error: {0}")]
77    PasskeyError(PasskeyError),
78
79    /// Error from Session operations
80    #[error("Session error: {0}")]
81    SessionError(SessionError),
82
83    /// Error from utils operations
84    #[error("Utils error: {0}")]
85    UtilsError(UtilError),
86
87    /// Invalid response mode
88    #[error("Invalid response mode: {0}")]
89    InvalidResponseMode(String),
90}
91
92impl CoordinationError {
93    /// Log the error and return self
94    ///
95    /// This method logs the error with appropriate context and returns self,
96    /// allowing for method chaining and explicit logging when needed.
97    ///
98    pub fn log(self) -> Self {
99        match &self {
100            Self::Coordination(msg) => tracing::error!("Coordination error: {}", msg),
101            Self::Database(msg) => tracing::error!("Database error: {}", msg),
102            Self::Authentication(msg) => tracing::error!("Authentication error: {}", msg),
103            Self::SessionMismatch(msg) => tracing::error!("Session mismatch: {}", msg),
104            Self::MissingContextToken => tracing::error!("Context token is missing"),
105            Self::Unauthorized => tracing::error!("Unauthorized access"),
106            Self::UnexpectedlyAuthorized => tracing::error!("Unexpectedly authorized access"),
107            Self::NoContent => tracing::error!("No content"),
108            Self::InvalidMode => tracing::error!("Invalid mode"),
109            Self::Conflict(message) => tracing::error!("Conflict: {}", message),
110            Self::InvalidState(message) => tracing::error!("Invalid state: {}", message),
111            Self::ResourceNotFound {
112                resource_type,
113                resource_id,
114            } => tracing::error!("Resource not found: {} {}", resource_type, resource_id),
115            Self::UserError(err) => tracing::error!("User error: {}", err),
116            Self::OAuth2Error(err) => tracing::error!("OAuth2 error: {}", err),
117            Self::PasskeyError(err) => tracing::error!("Passkey error: {}", err),
118            Self::SessionError(err) => tracing::error!("Session error: {}", err),
119            Self::UtilsError(err) => tracing::error!("Utils error: {}", err),
120            Self::InvalidResponseMode(message) => {
121                tracing::error!("Invalid response mode: {}", message)
122            }
123        }
124        self
125    }
126}
127
128// Custom From implementations that automatically log errors
129
130impl From<OAuth2Error> for CoordinationError {
131    fn from(err: OAuth2Error) -> Self {
132        let error = Self::OAuth2Error(err);
133        tracing::error!("{}", error);
134        error
135    }
136}
137
138impl From<PasskeyError> for CoordinationError {
139    fn from(err: PasskeyError) -> Self {
140        let error = Self::PasskeyError(err);
141        tracing::error!("{}", error);
142        error
143    }
144}
145
146impl From<SessionError> for CoordinationError {
147    fn from(err: SessionError) -> Self {
148        let error = Self::SessionError(err);
149        tracing::error!("{}", error);
150        error
151    }
152}
153
154impl From<UserError> for CoordinationError {
155    fn from(err: UserError) -> Self {
156        let error = Self::UserError(err);
157        tracing::error!("{}", error);
158        error
159    }
160}
161
162impl From<UtilError> for CoordinationError {
163    fn from(err: UtilError) -> Self {
164        let error = Self::UtilsError(err);
165        tracing::error!("{}", error);
166        error
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_error_is_sync_and_send() {
176        fn assert_sync_send<T: Sync + Send>() {}
177        assert_sync_send::<CoordinationError>();
178    }
179
180    #[test]
181    fn test_error_log() {
182        // Test that the log method returns self and works correctly
183        let err = CoordinationError::Coordination("test error".to_string());
184        let logged_err = err.log();
185
186        if let CoordinationError::Coordination(msg) = logged_err {
187            assert_eq!(msg, "test error");
188        } else {
189            panic!("Wrong error type after logging");
190        }
191    }
192}