Skip to content

Commit

Permalink
TypedReport: a dream that can never be :(
Browse files Browse the repository at this point in the history
Basically, the idea was to have something like 'Report' that would
be strongly typed, except I ran into the exact same reason that anyhow
doesn't do this: the blanket `From` impl :(

So this is, in essence, impossible to do ergonomically, but I'm keeping
this around for future reference.
  • Loading branch information
zkat committed Nov 26, 2024
1 parent 01564e0 commit 3315cd3
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
// #![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
#![warn(unreachable_pub, rust_2018_idioms)]
#![allow(unexpected_cfgs)]
//! You run miette? You run her code like the software? Oh. Oh! Error code for
Expand Down Expand Up @@ -804,3 +804,6 @@ mod named_source;
mod panic;
mod protocol;
mod source_impls;
mod typed_report;

pub use typed_report::*;
68 changes: 68 additions & 0 deletions src/typed_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::any::{Any, TypeId};
use std::backtrace::Backtrace;
use std::error::Error;
use std::ops::Deref;

pub type TypedResult<T, E> = Result<T, TypedReport<E>>;

pub struct TypedReport<T: Error + 'static> {
error: T,
backtrace: Backtrace,
}

impl<T: Error + 'static> TypedReport<T> {
pub fn unwrap(self) -> T {
self.error
}

pub fn inner(&self) -> &T {
self.error.as_ref()
}

pub fn backtrace(&self) -> &Backtrace {
self.backtrace.as_ref()
}
}

impl<T: Error + 'static> Deref for TypedReport<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
self.error.as_ref().unwrap()
}
}

impl<T, U, V> From<U> for TypedReport<T>
where
T: Any + Error + 'static,
U: Any + Error + 'static,
V: Any + Error + 'static + From<T>,
{
fn from(value: U) -> Self {
let val = if TypeId::of::<U>() == TypeId::of::<TypedReport<V>>() {
value.unwrap().into()
} else {
value
};
TypedReport {
error: val,
backtrace: Backtrace::capture(),
}
}
}

impl<T, U> From<TypedReport<T>> for TypedReport<U>
where
T: Any + Error + 'static,
U: Any + Error + 'static + From<T>,
{
fn from(value: TypedReport<T>) -> Self {
if TypeId::of::<T>() == TypeId::of::<U>() {
return value
}
TypedReport {
error: value.error.take().map(|x| x.into()),
backtrace: value.backtrace.take(),
}
}
}
44 changes: 44 additions & 0 deletions tests/typed_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use miette::{Diagnostic, TypedReport};
use thiserror::Error;

#[test]
fn into_typed() {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(error::on::base))]
struct MyBad {
#[source_code]
src: String,
#[label("this bit here")]
highlight: (usize, usize),
}

let src = "source\n text\n here".to_string();
let err = MyBad {
src,
highlight: (9, 4),
};
let typed_err: TypedReport<_> = err.into();
assert_eq!(typed_err.code().unwrap().to_string(), "error::on::base");
}

#[test]
fn backtrace_retention() {
#[derive(Debug, Error)]
#[error("oops!")]
struct MyBad;

#[derive(Debug, Error)]
#[error("also fail: {0}")]
struct AlsoBad(#[from] MyBad);

let typed_err: TypedReport<_> = MyBad.into();
let backtrace1 = typed_err.backtrace().to_string();

let other: TypedReport<AlsoBad> = typed_err.into();

let backtrace2 = other.backtrace().to_string();

assert_eq!(backtrace1, backtrace2);
}

0 comments on commit 3315cd3

Please sign in to comment.