1use thiserror::Error;
8
9use crate::crdt::doc::PathError;
10
11#[non_exhaustive]
17#[derive(Debug, Error)]
18pub enum CRDTError {
19 #[error("CRDT merge failed: {reason}")]
21 MergeFailed { reason: String },
22
23 #[error("CRDT serialization failed: {reason}")]
25 SerializationFailed { reason: String },
26
27 #[error("CRDT deserialization failed: {reason}")]
29 DeserializationFailed { reason: String },
30
31 #[error("CRDT type mismatch: expected {expected}, found {actual}")]
33 TypeMismatch { expected: String, actual: String },
34
35 #[error("Invalid CRDT value: {reason}")]
37 InvalidValue { reason: String },
38
39 #[error("CRDT list operation failed: {operation} - {reason}")]
41 ListOperationFailed { operation: String, reason: String },
42
43 #[error("CRDT document operation failed: {operation} - {reason}")]
45 DocOperationFailed { operation: String, reason: String },
46
47 #[error("CRDT nested operation failed: {path} - {reason}")]
49 NestedOperationFailed { path: String, reason: String },
50
51 #[error("Invalid UUID format: {uuid}")]
53 InvalidUuid { uuid: String },
54
55 #[error("CRDT element not found: {key}")]
57 ElementNotFound { key: String },
58
59 #[error("Invalid CRDT path: {path}")]
61 InvalidPath { path: String },
62
63 #[error("List index out of bounds: index {index}, length {len}")]
65 ListIndexOutOfBounds { index: usize, len: usize },
66}
67
68impl CRDTError {
69 pub fn is_merge_error(&self) -> bool {
71 matches!(self, CRDTError::MergeFailed { .. })
72 }
73
74 pub fn is_serialization_error(&self) -> bool {
76 matches!(
77 self,
78 CRDTError::SerializationFailed { .. } | CRDTError::DeserializationFailed { .. }
79 )
80 }
81
82 pub fn is_type_error(&self) -> bool {
84 matches!(self, CRDTError::TypeMismatch { .. })
85 }
86
87 pub fn is_list_operation_error(&self) -> bool {
89 matches!(self, CRDTError::ListOperationFailed { .. })
90 }
91
92 pub fn is_doc_error(&self) -> bool {
94 matches!(self, CRDTError::DocOperationFailed { .. })
95 }
96
97 pub fn is_nested_error(&self) -> bool {
99 matches!(self, CRDTError::NestedOperationFailed { .. })
100 }
101
102 pub fn is_not_found(&self) -> bool {
104 matches!(self, CRDTError::ElementNotFound { .. })
105 }
106
107 pub fn is_list_error(&self) -> bool {
109 matches!(self, CRDTError::ListIndexOutOfBounds { .. })
110 }
111
112 pub fn operation(&self) -> Option<&str> {
114 match self {
115 CRDTError::ListOperationFailed { operation, .. }
116 | CRDTError::DocOperationFailed { operation, .. } => Some(operation),
117 _ => None,
118 }
119 }
120
121 pub fn path(&self) -> Option<&str> {
123 match self {
124 CRDTError::NestedOperationFailed { path, .. } | CRDTError::InvalidPath { path } => {
125 Some(path)
126 }
127 _ => None,
128 }
129 }
130
131 pub fn key(&self) -> Option<&str> {
133 match self {
134 CRDTError::ElementNotFound { key } => Some(key),
135 _ => None,
136 }
137 }
138}
139
140impl From<PathError> for CRDTError {
142 fn from(err: PathError) -> Self {
143 CRDTError::InvalidPath {
144 path: err.to_string(),
145 }
146 }
147}
148
149impl From<CRDTError> for crate::Error {
151 fn from(err: CRDTError) -> Self {
152 crate::Error::CRDT(err)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_crdt_error_list_index_out_of_bounds() {
162 let error = CRDTError::ListIndexOutOfBounds { index: 5, len: 3 };
163
164 assert!(error.is_list_error());
165 assert!(!error.is_merge_error());
166 assert!(!error.is_serialization_error());
167 assert!(!error.is_type_error());
168 assert!(!error.is_list_operation_error());
169 assert!(!error.is_doc_error());
170 assert!(!error.is_nested_error());
171 assert!(!error.is_not_found());
172
173 assert_eq!(error.operation(), None);
174 assert_eq!(error.path(), None);
175 assert_eq!(error.key(), None);
176
177 let display = format!("{error}");
178 assert!(display.contains("List index out of bounds"));
179 assert!(display.contains("index 5"));
180 assert!(display.contains("length 3"));
181 }
182
183 #[test]
184 fn test_crdt_error_classification() {
185 let merge_error = CRDTError::MergeFailed {
186 reason: "test".to_string(),
187 };
188 assert!(merge_error.is_merge_error());
189
190 let serialization_error = CRDTError::SerializationFailed {
191 reason: "test".to_string(),
192 };
193 assert!(serialization_error.is_serialization_error());
194
195 let type_error = CRDTError::TypeMismatch {
196 expected: "string".to_string(),
197 actual: "int".to_string(),
198 };
199 assert!(type_error.is_type_error());
200
201 let list_error = CRDTError::ListOperationFailed {
202 operation: "insert".to_string(),
203 reason: "test".to_string(),
204 };
205 assert!(list_error.is_list_operation_error());
206 assert_eq!(list_error.operation(), Some("insert"));
207
208 let doc_error = CRDTError::DocOperationFailed {
209 operation: "set".to_string(),
210 reason: "test".to_string(),
211 };
212 assert!(doc_error.is_doc_error());
213 assert_eq!(doc_error.operation(), Some("set"));
214
215 let nested_error = CRDTError::NestedOperationFailed {
216 path: "user.profile".to_string(),
217 reason: "test".to_string(),
218 };
219 assert!(nested_error.is_nested_error());
220 assert_eq!(nested_error.path(), Some("user.profile"));
221
222 let not_found_error = CRDTError::ElementNotFound {
223 key: "missing".to_string(),
224 };
225 assert!(not_found_error.is_not_found());
226 assert_eq!(not_found_error.key(), Some("missing"));
227 }
228
229 #[test]
230 fn test_crdt_error_conversion_to_main_error() {
231 let crdt_error = CRDTError::ListIndexOutOfBounds { index: 1, len: 0 };
232 let main_error: crate::Error = crdt_error.into();
233
234 assert_eq!(main_error.module(), "crdt");
235
236 if let crate::Error::CRDT(inner) = main_error {
237 assert!(inner.is_list_error());
238 } else {
239 panic!("Expected CRDT error variant");
240 }
241 }
242
243 #[test]
244 fn test_crdt_error_display_messages() {
245 let errors = vec![
246 CRDTError::ListIndexOutOfBounds { index: 10, len: 5 },
247 CRDTError::MergeFailed {
248 reason: "conflict".to_string(),
249 },
250 CRDTError::TypeMismatch {
251 expected: "Doc".to_string(),
252 actual: "Text".to_string(),
253 },
254 CRDTError::InvalidPath {
255 path: "invalid..path".to_string(),
256 },
257 CRDTError::ElementNotFound {
258 key: "nonexistent".to_string(),
259 },
260 ];
261
262 for error in errors {
263 let display = format!("{error}");
264 assert!(!display.is_empty());
265 assert!(display.len() > 10); }
267 }
268
269 #[test]
270 fn test_path_error_conversion() {
271 let path_error = PathError::InvalidComponent {
272 component: "user.name".to_string(),
273 reason: "components cannot contain dots".to_string(),
274 };
275 let crdt_error: CRDTError = path_error.into();
276
277 match crdt_error {
278 CRDTError::InvalidPath { path } => {
279 assert!(path.contains("components cannot contain dots"));
280 }
281 _ => panic!("Expected InvalidPath variant"),
282 }
283
284 let path_error = PathError::InvalidComponent {
286 component: "test.value".to_string(),
287 reason: "components cannot contain dots".to_string(),
288 };
289 let main_error: crate::Error = CRDTError::from(path_error).into();
290 assert_eq!(main_error.module(), "crdt");
291 }
292}