use std::any::type_name;
use std::fmt;
use std::fmt::{Debug, Display};
use std::io;
use std::panic::Location;
use std::result::Result as StdResult;
use derive_more::Display;
#[derive(Debug, thiserror::Error)]
pub enum Error<E: Display> {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Parse error: {0}")]
Parse(#[from] Report<E>),
}
#[derive(thiserror::Error)]
#[error("{error}")]
pub struct Report<E> {
#[source]
error: E,
inner: Box<ReportInner>,
}
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "extra unparsed input")]
pub struct ExtraUnparsedInput;
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing value of type `{}`", _0)]
pub struct WhileParsingType(&'static str);
pub type Result<T, E> = StdResult<T, Report<E>>;
pub trait ResultExt: Sized {
#[track_caller]
fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self;
#[track_caller]
fn while_parsing_type(self) -> 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 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 WhileParsingType {
pub fn new<T: ?Sized>() -> Self {
Self(type_name::<T>())
}
}
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> {
#[track_caller]
fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self {
match self {
Ok(value) => Ok(value),
Err(err) => Err(err.attach_printable(printable)),
}
}
#[track_caller]
fn while_parsing_type(self) -> Self {
self.attach_printable(WhileParsingType::new::<T>())
}
}
impl<T, E: Display> ResultExt for StdResult<T, Error<E>> {
#[track_caller]
fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self {
match self {
Err(Error::Io(err)) => Err(Error::Io(err)),
Err(Error::Parse(err)) => Err(Error::Parse(err.attach_printable(printable))),
_ => self,
}
}
#[track_caller]
fn while_parsing_type(self) -> Self {
self.attach_printable(WhileParsingType::new::<T>())
}
}
#[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));
}
}