use thiserror::Error;
use crate::crdt::doc::PathError;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum CRDTError {
#[error("CRDT merge failed: {reason}")]
MergeFailed { reason: String },
#[error("CRDT serialization failed: {reason}")]
SerializationFailed { reason: String },
#[error("CRDT deserialization failed: {reason}")]
DeserializationFailed { reason: String },
#[error("CRDT type mismatch: expected {expected}, found {actual}")]
TypeMismatch { expected: String, actual: String },
#[error("Invalid CRDT value: {reason}")]
InvalidValue { reason: String },
#[error("CRDT list operation failed: {operation} - {reason}")]
ListOperationFailed { operation: String, reason: String },
#[error("CRDT document operation failed: {operation} - {reason}")]
DocOperationFailed { operation: String, reason: String },
#[error("CRDT nested operation failed: {path} - {reason}")]
NestedOperationFailed { path: String, reason: String },
#[error("Invalid UUID format: {uuid}")]
InvalidUuid { uuid: String },
#[error("CRDT element not found: {key}")]
ElementNotFound { key: String },
#[error("Invalid CRDT path: {path}")]
InvalidPath { path: String },
#[error("List index out of bounds: index {index}, length {len}")]
ListIndexOutOfBounds { index: usize, len: usize },
}
impl CRDTError {
pub fn is_merge_error(&self) -> bool {
matches!(self, CRDTError::MergeFailed { .. })
}
pub fn is_serialization_error(&self) -> bool {
matches!(
self,
CRDTError::SerializationFailed { .. } | CRDTError::DeserializationFailed { .. }
)
}
pub fn is_type_error(&self) -> bool {
matches!(self, CRDTError::TypeMismatch { .. })
}
pub fn is_list_operation_error(&self) -> bool {
matches!(self, CRDTError::ListOperationFailed { .. })
}
pub fn is_doc_error(&self) -> bool {
matches!(self, CRDTError::DocOperationFailed { .. })
}
pub fn is_nested_error(&self) -> bool {
matches!(self, CRDTError::NestedOperationFailed { .. })
}
pub fn is_not_found_error(&self) -> bool {
matches!(self, CRDTError::ElementNotFound { .. })
}
pub fn is_list_error(&self) -> bool {
matches!(self, CRDTError::ListIndexOutOfBounds { .. })
}
pub fn operation(&self) -> Option<&str> {
match self {
CRDTError::ListOperationFailed { operation, .. }
| CRDTError::DocOperationFailed { operation, .. } => Some(operation),
_ => None,
}
}
pub fn path(&self) -> Option<&str> {
match self {
CRDTError::NestedOperationFailed { path, .. } | CRDTError::InvalidPath { path } => {
Some(path)
}
_ => None,
}
}
pub fn key(&self) -> Option<&str> {
match self {
CRDTError::ElementNotFound { key } => Some(key),
_ => None,
}
}
}
impl From<PathError> for CRDTError {
fn from(err: PathError) -> Self {
CRDTError::InvalidPath {
path: err.to_string(),
}
}
}
impl From<CRDTError> for crate::Error {
fn from(err: CRDTError) -> Self {
crate::Error::CRDT(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crdt_error_list_index_out_of_bounds() {
let error = CRDTError::ListIndexOutOfBounds { index: 5, len: 3 };
assert!(error.is_list_error());
assert!(!error.is_merge_error());
assert!(!error.is_serialization_error());
assert!(!error.is_type_error());
assert!(!error.is_list_operation_error());
assert!(!error.is_doc_error());
assert!(!error.is_nested_error());
assert!(!error.is_not_found_error());
assert_eq!(error.operation(), None);
assert_eq!(error.path(), None);
assert_eq!(error.key(), None);
let display = format!("{error}");
assert!(display.contains("List index out of bounds"));
assert!(display.contains("index 5"));
assert!(display.contains("length 3"));
}
#[test]
fn test_crdt_error_classification() {
let merge_error = CRDTError::MergeFailed {
reason: "test".to_string(),
};
assert!(merge_error.is_merge_error());
let serialization_error = CRDTError::SerializationFailed {
reason: "test".to_string(),
};
assert!(serialization_error.is_serialization_error());
let type_error = CRDTError::TypeMismatch {
expected: "string".to_string(),
actual: "int".to_string(),
};
assert!(type_error.is_type_error());
let list_error = CRDTError::ListOperationFailed {
operation: "insert".to_string(),
reason: "test".to_string(),
};
assert!(list_error.is_list_operation_error());
assert_eq!(list_error.operation(), Some("insert"));
let doc_error = CRDTError::DocOperationFailed {
operation: "set".to_string(),
reason: "test".to_string(),
};
assert!(doc_error.is_doc_error());
assert_eq!(doc_error.operation(), Some("set"));
let nested_error = CRDTError::NestedOperationFailed {
path: "user.profile".to_string(),
reason: "test".to_string(),
};
assert!(nested_error.is_nested_error());
assert_eq!(nested_error.path(), Some("user.profile"));
let not_found_error = CRDTError::ElementNotFound {
key: "missing".to_string(),
};
assert!(not_found_error.is_not_found_error());
assert_eq!(not_found_error.key(), Some("missing"));
}
#[test]
fn test_crdt_error_conversion_to_main_error() {
let crdt_error = CRDTError::ListIndexOutOfBounds { index: 1, len: 0 };
let main_error: crate::Error = crdt_error.into();
assert_eq!(main_error.module(), "crdt");
if let crate::Error::CRDT(inner) = main_error {
assert!(inner.is_list_error());
} else {
panic!("Expected CRDT error variant");
}
}
#[test]
fn test_crdt_error_display_messages() {
let errors = vec![
CRDTError::ListIndexOutOfBounds { index: 10, len: 5 },
CRDTError::MergeFailed {
reason: "conflict".to_string(),
},
CRDTError::TypeMismatch {
expected: "Doc".to_string(),
actual: "Text".to_string(),
},
CRDTError::InvalidPath {
path: "invalid..path".to_string(),
},
CRDTError::ElementNotFound {
key: "nonexistent".to_string(),
},
];
for error in errors {
let display = format!("{error}");
assert!(!display.is_empty());
assert!(display.len() > 10); }
}
#[test]
fn test_path_error_conversion() {
let path_error = PathError::InvalidComponent {
component: "user.name".to_string(),
reason: "components cannot contain dots".to_string(),
};
let crdt_error: CRDTError = path_error.into();
match crdt_error {
CRDTError::InvalidPath { path } => {
assert!(path.contains("components cannot contain dots"));
}
_ => panic!("Expected InvalidPath variant"),
}
let path_error = PathError::InvalidComponent {
component: "test.value".to_string(),
reason: "components cannot contain dots".to_string(),
};
let main_error: crate::Error = CRDTError::from(path_error).into();
assert_eq!(main_error.module(), "crdt");
}
}