use crate::types::AgentId;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PersistenceError {
pub agent_id: Option<AgentId>,
kind: Box<PersistenceErrorKind>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PersistenceErrorKind {
DatabaseOpen {
path: String,
message: String,
},
SchemaInit {
message: String,
},
QueryFailed {
operation: String,
message: String,
},
NotFound {
entity: String,
id: String,
},
SerializationFailed {
message: String,
},
DeserializationFailed {
message: String,
},
ShuttingDown,
TransactionFailed {
message: String,
},
ConnectionError {
message: String,
},
EmbeddingFailed {
provider: String,
message: String,
},
EmbeddingDimensionMismatch {
expected: usize,
actual: usize,
},
VectorSearchFailed {
message: String,
},
}
impl PersistenceError {
#[must_use]
pub fn new(kind: PersistenceErrorKind) -> Self {
Self {
agent_id: None,
kind: Box::new(kind),
}
}
#[must_use]
pub fn with_agent(agent_id: AgentId, kind: PersistenceErrorKind) -> Self {
Self {
agent_id: Some(agent_id),
kind: Box::new(kind),
}
}
#[must_use]
pub fn kind(&self) -> &PersistenceErrorKind {
&self.kind
}
#[must_use]
pub fn database_open(path: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::DatabaseOpen {
path: path.into(),
message: message.into(),
})
}
#[must_use]
pub fn schema_init(message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::SchemaInit {
message: message.into(),
})
}
#[must_use]
pub fn query_failed(operation: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::QueryFailed {
operation: operation.into(),
message: message.into(),
})
}
#[must_use]
pub fn not_found(entity: impl Into<String>, id: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::NotFound {
entity: entity.into(),
id: id.into(),
})
}
#[must_use]
pub fn serialization_failed(message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::SerializationFailed {
message: message.into(),
})
}
#[must_use]
pub fn deserialization_failed(message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::DeserializationFailed {
message: message.into(),
})
}
#[must_use]
pub fn shutting_down() -> Self {
Self::new(PersistenceErrorKind::ShuttingDown)
}
#[must_use]
pub fn transaction_failed(message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::TransactionFailed {
message: message.into(),
})
}
#[must_use]
pub fn connection_error(message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::ConnectionError {
message: message.into(),
})
}
#[must_use]
pub fn embedding_failed(provider: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::EmbeddingFailed {
provider: provider.into(),
message: message.into(),
})
}
#[must_use]
pub fn embedding_dimension_mismatch(expected: usize, actual: usize) -> Self {
Self::new(PersistenceErrorKind::EmbeddingDimensionMismatch { expected, actual })
}
#[must_use]
pub fn vector_search_failed(message: impl Into<String>) -> Self {
Self::new(PersistenceErrorKind::VectorSearchFailed {
message: message.into(),
})
}
#[must_use]
pub fn is_retriable(&self) -> bool {
matches!(
*self.kind,
PersistenceErrorKind::ConnectionError { .. }
| PersistenceErrorKind::TransactionFailed { .. }
)
}
#[must_use]
pub fn is_not_found(&self) -> bool {
matches!(*self.kind, PersistenceErrorKind::NotFound { .. })
}
#[must_use]
pub fn is_shutting_down(&self) -> bool {
matches!(*self.kind, PersistenceErrorKind::ShuttingDown)
}
}
impl fmt::Display for PersistenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref id) = self.agent_id {
write!(f, "agent '{}': ", id)?;
}
match self.kind.as_ref() {
PersistenceErrorKind::DatabaseOpen { path, message } => {
write!(
f,
"failed to open database at '{}': {}; check file permissions and path",
path, message
)
}
PersistenceErrorKind::SchemaInit { message } => {
write!(
f,
"failed to initialize database schema: {}; database may be corrupted",
message
)
}
PersistenceErrorKind::QueryFailed { operation, message } => {
write!(f, "{} query failed: {}", operation, message)
}
PersistenceErrorKind::NotFound { entity, id } => {
write!(f, "{} with id '{}' not found", entity, id)
}
PersistenceErrorKind::SerializationFailed { message } => {
write!(f, "failed to serialize data: {}", message)
}
PersistenceErrorKind::DeserializationFailed { message } => {
write!(f, "failed to deserialize data: {}", message)
}
PersistenceErrorKind::ShuttingDown => {
write!(
f,
"memory store is shutting down; cannot accept new requests"
)
}
PersistenceErrorKind::TransactionFailed { message } => {
write!(f, "transaction failed: {}; retry may succeed", message)
}
PersistenceErrorKind::ConnectionError { message } => {
write!(f, "database connection error: {}", message)
}
PersistenceErrorKind::EmbeddingFailed { provider, message } => {
write!(
f,
"embedding provider '{}' failed: {}; check provider configuration",
provider, message
)
}
PersistenceErrorKind::EmbeddingDimensionMismatch { expected, actual } => {
write!(
f,
"embedding dimension mismatch: expected {}, got {}; ensure consistent provider",
expected, actual
)
}
PersistenceErrorKind::VectorSearchFailed { message } => {
write!(f, "vector search failed: {}", message)
}
}
}
}
impl std::error::Error for PersistenceError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn persistence_error_database_open_display() {
let error = PersistenceError::database_open("/path/to/db", "permission denied");
let msg = error.to_string();
assert!(msg.contains("/path/to/db"));
assert!(msg.contains("permission denied"));
}
#[test]
fn persistence_error_connection_is_retriable() {
let error = PersistenceError::connection_error("timeout");
assert!(error.is_retriable());
}
#[test]
fn persistence_error_transaction_is_retriable() {
let error = PersistenceError::transaction_failed("deadlock");
assert!(error.is_retriable());
}
#[test]
fn persistence_error_not_found_is_not_retriable() {
let error = PersistenceError::not_found("conversation", "conv_123");
assert!(!error.is_retriable());
assert!(error.is_not_found());
}
#[test]
fn persistence_error_schema_init_display() {
let error = PersistenceError::schema_init("table already exists");
let msg = error.to_string();
assert!(msg.contains("schema"));
assert!(msg.contains("table already exists"));
}
#[test]
fn persistence_error_query_failed_display() {
let error = PersistenceError::query_failed("INSERT", "constraint violation");
let msg = error.to_string();
assert!(msg.contains("INSERT"));
assert!(msg.contains("constraint violation"));
}
#[test]
fn persistence_error_not_found_display() {
let error = PersistenceError::not_found("conversation", "conv_abc");
let msg = error.to_string();
assert!(msg.contains("conversation"));
assert!(msg.contains("conv_abc"));
assert!(msg.contains("not found"));
}
#[test]
fn persistence_error_serialization_display() {
let error = PersistenceError::serialization_failed("invalid utf-8");
let msg = error.to_string();
assert!(msg.contains("serialize"));
assert!(msg.contains("invalid utf-8"));
}
#[test]
fn persistence_error_deserialization_display() {
let error = PersistenceError::deserialization_failed("missing field");
let msg = error.to_string();
assert!(msg.contains("deserialize"));
assert!(msg.contains("missing field"));
}
#[test]
fn persistence_error_shutting_down() {
let error = PersistenceError::shutting_down();
assert!(error.is_shutting_down());
let msg = error.to_string();
assert!(msg.contains("shutting down"));
}
#[test]
fn persistence_error_with_agent_display() {
let agent_id = AgentId::new();
let error = PersistenceError::with_agent(
agent_id.clone(),
PersistenceErrorKind::NotFound {
entity: "state".to_string(),
id: "state_123".to_string(),
},
);
let msg = error.to_string();
assert!(msg.contains(&agent_id.to_string()));
assert!(msg.contains("state"));
}
#[test]
fn persistence_error_kind_accessor() {
let error = PersistenceError::connection_error("timeout");
assert!(matches!(
error.kind(),
PersistenceErrorKind::ConnectionError { .. }
));
}
#[test]
fn persistence_error_embedding_failed_display() {
let error = PersistenceError::embedding_failed("openai", "rate limited");
let msg = error.to_string();
assert!(msg.contains("openai"));
assert!(msg.contains("rate limited"));
}
#[test]
fn persistence_error_embedding_dimension_mismatch_display() {
let error = PersistenceError::embedding_dimension_mismatch(384, 768);
let msg = error.to_string();
assert!(msg.contains("384"));
assert!(msg.contains("768"));
}
#[test]
fn persistence_error_vector_search_failed_display() {
let error = PersistenceError::vector_search_failed("index corrupted");
let msg = error.to_string();
assert!(msg.contains("vector search"));
assert!(msg.contains("index corrupted"));
}
}