use crate::crdt::{CheckboxState, TaskId};
use crate::identity::AgentId;
pub type Result<T> = std::result::Result<T, CrdtError>;
#[derive(Debug, thiserror::Error)]
pub enum CrdtError {
#[error("task not found: {0:?}")]
TaskNotFound(TaskId),
#[error("invalid state transition: {current:?} -> {attempted:?}")]
InvalidStateTransition {
current: CheckboxState,
attempted: CheckboxState,
},
#[error("task already claimed by {0}")]
AlreadyClaimed(AgentId),
#[error("serialization error: {0}")]
Serialization(#[from] bincode::Error),
#[error("CRDT merge error: {0}")]
Merge(String),
#[error("gossip error: {0}")]
Gossip(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("system clock error: {0}")]
SystemClock(String),
}
#[cfg(test)]
mod tests {
use super::*;
fn mock_agent_id() -> AgentId {
AgentId([42u8; 32])
}
fn mock_task_id() -> TaskId {
TaskId::from_bytes([1u8; 32])
}
#[test]
fn test_checkbox_state_equality() {
let agent = mock_agent_id();
let empty1 = CheckboxState::Empty;
let empty2 = CheckboxState::Empty;
assert_eq!(empty1, empty2);
let claimed1 = CheckboxState::Claimed {
agent_id: agent,
timestamp: 100,
};
let claimed2 = CheckboxState::Claimed {
agent_id: agent,
timestamp: 100,
};
assert_eq!(claimed1, claimed2);
let done1 = CheckboxState::Done {
agent_id: agent,
timestamp: 200,
};
let done2 = CheckboxState::Done {
agent_id: agent,
timestamp: 200,
};
assert_eq!(done1, done2);
}
#[test]
fn test_error_display_task_not_found() {
let task_id = mock_task_id();
let error = CrdtError::TaskNotFound(task_id);
let display = format!("{}", error);
assert!(display.contains("task not found"));
}
#[test]
fn test_error_display_invalid_transition() {
let agent = mock_agent_id();
let error = CrdtError::InvalidStateTransition {
current: CheckboxState::Empty,
attempted: CheckboxState::Done {
agent_id: agent,
timestamp: 100,
},
};
let display = format!("{}", error);
assert!(display.contains("invalid state transition"));
assert!(display.contains("Empty"));
assert!(display.contains("Done"));
}
#[test]
fn test_error_display_already_claimed() {
let agent = mock_agent_id();
let error = CrdtError::AlreadyClaimed(agent);
let display = format!("{}", error);
assert!(display.contains("already claimed"));
}
#[test]
fn test_error_display_merge() {
let error = CrdtError::Merge("conflict detected".to_string());
let display = format!("{}", error);
assert!(display.contains("CRDT merge error"));
assert!(display.contains("conflict detected"));
}
#[test]
fn test_error_display_gossip() {
let error = CrdtError::Gossip("connection failed".to_string());
let display = format!("{}", error);
assert!(display.contains("gossip error"));
assert!(display.contains("connection failed"));
}
#[test]
fn test_error_from_bincode() {
fn _assert_from_impl(_: bincode::Error) -> CrdtError {
CrdtError::Serialization(bincode::Error::new(bincode::ErrorKind::Custom(
"test".to_string(),
)))
}
}
#[test]
fn test_error_from_io() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let crdt_error: CrdtError = io_error.into();
let display = format!("{}", crdt_error);
assert!(display.contains("I/O error"));
assert!(display.contains("file not found"));
}
}