use core::fmt;
use std::path::PathBuf;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
NotImplemented,
#[cfg(feature = "nested")]
InvalidPath,
#[cfg(feature = "ttl")]
TtlOverflow,
Io(std::io::Error),
MagicMismatch,
VersionMismatch {
found: u32,
expected: u32,
},
FeatureMismatch {
file_flags: u32,
build_flags: u32,
},
Corrupted {
offset: u64,
reason: &'static str,
},
InvalidConfig(&'static str),
TransactionInvalid,
TransactionAborted(&'static str),
LockBusy {
path: PathBuf,
},
LockfileError(std::io::Error),
LockPoisoned,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotImplemented => f.write_str("emdb: operation not yet implemented"),
#[cfg(feature = "nested")]
Self::InvalidPath => f.write_str("emdb: invalid nested path"),
#[cfg(feature = "ttl")]
Self::TtlOverflow => f.write_str("emdb: ttl overflow"),
Self::Io(err) => write!(f, "emdb: io error ({})", err.kind()),
Self::MagicMismatch => f.write_str("emdb: file magic mismatch"),
Self::VersionMismatch { found, expected } => {
write!(f, "emdb: format version mismatch (found {}, expected {})", found, expected)
}
Self::FeatureMismatch {
file_flags,
build_flags,
} => write!(
f,
"emdb: feature mismatch (file flags 0x{file_flags:08x}, build flags 0x{build_flags:08x})"
),
Self::Corrupted { offset, reason } => {
write!(f, "emdb: corrupted data at offset {} ({})", offset, reason)
}
Self::InvalidConfig(msg) => write!(f, "emdb: invalid configuration ({msg})"),
Self::TransactionInvalid => f.write_str("emdb: invalid transaction context"),
Self::TransactionAborted(msg) => write!(f, "emdb: transaction aborted ({msg})"),
Self::LockBusy { path } => {
write!(f, "emdb: lock busy ({})", path.display())
}
Self::LockfileError(err) => {
write!(f, "emdb: lockfile error ({})", err.kind())
}
Self::LockPoisoned => f.write_str("emdb: lock poisoned"),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_not_implemented_display_is_stable() {
let msg = format!("{}", Error::NotImplemented);
assert!(msg.contains("not yet implemented"));
}
#[test]
fn test_error_implements_std_error() {
fn assert_error<E: std::error::Error>() {}
assert_error::<Error>();
}
#[test]
fn test_io_error_display_does_not_leak_payload() {
let err = Error::Io(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"secret",
));
let msg = format!("{}", err);
assert!(msg.contains("permission denied") || msg.contains("PermissionDenied"));
assert!(!msg.contains("secret"));
}
#[test]
fn test_version_mismatch_display_is_stable() {
let msg = format!(
"{}",
Error::VersionMismatch {
found: 2,
expected: 1,
}
);
assert!(msg.contains("found 2"));
assert!(msg.contains("expected 1"));
}
#[test]
fn test_corrupted_display_includes_offset_and_reason() {
let msg = format!(
"{}",
Error::Corrupted {
offset: 42,
reason: "crc mismatch",
}
);
assert!(msg.contains("42"));
assert!(msg.contains("crc mismatch"));
}
#[test]
fn test_from_io_maps_to_io_variant() {
let err: Error = std::io::Error::new(std::io::ErrorKind::NotFound, "missing").into();
assert!(matches!(err, Error::Io(_)));
}
#[test]
fn test_transaction_error_displays_are_stable() {
let invalid = format!("{}", Error::TransactionInvalid);
assert!(invalid.contains("transaction"));
let aborted = format!("{}", Error::TransactionAborted("invariant"));
assert!(aborted.contains("invariant"));
}
#[test]
fn test_lock_errors_display_are_stable() {
let busy = format!(
"{}",
Error::LockBusy {
path: std::path::PathBuf::from("/tmp/demo.lock"),
}
);
assert!(busy.contains("lock busy"));
let io_msg = format!("{}", Error::LockfileError(std::io::Error::other("x")));
assert!(io_msg.contains("lockfile error"));
let poisoned = format!("{}", Error::LockPoisoned);
assert!(poisoned.contains("lock poisoned"));
}
#[cfg(feature = "nested")]
#[test]
fn test_invalid_path_display_is_stable() {
let msg = format!("{}", Error::InvalidPath);
assert_eq!(msg, "emdb: invalid nested path");
}
#[cfg(feature = "ttl")]
#[test]
fn test_ttl_overflow_display_is_stable() {
let msg = format!("{}", Error::TtlOverflow);
assert_eq!(msg, "emdb: ttl overflow");
}
}