use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Entry not found: {0}")]
NotFound(String),
#[error("Invalid embedding dimension: expected {expected}, got {actual}")]
DimensionMismatch {
expected: usize,
actual: usize,
},
#[error("Storage error: {0}")]
Storage(String),
#[error("Embedding error: {0}")]
Embedding(String),
#[error("Learning error: {0}")]
Learning(String),
#[error("Serialization error: {0}")]
Serialization(#[from] bincode::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid configuration: {0}")]
Config(String),
#[error("Index corruption: {0}")]
IndexCorruption(String),
#[error("Concurrent access conflict: {0}")]
ConcurrencyConflict(String),
#[error("Ingest error: {0}")]
Ingest(String),
}
impl Error {
pub fn storage(msg: impl Into<String>) -> Self {
Self::Storage(msg.into())
}
pub fn embedding(msg: impl Into<String>) -> Self {
Self::Embedding(msg.into())
}
pub fn learning(msg: impl Into<String>) -> Self {
Self::Learning(msg.into())
}
pub fn not_found(id: impl Into<String>) -> Self {
Self::NotFound(id.into())
}
pub fn config(msg: impl Into<String>) -> Self {
Self::Config(msg.into())
}
pub fn ingest(msg: impl Into<String>) -> Self {
Self::Ingest(msg.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constructors_and_display() {
let cases: Vec<(Error, &str)> = vec![
(Error::storage("disk full"), "Storage error: disk full"),
(
Error::embedding("bad provider"),
"Embedding error: bad provider",
),
(Error::learning("nan"), "Learning error: nan"),
(Error::not_found("abc"), "Entry not found: abc"),
(
Error::config("missing key"),
"Invalid configuration: missing key",
),
(Error::ingest("bad markdown"), "Ingest error: bad markdown"),
(
Error::DimensionMismatch {
expected: 384,
actual: 128,
},
"Invalid embedding dimension: expected 384, got 128",
),
(
Error::IndexCorruption("checksum".into()),
"Index corruption: checksum",
),
(
Error::ConcurrencyConflict("lock".into()),
"Concurrent access conflict: lock",
),
];
for (err, expected) in cases {
assert_eq!(err.to_string(), expected);
}
}
#[test]
fn from_io_and_json() {
let io: Error = std::io::Error::new(std::io::ErrorKind::NotFound, "x").into();
assert!(matches!(io, Error::Io(_)));
assert!(io.to_string().starts_with("IO error:"));
let json_err = serde_json::from_str::<u32>("not json").unwrap_err();
let json: Error = json_err.into();
assert!(matches!(json, Error::Json(_)));
}
#[test]
fn from_bincode() {
let bin: Result<i32> = bincode::deserialize::<i32>(&[]).map_err(Into::into);
let err = bin.unwrap_err();
assert!(matches!(err, Error::Serialization(_)));
assert!(err.to_string().starts_with("Serialization error:"));
}
}