use std::fmt;
use std::fmt::{Debug, Display};
use std::io;
use std::panic::Location;
use crate::parse::ParseError;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Parse error: {0}")]
Parse(#[from] Report<ParseError>),
}
#[derive(thiserror::Error)]
#[error("{error}")]
pub struct Report<E> {
#[source]
error: E,
inner: Box<ReportInner>,
}
pub(crate) type Result<T, E> = std::result::Result<T, Report<E>>;
pub(crate) trait ResultExt: Sized {
#[track_caller]
fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self;
}
struct ReportInner {
location: &'static Location<'static>,
stack: ReportStack,
}
#[derive(Default)]
struct ReportStack {
entries: Vec<ReportEntry>,
}
#[derive(derive_more::Display)]
#[display(fmt = "{message} at {location}")]
struct ReportEntry {
message: Box<dyn Display + Send + Sync + 'static>,
location: &'static Location<'static>,
}
impl<E> Report<E> {
pub fn get_ref(&self) -> &E {
&self.error
}
pub fn into_inner(self) -> E {
self.error
}
#[track_caller]
pub(crate) fn attach_printable<P: Display + Send + Sync + 'static>(mut self, message: P) -> Self {
let entry = ReportEntry { message: Box::new(message), location: Location::caller() };
self.inner.stack.entries.push(entry);
self
}
}
impl<E> From<E> for Report<E> {
#[track_caller]
fn from(error: E) -> Self {
Self { error, inner: Box::new(ReportInner { location: Location::caller(), stack: Default::default() }) }
}
}
impl<E: Display> Debug for Report<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { error, inner } = self;
let ReportInner { location, stack } = &**inner;
write!(f, "{error} at {location}\n{stack}")
}
}
impl Display for ReportStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for entry in &self.entries[..self.entries.len().saturating_sub(1)] {
writeln!(f, " - {entry}")?;
}
if let Some(entry) = self.entries.last() {
write!(f, " - {entry}")?;
}
Ok(())
}
}
impl<T, E> ResultExt for Result<T, E> {
fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self {
self.map_err(|err| err.attach_printable(printable))
}
}
#[cfg(test)]
mod test {
use super::*;
const TEST_ERROR_DISPLAY: &str = "test error display";
const TEST_ATTACHMENT: &str = "test attachment";
#[derive(Debug, thiserror::Error)]
#[error("{}", TEST_ERROR_DISPLAY)]
struct TestError;
fn test_report() -> Report<TestError> {
report_attach!(TestError, TEST_ATTACHMENT)
}
#[test]
fn test_report_display() {
assert_eq!(test_report().to_string(), TEST_ERROR_DISPLAY);
}
#[test]
fn test_report_debug() {
let report_debug = format!("{report:?}", report = test_report());
assert!(report_debug.starts_with(TEST_ERROR_DISPLAY));
assert!(report_debug.contains(TEST_ATTACHMENT));
}
}