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 { expected: u64, actual: u64 },
64
65    /// Internal error
66    #[error("Internal error: {0}")]
67    Internal(String),
68}
69
70impl From<sqlx::Error> for CollabError {
71    fn from(err: sqlx::Error) -> Self {
72        Self::DatabaseError(err.to_string())
73    }
74}
75
76impl From<serde_json::Error> for CollabError {
77    fn from(err: serde_json::Error) -> Self {
78        Self::SerializationError(err.to_string())
79    }
80}
81
82impl From<tokio::time::error::Elapsed> for CollabError {
83    fn from(err: tokio::time::error::Elapsed) -> Self {
84        Self::Timeout(err.to_string())
85    }
86}
87
88impl From<sqlx::migrate::MigrateError> for CollabError {
89    fn from(err: sqlx::migrate::MigrateError) -> Self {
90        Self::DatabaseError(err.to_string())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_authentication_failed() {
100        let err = CollabError::AuthenticationFailed("invalid token".to_string());
101        let msg = err.to_string();
102        assert!(msg.contains("Authentication failed"));
103        assert!(msg.contains("invalid token"));
104    }
105
106    #[test]
107    fn test_authorization_failed() {
108        let err = CollabError::AuthorizationFailed("missing permission".to_string());
109        let msg = err.to_string();
110        assert!(msg.contains("Authorization failed"));
111        assert!(msg.contains("missing permission"));
112    }
113
114    #[test]
115    fn test_workspace_not_found() {
116        let err = CollabError::WorkspaceNotFound("ws-123".to_string());
117        let msg = err.to_string();
118        assert!(msg.contains("Workspace not found"));
119        assert!(msg.contains("ws-123"));
120    }
121
122    #[test]
123    fn test_user_not_found() {
124        let err = CollabError::UserNotFound("user-456".to_string());
125        let msg = err.to_string();
126        assert!(msg.contains("User not found"));
127        assert!(msg.contains("user-456"));
128    }
129
130    #[test]
131    fn test_conflict_detected() {
132        let err = CollabError::ConflictDetected("concurrent edit".to_string());
133        let msg = err.to_string();
134        assert!(msg.contains("Conflict detected"));
135        assert!(msg.contains("concurrent edit"));
136    }
137
138    #[test]
139    fn test_sync_error() {
140        let err = CollabError::SyncError("sync failed".to_string());
141        let msg = err.to_string();
142        assert!(msg.contains("Sync error"));
143        assert!(msg.contains("sync failed"));
144    }
145
146    #[test]
147    fn test_database_error() {
148        let err = CollabError::DatabaseError("connection refused".to_string());
149        let msg = err.to_string();
150        assert!(msg.contains("Database error"));
151        assert!(msg.contains("connection refused"));
152    }
153
154    #[test]
155    fn test_websocket_error() {
156        let err = CollabError::WebSocketError("connection closed".to_string());
157        let msg = err.to_string();
158        assert!(msg.contains("WebSocket error"));
159        assert!(msg.contains("connection closed"));
160    }
161
162    #[test]
163    fn test_serialization_error() {
164        let err = CollabError::SerializationError("invalid json".to_string());
165        let msg = err.to_string();
166        assert!(msg.contains("Serialization error"));
167        assert!(msg.contains("invalid json"));
168    }
169
170    #[test]
171    fn test_invalid_input() {
172        let err = CollabError::InvalidInput("empty name".to_string());
173        let msg = err.to_string();
174        assert!(msg.contains("Invalid input"));
175        assert!(msg.contains("empty name"));
176    }
177
178    #[test]
179    fn test_already_exists() {
180        let err = CollabError::AlreadyExists("workspace-name".to_string());
181        let msg = err.to_string();
182        assert!(msg.contains("already exists"));
183        assert!(msg.contains("workspace-name"));
184    }
185
186    #[test]
187    fn test_timeout() {
188        let err = CollabError::Timeout("operation timed out".to_string());
189        let msg = err.to_string();
190        assert!(msg.contains("timeout"));
191        assert!(msg.contains("operation timed out"));
192    }
193
194    #[test]
195    fn test_connection_error() {
196        let err = CollabError::ConnectionError("host unreachable".to_string());
197        let msg = err.to_string();
198        assert!(msg.contains("Connection error"));
199        assert!(msg.contains("host unreachable"));
200    }
201
202    #[test]
203    fn test_version_mismatch() {
204        let err = CollabError::VersionMismatch {
205            expected: 10,
206            actual: 8,
207        };
208        let msg = err.to_string();
209        assert!(msg.contains("Version mismatch"));
210        assert!(msg.contains("10"));
211        assert!(msg.contains("8"));
212    }
213
214    #[test]
215    fn test_internal_error() {
216        let err = CollabError::Internal("unexpected failure".to_string());
217        let msg = err.to_string();
218        assert!(msg.contains("Internal error"));
219        assert!(msg.contains("unexpected failure"));
220    }
221
222    #[test]
223    fn test_from_serde_json_error() {
224        let json_err: serde_json::Error = serde_json::from_str::<String>("invalid").unwrap_err();
225        let err: CollabError = json_err.into();
226        assert!(matches!(err, CollabError::SerializationError(_)));
227    }
228
229    #[test]
230    fn test_error_debug() {
231        let err = CollabError::AuthenticationFailed("test".to_string());
232        let debug = format!("{:?}", err);
233        assert!(debug.contains("AuthenticationFailed"));
234    }
235}