use thiserror::Error;
#[derive(Error, Debug)]
pub enum CrdtError {
#[error("Database error: {0}")]
Database(#[from] rusqlite::Error),
#[error("CRDT encoding/decoding error: {0}")]
Encoding(String),
#[error("Document not found: {0}")]
DocumentNotFound(String),
#[error("Invalid entity type: {0}")]
InvalidEntityType(String),
#[error("Invalid document ID: {0}")]
InvalidDocumentId(String),
#[error("Conflict resolution failed: {0}")]
ConflictResolution(String),
#[error("State vector mismatch: expected {expected}, got {actual}")]
StateVectorMismatch { expected: String, actual: String },
#[error("SQL materialization failed for {entity_type}/{entity_id}: {reason}")]
MaterializationFailed {
entity_type: String,
entity_id: String,
reason: String,
},
#[error("Map operation failed on key '{key}': {reason}")]
MapOperation { key: String, reason: String },
#[error("Type mismatch for key '{key}': expected {expected}, got {actual}")]
TypeMismatch {
key: String,
expected: String,
actual: String,
},
#[error("Schema initialization failed: {0}")]
SchemaInit(String),
#[error("Operation error: {0}")]
Operation(String),
#[error("Connection pool error: {0}")]
Pool(String),
}
pub type CrdtResult<T> = Result<T, CrdtError>;
impl CrdtError {
pub fn encoding_error(msg: impl Into<String>) -> Self {
Self::Encoding(msg.into())
}
pub fn materialization_failed(
entity_type: impl Into<String>,
entity_id: impl Into<String>,
reason: impl Into<String>,
) -> Self {
Self::MaterializationFailed {
entity_type: entity_type.into(),
entity_id: entity_id.into(),
reason: reason.into(),
}
}
pub fn map_operation(key: impl Into<String>, reason: impl Into<String>) -> Self {
Self::MapOperation {
key: key.into(),
reason: reason.into(),
}
}
pub fn type_mismatch(
key: impl Into<String>,
expected: impl Into<String>,
actual: impl Into<String>,
) -> Self {
Self::TypeMismatch {
key: key.into(),
expected: expected.into(),
actual: actual.into(),
}
}
}
impl From<deadpool_sqlite::InteractError> for CrdtError {
fn from(err: deadpool_sqlite::InteractError) -> Self {
Self::Pool(err.to_string())
}
}
impl From<deadpool_sqlite::PoolError> for CrdtError {
fn from(err: deadpool_sqlite::PoolError) -> Self {
Self::Pool(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = CrdtError::DocumentNotFound("doc-123".to_string());
assert_eq!(err.to_string(), "Document not found: doc-123");
let err = CrdtError::type_mismatch("status", "string", "number");
assert_eq!(
err.to_string(),
"Type mismatch for key 'status': expected string, got number"
);
}
#[test]
fn test_materialization_error() {
let err = CrdtError::materialization_failed("channel", "ch-1", "SQL constraint violation");
assert_eq!(
err.to_string(),
"SQL materialization failed for channel/ch-1: SQL constraint violation"
);
}
}