use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum StorageError {
#[error("entity not found: {id}")]
NotFound {
id: String,
},
#[error("entity already exists: {id}")]
AlreadyExists {
id: String,
},
#[error("validation error: {message}")]
Validation {
message: String,
},
#[error("connection error: {message}")]
Connection {
message: String,
},
#[error("query error: {message}")]
Query {
message: String,
},
#[error("timeout after {duration_ms}ms")]
Timeout {
duration_ms: u64,
},
#[error("simulated fault: {fault_type}")]
SimulatedFault {
fault_type: String,
},
#[error("internal error: {message}")]
Internal {
message: String,
},
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("write failed: {0}")]
WriteFailed(String),
#[error("read failed: {0}")]
ReadFailed(String),
#[error("serialization error: {0}")]
SerializationError(String),
#[error("deserialization error: {0}")]
DeserializationError(String),
}
impl StorageError {
#[must_use]
pub fn not_found(id: impl Into<String>) -> Self {
Self::NotFound { id: id.into() }
}
#[must_use]
pub fn already_exists(id: impl Into<String>) -> Self {
Self::AlreadyExists { id: id.into() }
}
#[must_use]
pub fn validation(message: impl Into<String>) -> Self {
Self::Validation {
message: message.into(),
}
}
#[must_use]
pub fn connection(message: impl Into<String>) -> Self {
Self::Connection {
message: message.into(),
}
}
#[must_use]
pub fn query(message: impl Into<String>) -> Self {
Self::Query {
message: message.into(),
}
}
#[must_use]
pub fn timeout(duration_ms: u64) -> Self {
Self::Timeout { duration_ms }
}
#[must_use]
pub fn simulated_fault(fault_type: impl Into<String>) -> Self {
Self::SimulatedFault {
fault_type: fault_type.into(),
}
}
#[must_use]
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
#[must_use]
pub fn read(message: impl Into<String>) -> Self {
Self::Query {
message: format!("read: {}", message.into()),
}
}
#[must_use]
pub fn write(message: impl Into<String>) -> Self {
Self::Query {
message: format!("write: {}", message.into()),
}
}
#[must_use]
pub fn is_transient(&self) -> bool {
matches!(
self,
Self::Connection { .. } | Self::Timeout { .. } | Self::SimulatedFault { .. }
)
}
}
pub type StorageResult<T> = Result<T, StorageError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_constructors() {
let err = StorageError::not_found("test-id");
assert!(matches!(err, StorageError::NotFound { id } if id == "test-id"));
let err = StorageError::validation("invalid content");
assert!(
matches!(err, StorageError::Validation { message } if message == "invalid content")
);
}
#[test]
fn test_is_transient() {
assert!(StorageError::connection("failed").is_transient());
assert!(StorageError::timeout(1000).is_transient());
assert!(StorageError::simulated_fault("network").is_transient());
assert!(!StorageError::not_found("id").is_transient());
assert!(!StorageError::validation("bad").is_transient());
}
}