Skip to main content

mockforge_collab/
error.rs

1//! Error types for collaboration features
2
3/// Result type for collaboration operations
4pub type Result<T> = std::result::Result<T, CollabError>;
5
6/// Errors that can occur during collaboration operations
7#[derive(Debug, thiserror::Error)]
8pub enum CollabError {
9    /// Authentication failed
10    #[error("Authentication failed: {0}")]
11    AuthenticationFailed(String),
12
13    /// Authorization failed (insufficient permissions)
14    #[error("Authorization failed: {0}")]
15    AuthorizationFailed(String),
16
17    /// Workspace not found
18    #[error("Workspace not found: {0}")]
19    WorkspaceNotFound(String),
20
21    /// User not found
22    #[error("User not found: {0}")]
23    UserNotFound(String),
24
25    /// Conflict detected
26    #[error("Conflict detected: {0}")]
27    ConflictDetected(String),
28
29    /// Sync error
30    #[error("Sync error: {0}")]
31    SyncError(String),
32
33    /// Database error
34    #[error("Database error: {0}")]
35    DatabaseError(String),
36
37    /// WebSocket error
38    #[error("WebSocket error: {0}")]
39    WebSocketError(String),
40
41    /// Serialization error
42    #[error("Serialization error: {0}")]
43    SerializationError(String),
44
45    /// Invalid input
46    #[error("Invalid input: {0}")]
47    InvalidInput(String),
48
49    /// Resource already exists
50    #[error("Resource already exists: {0}")]
51    AlreadyExists(String),
52
53    /// Operation timeout
54    #[error("Operation timeout: {0}")]
55    Timeout(String),
56
57    /// Connection error
58    #[error("Connection error: {0}")]
59    ConnectionError(String),
60
61    /// Version mismatch
62    #[error("Version mismatch: expected {expected}, got {actual}")]
63    VersionMismatch {
64        /// Expected version
65        expected: u64,
66        /// Actual version
67        actual: u64,
68    },
69
70    /// Internal error
71    #[error("Internal error: {0}")]
72    Internal(String),
73}
74
75impl From<sqlx::Error> for CollabError {
76    fn from(err: sqlx::Error) -> Self {
77        Self::DatabaseError(err.to_string())
78    }
79}
80
81impl From<serde_json::Error> for CollabError {
82    fn from(err: serde_json::Error) -> Self {
83        Self::SerializationError(err.to_string())
84    }
85}
86
87impl From<tokio::time::error::Elapsed> for CollabError {
88    fn from(err: tokio::time::error::Elapsed) -> Self {
89        Self::Timeout(err.to_string())
90    }
91}
92
93impl From<sqlx::migrate::MigrateError> for CollabError {
94    fn from(err: sqlx::migrate::MigrateError) -> Self {
95        Self::DatabaseError(err.to_string())
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_authentication_failed() {
105        let err = CollabError::AuthenticationFailed("invalid token".to_string());
106        let msg = err.to_string();
107        assert!(msg.contains("Authentication failed"));
108        assert!(msg.contains("invalid token"));
109    }
110
111    #[test]
112    fn test_authorization_failed() {
113        let err = CollabError::AuthorizationFailed("missing permission".to_string());
114        let msg = err.to_string();
115        assert!(msg.contains("Authorization failed"));
116        assert!(msg.contains("missing permission"));
117    }
118
119    #[test]
120    fn test_workspace_not_found() {
121        let err = CollabError::WorkspaceNotFound("ws-123".to_string());
122        let msg = err.to_string();
123        assert!(msg.contains("Workspace not found"));
124        assert!(msg.contains("ws-123"));
125    }
126
127    #[test]
128    fn test_user_not_found() {
129        let err = CollabError::UserNotFound("user-456".to_string());
130        let msg = err.to_string();
131        assert!(msg.contains("User not found"));
132        assert!(msg.contains("user-456"));
133    }
134
135    #[test]
136    fn test_conflict_detected() {
137        let err = CollabError::ConflictDetected("concurrent edit".to_string());
138        let msg = err.to_string();
139        assert!(msg.contains("Conflict detected"));
140        assert!(msg.contains("concurrent edit"));
141    }
142
143    #[test]
144    fn test_sync_error() {
145        let err = CollabError::SyncError("sync failed".to_string());
146        let msg = err.to_string();
147        assert!(msg.contains("Sync error"));
148        assert!(msg.contains("sync failed"));
149    }
150
151    #[test]
152    fn test_database_error() {
153        let err = CollabError::DatabaseError("connection refused".to_string());
154        let msg = err.to_string();
155        assert!(msg.contains("Database error"));
156        assert!(msg.contains("connection refused"));
157    }
158
159    #[test]
160    fn test_websocket_error() {
161        let err = CollabError::WebSocketError("connection closed".to_string());
162        let msg = err.to_string();
163        assert!(msg.contains("WebSocket error"));
164        assert!(msg.contains("connection closed"));
165    }
166
167    #[test]
168    fn test_serialization_error() {
169        let err = CollabError::SerializationError("invalid json".to_string());
170        let msg = err.to_string();
171        assert!(msg.contains("Serialization error"));
172        assert!(msg.contains("invalid json"));
173    }
174
175    #[test]
176    fn test_invalid_input() {
177        let err = CollabError::InvalidInput("empty name".to_string());
178        let msg = err.to_string();
179        assert!(msg.contains("Invalid input"));
180        assert!(msg.contains("empty name"));
181    }
182
183    #[test]
184    fn test_already_exists() {
185        let err = CollabError::AlreadyExists("workspace-name".to_string());
186        let msg = err.to_string();
187        assert!(msg.contains("already exists"));
188        assert!(msg.contains("workspace-name"));
189    }
190
191    #[test]
192    fn test_timeout() {
193        let err = CollabError::Timeout("operation timed out".to_string());
194        let msg = err.to_string();
195        assert!(msg.contains("timeout"));
196        assert!(msg.contains("operation timed out"));
197    }
198
199    #[test]
200    fn test_connection_error() {
201        let err = CollabError::ConnectionError("host unreachable".to_string());
202        let msg = err.to_string();
203        assert!(msg.contains("Connection error"));
204        assert!(msg.contains("host unreachable"));
205    }
206
207    #[test]
208    fn test_version_mismatch() {
209        let err = CollabError::VersionMismatch {
210            expected: 10,
211            actual: 8,
212        };
213        let msg = err.to_string();
214        assert!(msg.contains("Version mismatch"));
215        assert!(msg.contains("10"));
216        assert!(msg.contains('8'));
217    }
218
219    #[test]
220    fn test_internal_error() {
221        let err = CollabError::Internal("unexpected failure".to_string());
222        let msg = err.to_string();
223        assert!(msg.contains("Internal error"));
224        assert!(msg.contains("unexpected failure"));
225    }
226
227    #[test]
228    fn test_from_serde_json_error() {
229        let json_err: serde_json::Error = serde_json::from_str::<String>("invalid").unwrap_err();
230        let err: CollabError = json_err.into();
231        assert!(matches!(err, CollabError::SerializationError(_)));
232    }
233
234    #[test]
235    fn test_error_debug() {
236        let err = CollabError::AuthenticationFailed("test".to_string());
237        let debug = format!("{err:?}");
238        assert!(debug.contains("AuthenticationFailed"));
239    }
240}