use thiserror::Error;
#[derive(Debug, Error)]
pub enum StorageError {
#[error("{backend} backend error: {message}")]
Backend {
backend: &'static str,
message: String,
},
#[error("storage serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("storage I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("not found: {kind} {id}")]
NotFound {
kind: &'static str,
id: String,
},
#[error("conflict on {kind} {id}: {reason}")]
Conflict {
kind: &'static str,
id: String,
reason: String,
},
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("unsupported: {0}")]
Unsupported(String),
}
impl StorageError {
pub fn backend(backend: &'static str, message: impl Into<String>) -> Self {
Self::Backend {
backend,
message: message.into(),
}
}
pub fn not_found(kind: &'static str, id: impl Into<String>) -> Self {
Self::NotFound {
kind,
id: id.into(),
}
}
pub fn conflict(kind: &'static str, id: impl Into<String>, reason: impl Into<String>) -> Self {
Self::Conflict {
kind,
id: id.into(),
reason: reason.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_displays_include_context() {
let e = StorageError::not_found("message", "msg-42");
assert_eq!(e.to_string(), "not found: message msg-42");
let e = StorageError::backend("lance", "table missing");
assert!(e.to_string().contains("lance"));
assert!(e.to_string().contains("table missing"));
let e = StorageError::conflict("session", "s-7", "stale version");
assert!(e.to_string().contains("s-7"));
assert!(e.to_string().contains("stale version"));
}
#[test]
fn rides_anyhow_and_roundtrips_via_downcast() {
let e: anyhow::Error = StorageError::not_found("plan", "p-1").into();
let typed = e
.downcast_ref::<StorageError>()
.expect("typed variant preserved through anyhow");
assert!(matches!(typed, StorageError::NotFound { kind: "plan", .. }));
}
}