use uuid::Uuid;
pub mod cache;
pub mod relationship;
pub use cache::CacheError;
pub use relationship::RelationshipError;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Storage error: {0}")]
Storage(String),
#[error("Learning error: {0}")]
Learning(String),
#[error("MCP error: {0}")]
MCP(String),
#[error("Episode not found: {0}")]
NotFound(Uuid),
#[error("Pattern error: {0}")]
Pattern(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Execution timeout")]
ExecutionTimeout,
#[error("Circuit breaker open")]
CircuitBreakerOpen,
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Invalid state: {0}")]
InvalidState(String),
#[error("Security validation failed: {0}")]
Security(String),
#[error("Validation failed: {0}")]
ValidationFailed(String),
#[error("Quota exceeded: {0}")]
QuotaExceeded(String),
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Embedding error: {0}")]
Embedding(#[from] anyhow::Error),
#[error("{0}")]
Relationship(#[from] RelationshipError),
#[error("{0}")]
Cache(#[from] CacheError),
}
impl Error {
#[must_use]
pub fn is_recoverable(&self) -> bool {
match self {
Error::Storage(_)
| Error::MCP(_)
| Error::ExecutionTimeout
| Error::CircuitBreakerOpen
| Error::RateLimitExceeded(_)
| Error::Io(_)
| Error::Embedding(_) => true,
Error::Learning(_)
| Error::NotFound(_)
| Error::Pattern(_)
| Error::Serialization(_)
| Error::InvalidInput(_)
| Error::InvalidState(_)
| Error::Security(_)
| Error::ValidationFailed(_)
| Error::QuotaExceeded(_)
| Error::Configuration(_) => false,
Error::Relationship(rel_err) => {
matches!(rel_err, RelationshipError::ValidationFailed { .. })
}
Error::Cache(cache_err) => matches!(
cache_err,
CacheError::EvictionFailed { .. }
| CacheError::SerializationFailed { .. }
| CacheError::EntryTooLarge { .. }
),
}
}
#[must_use]
pub fn is_relationship_error(&self) -> bool {
matches!(self, Error::Relationship(_))
}
#[must_use]
pub fn is_cache_error(&self) -> bool {
matches!(self, Error::Cache(_))
}
#[must_use]
pub fn as_relationship_error(&self) -> Option<&RelationshipError> {
match self {
Error::Relationship(e) => Some(e),
_ => None,
}
}
#[must_use]
pub fn as_cache_error(&self) -> Option<&CacheError> {
match self {
Error::Cache(e) => Some(e),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_from_relationship_error() {
let rel_err = RelationshipError::SelfReference {
episode_id: Uuid::new_v4(),
};
let err: Error = rel_err.into();
assert!(err.is_relationship_error());
assert!(!err.is_recoverable());
}
#[test]
fn test_error_from_cache_error() {
let cache_err = CacheError::EntryTooLarge {
key: "test".to_string(),
size: 100,
max_size: 50,
};
let err: Error = cache_err.into();
assert!(err.is_cache_error());
assert!(err.is_recoverable());
}
#[test]
fn test_as_relationship_error() {
let rel_err = RelationshipError::NotFound {
relationship_id: Uuid::new_v4(),
};
let err = Error::Relationship(rel_err.clone());
assert!(err.as_relationship_error().is_some());
assert_eq!(err.as_relationship_error(), Some(&rel_err));
let other_err = Error::NotFound(Uuid::new_v4());
assert!(other_err.as_relationship_error().is_none());
}
#[test]
fn test_as_cache_error() {
let cache_err = CacheError::EvictionFailed {
reason: "test".to_string(),
};
let err = Error::Cache(cache_err.clone());
assert!(err.as_cache_error().is_some());
assert_eq!(err.as_cache_error(), Some(&cache_err));
let other_err = Error::Storage("test".to_string());
assert!(other_err.as_cache_error().is_none());
}
#[test]
fn test_recoverable_cache_errors() {
let eviction_err = Error::Cache(CacheError::EvictionFailed {
reason: "test".to_string(),
});
assert!(eviction_err.is_recoverable());
let serialization_err = Error::Cache(CacheError::SerializationFailed {
key: "key".to_string(),
error: "test".to_string(),
});
assert!(serialization_err.is_recoverable());
let config_err = Error::Cache(CacheError::InvalidConfiguration {
field: "test".to_string(),
value: "invalid".to_string(),
});
assert!(!config_err.is_recoverable());
}
#[test]
fn test_recoverable_relationship_errors() {
let validation_err = Error::Relationship(RelationshipError::ValidationFailed {
reason: "test".to_string(),
});
assert!(validation_err.is_recoverable());
let self_ref_err = Error::Relationship(RelationshipError::SelfReference {
episode_id: Uuid::new_v4(),
});
assert!(!self_ref_err.is_recoverable());
}
#[test]
fn test_error_display() {
let rel_err = RelationshipError::SelfReference {
episode_id: Uuid::new_v4(),
};
let err = Error::Relationship(rel_err);
let msg = err.to_string();
assert!(msg.contains("self-referencing"));
}
}