use std::{fmt, io};
pub type Result<T> = std::result::Result<T, LuciError>;
#[derive(Debug)]
pub enum LuciError {
Io(io::Error),
IndexNotFound(String),
IndexCorrupted(String),
WriterLocked,
SchemaConflict {
field: String,
expected: String,
actual: String,
},
UnsupportedQuery(String),
InvalidQuery(String),
InvalidValue(String),
TransactionActive,
QueryBehaviorDifference { feature: String, difference: String },
SegmentFormatMigrationRequired(String),
SegmentFormatUnknown(String),
}
impl fmt::Display for LuciError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::IndexNotFound(path) => write!(f, "index not found: {path}"),
Self::IndexCorrupted(msg) => write!(f, "index corrupted: {msg}"),
Self::WriterLocked => write!(f, "another writer holds the lock"),
Self::SchemaConflict {
field,
expected,
actual,
} => write!(
f,
"schema conflict on field '{field}': expected {expected}, got {actual}"
),
Self::UnsupportedQuery(q) => write!(f, "unsupported query type: {q}"),
Self::InvalidQuery(msg) => write!(f, "invalid query: {msg}"),
Self::InvalidValue(msg) => write!(f, "invalid value: {msg}"),
Self::TransactionActive => write!(
f,
"cannot use index.add() while a transaction is active on this thread — use txn.add() instead"
),
Self::QueryBehaviorDifference {
feature,
difference,
} => write!(f, "{feature}: {difference}"),
Self::SegmentFormatMigrationRequired(msg) => {
write!(f, "segment format requires migration: {msg}")
}
Self::SegmentFormatUnknown(msg) => {
write!(f, "unknown segment format: {msg}")
}
}
}
}
impl std::error::Error for LuciError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for LuciError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn io_error_conversion() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file gone");
let luci_err = LuciError::from(io_err);
assert!(matches!(luci_err, LuciError::Io(_)));
assert!(luci_err.source().is_some());
}
#[test]
fn display_index_not_found() {
let err = LuciError::IndexNotFound("/tmp/test.luci".into());
assert!(format!("{err}").contains("/tmp/test.luci"));
}
#[test]
fn display_index_corrupted() {
let err = LuciError::IndexCorrupted("checksum mismatch".into());
assert!(format!("{err}").contains("checksum mismatch"));
}
#[test]
fn display_writer_locked() {
let err = LuciError::WriterLocked;
assert!(format!("{err}").contains("lock"));
}
#[test]
fn display_schema_conflict() {
let err = LuciError::SchemaConflict {
field: "price".into(),
expected: "float".into(),
actual: "text".into(),
};
let msg = format!("{err}");
assert!(msg.contains("price"));
assert!(msg.contains("float"));
assert!(msg.contains("text"));
}
#[test]
fn display_unsupported_query() {
let err = LuciError::UnsupportedQuery("span_near".into());
assert!(format!("{err}").contains("span_near"));
}
#[test]
fn display_invalid_query() {
let err = LuciError::InvalidQuery("unexpected token".into());
assert!(format!("{err}").contains("unexpected token"));
}
#[test]
fn display_invalid_value() {
let err = LuciError::InvalidValue("keyword value exceeds 65535 bytes".into());
let msg = format!("{err}");
assert!(msg.contains("invalid value"));
assert!(msg.contains("65535"));
}
#[test]
fn non_io_errors_have_no_source() {
let err = LuciError::WriterLocked;
assert!(err.source().is_none());
}
}