use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum StorageError {
#[error("database error: {0}")]
Database(#[from] lbug::Error),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("csv error: {0}")]
Csv(#[from] csv::Error),
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
#[error("query failed: {0}")]
Query(String),
#[error("schema error: {0}")]
Schema(String),
#[error("not found: {0}")]
NotFound(String),
#[error("invalid data: {0}")]
InvalidData(String),
#[error("database corrupt: {0}")]
Corrupt(String),
#[error("database is locked by another process: {holder_hint}")]
DatabaseLocked { holder_hint: String },
}
pub type Result<T> = std::result::Result<T, StorageError>;
#[must_use]
pub fn is_table_missing_error(err: &StorageError) -> bool {
let msg = err.to_string();
msg.contains("does not exist") || msg.contains("no such") || msg.contains("Binder exception")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn database_variant_displays_message() {
let err = StorageError::Query("bad cypher".to_string());
assert_eq!(err.to_string(), "query failed: bad cypher");
}
#[test]
fn schema_variant_displays_message() {
let err = StorageError::Schema("unsupported index".to_string());
assert_eq!(err.to_string(), "schema error: unsupported index");
}
#[test]
fn not_found_variant_displays_message() {
let err = StorageError::NotFound("project foo".to_string());
assert_eq!(err.to_string(), "not found: project foo");
}
#[test]
fn invalid_data_variant_displays_message() {
let err = StorageError::InvalidData("missing id".to_string());
assert_eq!(err.to_string(), "invalid data: missing id");
}
#[test]
fn database_locked_displays_holder_hint() {
let err = StorageError::DatabaseLocked {
holder_hint: "Lock is held by PID 1234".to_string(),
};
let s = err.to_string();
assert!(s.contains("database is locked"), "消息:{s}");
assert!(s.contains("PID 1234"), "应含 holder_hint:{s}");
assert!(s.contains("another process"), "应提示另一进程:{s}");
}
#[test]
fn io_error_converts_via_from() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "missing file");
let storage_err: StorageError = io_err.into();
assert!(storage_err.to_string().contains("io error"));
assert!(storage_err.to_string().contains("missing file"));
}
#[test]
fn csv_error_converts_via_from() {
let io_err = io::Error::other("csv failure");
let csv_err: csv::Error = io_err.into();
let storage_err: StorageError = csv_err.into();
assert!(storage_err.to_string().contains("csv error"));
}
#[test]
fn json_error_converts_via_from() {
let json_err = serde_json::from_str::<serde_json::Value>("bad").unwrap_err();
let storage_err: StorageError = json_err.into();
assert!(storage_err.to_string().contains("json error"));
}
#[test]
fn debug_includes_variant_name() {
let err = StorageError::NotFound("x".to_string());
let s = format!("{err:?}");
assert!(s.contains("NotFound"));
assert!(s.contains("\"x\""));
}
#[test]
fn is_table_missing_error_detects_binder_exception() {
let err = StorageError::Query("Binder exception: Table PROJECT does not exist.".into());
assert!(is_table_missing_error(&err));
}
#[test]
fn is_table_missing_error_detects_does_not_exist() {
let err = StorageError::Query("Table PROJECT does not exist.".into());
assert!(is_table_missing_error(&err));
}
#[test]
fn is_table_missing_error_detects_no_such() {
let err = StorageError::Query("no such table: PROJECT".into());
assert!(is_table_missing_error(&err));
}
#[test]
fn is_table_missing_error_returns_false_for_other_errors() {
let err = StorageError::Query("syntax error at line 1".into());
assert!(!is_table_missing_error(&err));
}
#[test]
fn is_table_missing_error_returns_false_for_not_found() {
let err = StorageError::NotFound("project foo".into());
assert!(!is_table_missing_error(&err));
}
#[test]
fn is_table_missing_error_returns_false_for_io() {
let err = StorageError::Io(std::io::Error::other("disk failure"));
assert!(!is_table_missing_error(&err));
}
}