use error_stack::Report;
use std::error::Error;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StorageError {
Open,
Read,
Write,
Delete,
Scan,
Flush,
Codec,
Meta,
Migrate,
Path,
Depth,
CommitFailed,
Taken,
Closed,
Notify,
Reentrant,
}
impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
StorageError::Open => "the store could not be opened",
StorageError::Read => "the store could not read",
StorageError::Write => "the store could not write",
StorageError::Delete => "the store could not delete",
StorageError::Scan => "the store could not list a prefix",
StorageError::Flush => "the store could not commit what it had buffered",
StorageError::Codec => "the value could not be encoded or decoded",
StorageError::Meta => "the schema bookkeeping could not be read or written",
StorageError::Migrate => "the data could not be brought up to the declared schema",
StorageError::Path => "a name that cannot be a level",
StorageError::Depth => "deeper than this store reads back",
StorageError::CommitFailed => "the flush this commit was waiting on did not complete",
StorageError::Taken => "two schemas want the same place",
StorageError::Closed => "the store was closed and has let go of its file",
StorageError::Notify => "the change was stored, and a subscriber could not take it",
StorageError::Reentrant => {
"the store cannot do this from inside what it is already doing"
}
})
}
}
impl Error for StorageError {}
pub type StorageResult<T> = Result<T, Report<StorageError>>;
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Occupied {
Value { level: String },
Branch { level: String },
}
impl fmt::Display for Occupied {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Occupied::Value { level } => {
write!(
f,
"`{level}` holds a value, so nothing can be stored under it"
)
}
Occupied::Branch { level } => write!(
f,
"`{level}` holds values under it, so a value cannot be stored at it"
),
}
}
}
impl Error for Occupied {}
pub fn one_line<C: fmt::Display + Send + Sync + 'static>(report: &Report<C>) -> String {
report
.frames()
.filter_map(|frame| match frame.kind() {
error_stack::FrameKind::Context(context) => Some(context.to_string()),
error_stack::FrameKind::Attachment(_) => None,
})
.collect::<Vec<_>>()
.join(" <- ")
}
#[derive(Debug)]
pub struct Caused {
said: String,
under: Option<Box<Caused>>,
}
impl Caused {
pub fn under<C>(report: &Report<C>) -> Option<Self> {
let mut links: Vec<Link> = Vec::new();
let mut waiting: Vec<String> = Vec::new();
for frame in report.frames() {
match frame.kind() {
error_stack::FrameKind::Context(context) => links.push(Link {
said: Some(context.to_string()),
carried: std::mem::take(&mut waiting),
}),
error_stack::FrameKind::Attachment(error_stack::AttachmentKind::Printable(
shown,
)) => waiting.push(shown.to_string()),
_ => {}
}
}
match links.len() {
0 => {}
1 => links[0].said = None,
_ => {
let outermost = links.remove(0);
let mut carried = outermost.carried;
carried.append(&mut links[0].carried);
links[0].carried = carried;
}
}
links
.into_iter()
.rev()
.filter(|link| !link.is_empty())
.fold(None, |under, link| {
Some(Caused {
said: link.spelled(),
under: under.map(Box::new),
})
})
}
}
struct Link {
said: Option<String>,
carried: Vec<String>,
}
impl Link {
fn is_empty(&self) -> bool {
self.said.is_none() && self.carried.is_empty()
}
fn spelled(self) -> String {
match (self.said, self.carried.is_empty()) {
(Some(said), true) => said,
(Some(said), false) => format!("{said} ({})", self.carried.join("; ")),
(None, _) => self.carried.join("; "),
}
}
}
impl fmt::Display for Caused {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.said)
}
}
impl Error for Caused {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.under.as_deref().map(|under| under as &dyn Error)
}
}
pub fn spelled<E>(why: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result
where
E: Error + ?Sized,
{
write!(f, "{why}")?;
let mut under = why.source();
if under.is_some() {
f.write_str("\n\nCaused by:")?;
}
while let Some(step) = under {
write!(f, "\n {step}")?;
under = step.source();
}
Ok(())
}
pub struct Because {
why: Report<StorageError>,
chain: std::sync::OnceLock<Option<Caused>>,
}
impl Because {
pub fn new(why: Report<StorageError>) -> Self {
Self {
why,
chain: std::sync::OnceLock::new(),
}
}
pub fn into_report(self) -> Report<StorageError> {
self.why
}
pub fn caused(&self) -> Option<&Caused> {
self.chain.get_or_init(|| Caused::under(&self.why)).as_ref()
}
pub fn explain(&self) -> String {
format!("{:?}", self.why)
}
}
impl std::ops::Deref for Because {
type Target = Report<StorageError>;
fn deref(&self) -> &Self::Target {
&self.why
}
}
impl fmt::Debug for Because {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.why, f)
}
}
impl fmt::Display for Because {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.why, f)
}
}
impl From<Report<StorageError>> for Because {
fn from(why: Report<StorageError>) -> Self {
Self::new(why)
}
}
pub trait IntoStorageReport {
fn into_report(self) -> Report<StorageError>;
}
impl IntoStorageReport for crate::path::StorePathError {
fn into_report(self) -> Report<StorageError> {
Report::new(self).change_context(StorageError::Path)
}
}