mirror of https://github.com/zkat/miette.git
feat(report): anyhow-ify DiagnosticReport (#35)
This PR overhauls the toplevel/main experience for `miette`. It adds a new `Report` type based on `eyre::Report` and overhauls various types to fit into this model, as well as prepare for some [future changes in Rust](https://github.com/nrc/rfcs/pull/1) that will make it possible to integrate `miette` directly with crates like `eyre` instead of having to use this specific `Report`. As such, this PR is a major breaking change, especially for anyone using `DiagnosticReport` and company. BREAKING CHANGES: * `DiagnosticReport` is now just `Report`, and is a different, `eyre::Report`-like type. * `DiagnosticResult` is now just `Result`. * `.into_diagnostic()` now just transforms the error into a `Report`. * `DiagnosticReportPrinter` has been replaced with `ReportHandler` * `set_printer` has been replaced by `set_hook` * `code` is now optional. * `.into_diagnostic()` no longer takes a `code` argument. * `#[diagnostic]` is now optional when deriving `Diagnostic`.
This commit is contained in:
parent
0427c9f966
commit
3f9da04b86
|
|
@ -13,7 +13,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
|
|||
|
||||
[dependencies]
|
||||
thiserror = "1.0.26"
|
||||
miette-derive = { version = "=1.1.0", path = "miette-derive" }
|
||||
miette-derive = { path = "miette-derive", version = "=1.1.0" }
|
||||
once_cell = "1.8.0"
|
||||
owo-colors = "2.0.0"
|
||||
atty = "0.2.14"
|
||||
|
|
@ -22,5 +22,12 @@ ci_info = "0.14.2"
|
|||
[dev-dependencies]
|
||||
semver = "1.0.4"
|
||||
|
||||
# Eyre devdeps
|
||||
futures = { version = "0.3", default-features = false }
|
||||
indenter = "0.3.0"
|
||||
rustversion = "1.0"
|
||||
trybuild = { version = "1.0.19", features = ["diff"] }
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
|
||||
[workspace]
|
||||
members = ["miette-derive"]
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ args = ["--prepend", "CHANGELOG.md", "-u", "--tag", "${@}"]
|
|||
workspace=false
|
||||
install_crate="cargo-release"
|
||||
command = "cargo"
|
||||
args = ["release", "--tag-prefix", "", "--workspace", "${@}"]
|
||||
args = ["release", "--workspace", "${@}"]
|
||||
|
|
|
|||
57
README.md
57
README.md
|
|
@ -45,11 +45,11 @@ diagnostic error code: ruget::api::bad_json
|
|||
- Unique error codes on every [Diagnostic].
|
||||
- Custom links to get more details on error codes.
|
||||
- Super handy derive macro for defining diagnostic metadata.
|
||||
- Lightweight [`anyhow`](https://docs.rs/anyhow)/[`eyre`](https://docs.rs/eyre)-style error wrapper type, [DiagnosticReport],
|
||||
- [`anyhow`](https://docs.rs/anyhow)/[`eyre`](https://docs.rs/eyre)-compatible error wrapper type, [Report],
|
||||
which can be returned from `main`.
|
||||
- Generic support for arbitrary [Source]s for snippet data, with default support for `String`s included.
|
||||
|
||||
The `miette` crate also comes bundled with a default [DiagnosticReportPrinter] with the following features:
|
||||
The `miette` crate also comes bundled with a default [ReportHandler] with the following features:
|
||||
|
||||
- Fancy graphical [diagnostic output](#about), using ANSI/Unicode text
|
||||
- single- and multi-line highlighting support
|
||||
|
|
@ -97,12 +97,12 @@ struct MyBad {
|
|||
/*
|
||||
Now let's define a function!
|
||||
|
||||
Use this DiagnosticResult type (or its expanded version) as the return type
|
||||
Use this Result type (or its expanded version) as the return type
|
||||
throughout your app (but NOT your libraries! Those should always return concrete
|
||||
types!).
|
||||
*/
|
||||
use miette::{DiagnosticResult, NamedSource};
|
||||
fn this_fails() -> DiagnosticResult<()> {
|
||||
use miette::{Result, NamedSource};
|
||||
fn this_fails() -> Result<()> {
|
||||
// You can use plain strings as a `Source`, or anything that implements
|
||||
// the one-method `Source` trait.
|
||||
let src = "source\n text\n here".to_string();
|
||||
|
|
@ -118,12 +118,12 @@ fn this_fails() -> DiagnosticResult<()> {
|
|||
}
|
||||
|
||||
/*
|
||||
Now to get everything printed nicely, just return a DiagnosticResult<()>
|
||||
Now to get everything printed nicely, just return a Result<()>
|
||||
and you're all set!
|
||||
|
||||
Note: You can swap out the default reporter for a custom one using `miette::set_reporter()`
|
||||
*/
|
||||
fn pretend_this_is_main() -> DiagnosticResult<()> {
|
||||
fn pretend_this_is_main() -> Result<()> {
|
||||
// kaboom~
|
||||
this_fails()?;
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ pub enum MyLibError {
|
|||
|
||||
Then, return this error type from all your fallible public APIs. It's a best
|
||||
practice to wrap any "external" error types in your error `enum` instead of
|
||||
using something like [eyre](https://docs.rs/eyre) in a library.
|
||||
using something like [Report] in a library.
|
||||
|
||||
### ... in application code
|
||||
|
||||
|
|
@ -191,33 +191,50 @@ Application code tends to work a little differently than libraries. You don't
|
|||
always need or care to define dedicated error wrappers for errors coming from
|
||||
external libraries and tools.
|
||||
|
||||
For this situation, `miette` includes two tools: [DiagnosticReport] and
|
||||
For this situation, `miette` includes two tools: [Report] and
|
||||
[IntoDiagnostic]. They work in tandem to make it easy to convert regular
|
||||
`std::error::Error`s into [Diagnostic]s. Additionally, there's a
|
||||
[DiagnosticResult] type alias that you can use to be more terse:
|
||||
[Result] type alias that you can use to be more terse.
|
||||
|
||||
When dealing with non-`Diagnostic` types, you'll want to `.into_diagnostic()`
|
||||
them:
|
||||
|
||||
```rust
|
||||
// my_app/lib/my_internal_file.rs
|
||||
use miette::{IntoDiagnostic, DiagnosticResult};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use semver::Version;
|
||||
|
||||
pub fn some_tool() -> DiagnosticResult<Version> {
|
||||
Ok("1.2.x".parse().into_diagnostic("my_app::semver::parse_error")?)
|
||||
pub fn some_tool() -> Result<Version> {
|
||||
Ok("1.2.x".parse().into_diagnostic()?)
|
||||
}
|
||||
```
|
||||
|
||||
`miette` also includes an `anyhow`/`eyre`-style `Context`/`WrapErr` traits that
|
||||
you can import to add ad-hoc context messages to your `Diagnostic`s, as well,
|
||||
though you'll still need to use `.into_diagnostic()` to make use of it:
|
||||
|
||||
```rust
|
||||
// my_app/lib/my_internal_file.rs
|
||||
use miette::{IntoDiagnostic, Result, WrapErr};
|
||||
use semver::Version;
|
||||
|
||||
pub fn some_tool() -> Result<Version> {
|
||||
Ok("1.2.x".parse().into_diagnostic().wrap_err("Parsing this tool's semver version failed.")?)
|
||||
}
|
||||
```
|
||||
|
||||
### ... in `main()`
|
||||
|
||||
`main()` is just like any other part of your application-internal code. Use
|
||||
`DiagnosticResult` as your return value, and it will pretty-print your
|
||||
`Result` as your return value, and it will pretty-print your
|
||||
diagnostics automatically.
|
||||
|
||||
```rust
|
||||
use miette::{DiagnosticResult, IntoDiagnostic};
|
||||
use miette::{Result, IntoDiagnostic};
|
||||
use semver::Version;
|
||||
|
||||
fn pretend_this_is_main() -> DiagnosticResult<()> {
|
||||
let version: Version = "1.2.x".parse().into_diagnostic("my_app::semver::parse_error")?;
|
||||
fn pretend_this_is_main() -> Result<()> {
|
||||
let version: Version = "1.2.x".parse().into_diagnostic()?;
|
||||
println!("{}", version);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -249,7 +266,7 @@ use thiserror::Error;
|
|||
#[diagnostic(
|
||||
code(my_app::my_error),
|
||||
// You can do formatting!
|
||||
url("https://my_website.com/error_codes#{}", self.code())
|
||||
url("https://my_website.com/error_codes#{}", self.code().unwrap())
|
||||
)]
|
||||
struct MyErr;
|
||||
```
|
||||
|
|
@ -330,7 +347,7 @@ pub struct MyErrorType {
|
|||
[`color-eyre`](https://crates.io/crates/color-eyre): these two enormously
|
||||
influential error handling libraries have pushed forward the experience of
|
||||
application-level error handling and error reporting. `miette`'s
|
||||
`DiagnosticReport` type is an attempt at a very very rough version of their
|
||||
`Report` type is an attempt at a very very rough version of their
|
||||
`Report` types.
|
||||
- [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
|
||||
for library-level error definitions, and for being the inspiration behind
|
||||
|
|
@ -344,7 +361,7 @@ pub struct MyErrorType {
|
|||
|
||||
`miette` is released to the Rust community under the [Apache license 2.0](./LICENSE).
|
||||
|
||||
It also includes some code taken from [`eyre`](https://github.com/yaahc/eyre),
|
||||
It also includes code taken from [`eyre`](https://github.com/yaahc/eyre),
|
||||
and some from [`thiserror`](https://github.com/dtolnay/thiserror), also under
|
||||
the Apache License. Some code is taken from
|
||||
[`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||
|
|
|
|||
|
|
@ -56,29 +56,30 @@ impl Code {
|
|||
}| {
|
||||
match args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
forward_to_single_field_variant(ident, fields, quote! { code() })
|
||||
Some(forward_to_single_field_variant(ident, fields, quote! { code() }))
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { code, .. }) => {
|
||||
let code = &code.0;
|
||||
match fields {
|
||||
let code = &code.as_ref()?.0;
|
||||
Some(match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
quote! { Self::#ident { .. } => std::boxed::Box::new(#code), }
|
||||
quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
syn::Fields::Unnamed(_) => {
|
||||
quote! { Self::#ident(..) => std::boxed::Box::new(#code), }
|
||||
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
syn::Fields::Unit => {
|
||||
quote! { Self::#ident => std::boxed::Box::new(#code), }
|
||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
Some(quote! {
|
||||
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
|
||||
fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
match self {
|
||||
#(#code_pairs)*
|
||||
_ => std::option::Option::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -87,8 +88,8 @@ impl Code {
|
|||
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||
let code = &self.0;
|
||||
Some(quote! {
|
||||
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
|
||||
std::boxed::Box::new(#code)
|
||||
fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
std::option::Option::Some(std::boxed::Box::new(#code))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ pub enum DiagnosticDefArgs {
|
|||
Concrete(DiagnosticConcreteArgs),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DiagnosticConcreteArgs {
|
||||
pub code: Code,
|
||||
pub code: Option<Code>,
|
||||
pub severity: Option<Severity>,
|
||||
pub help: Option<Help>,
|
||||
pub snippets: Option<Snippets>,
|
||||
|
|
@ -44,7 +45,7 @@ pub struct DiagnosticConcreteArgs {
|
|||
|
||||
impl DiagnosticConcreteArgs {
|
||||
fn parse(
|
||||
ident: &syn::Ident,
|
||||
_ident: &syn::Ident,
|
||||
fields: &syn::Fields,
|
||||
attr: &syn::Attribute,
|
||||
args: impl Iterator<Item = DiagnosticArg>,
|
||||
|
|
@ -75,8 +76,7 @@ impl DiagnosticConcreteArgs {
|
|||
}
|
||||
let snippets = Snippets::from_fields(fields)?;
|
||||
let concrete = DiagnosticConcreteArgs {
|
||||
code: code
|
||||
.ok_or_else(|| syn::Error::new(ident.span(), "Diagnostic code is required."))?,
|
||||
code,
|
||||
help,
|
||||
severity,
|
||||
snippets,
|
||||
|
|
@ -132,11 +132,12 @@ impl Diagnostic {
|
|||
args,
|
||||
}
|
||||
} else {
|
||||
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||
return Err(syn::Error::new(
|
||||
input.ident.span(),
|
||||
"#[diagnostic] attribute is required when deriving Diagnostic.",
|
||||
));
|
||||
Diagnostic::Struct {
|
||||
fields: data_struct.fields,
|
||||
ident: input.ident,
|
||||
generics: input.generics,
|
||||
args: DiagnosticDefArgs::Concrete(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
|
||||
|
|
@ -206,7 +207,7 @@ impl Diagnostic {
|
|||
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
|
||||
fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#matcher
|
||||
#field_name.code()
|
||||
}
|
||||
|
|
@ -230,7 +231,7 @@ impl Diagnostic {
|
|||
}
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(concrete) => {
|
||||
let code_body = concrete.code.gen_struct();
|
||||
let code_body = concrete.code.as_ref().and_then(|x| x.gen_struct());
|
||||
let help_body = concrete.help.as_ref().and_then(|x| x.gen_struct(fields));
|
||||
let sev_body = concrete.severity.as_ref().and_then(|x| x.gen_struct());
|
||||
let snip_body = concrete
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ impl Severity {
|
|||
fn severity(&self) -> std::option::Option<miette::Severity> {
|
||||
match self {
|
||||
#(#sev_pairs)*
|
||||
_ => None,
|
||||
_ => std::option::Option::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use ChainState::*;
|
|||
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub(crate) struct Chain<'a> {
|
||||
pub struct Chain<'a> {
|
||||
state: crate::chain::ChainState<'a>,
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ pub(crate) enum ChainState<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Chain<'a> {
|
||||
pub(crate) fn new(head: &'a (dyn StdError + 'static)) -> Self {
|
||||
pub fn new(head: &'a (dyn StdError + 'static)) -> Self {
|
||||
Chain {
|
||||
state: ChainState::Linked { next: Some(head) },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ pub enum MietteError {
|
|||
)]
|
||||
OutOfBounds,
|
||||
|
||||
/// Returned when installing a [crate::DiagnosticReportPrinter] failed.
|
||||
/// Returned when installing a [crate::ReportHandler] failed.
|
||||
/// Typically, this will be because [crate::set_printer] was called twice.
|
||||
#[error("Failed to install DiagnosticReportPrinter")]
|
||||
#[error("Failed to install ReportHandler")]
|
||||
#[diagnostic(code(miette::set_printer_failed), url(docsrs))]
|
||||
SetPrinterFailure,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
use super::error::ContextError;
|
||||
use super::{Report, WrapErr};
|
||||
use core::fmt::{self, Debug, Display, Write};
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use crate::Diagnostic;
|
||||
|
||||
mod ext {
|
||||
use super::*;
|
||||
|
||||
pub trait Diag {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn ext_report<D>(self, msg: D) -> Report
|
||||
where
|
||||
D: Display + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl<E> Diag for E
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
fn ext_report<D>(self, msg: D) -> Report
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
Report::from_msg(msg, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diag for Report {
|
||||
fn ext_report<D>(self, msg: D) -> Report
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.wrap_err(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> WrapErr<T, E> for Result<T, E>
|
||||
where
|
||||
E: ext::Diag + Send + Sync + 'static,
|
||||
{
|
||||
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
match self {
|
||||
Ok(t) => Ok(t),
|
||||
Err(e) => Err(e.ext_report(msg)),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
match self {
|
||||
Ok(t) => Ok(t),
|
||||
Err(e) => Err(e.ext_report(msg())),
|
||||
}
|
||||
}
|
||||
|
||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.wrap_err(msg)
|
||||
}
|
||||
|
||||
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.wrap_err_with(msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, E> Debug for ContextError<D, E>
|
||||
where
|
||||
D: Display,
|
||||
E: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Error")
|
||||
.field("msg", &Quoted(&self.msg))
|
||||
.field("source", &self.error)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, E> Display for ContextError<D, E>
|
||||
where
|
||||
D: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.msg, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, E> StdError for ContextError<D, E>
|
||||
where
|
||||
D: Display,
|
||||
E: StdError + 'static,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
Some(&self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> StdError for ContextError<D, Report>
|
||||
where
|
||||
D: Display,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
Some(self.error.inner.error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, E> Diagnostic for ContextError<D, E>
|
||||
where
|
||||
D: Display,
|
||||
E: Diagnostic + 'static,
|
||||
{
|
||||
}
|
||||
|
||||
impl<D> Diagnostic for ContextError<D, Report> where D: Display {}
|
||||
|
||||
struct Quoted<D>(D);
|
||||
|
||||
impl<D> Debug for Quoted<D>
|
||||
where
|
||||
D: Display,
|
||||
{
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_char('"')?;
|
||||
Quoted(&mut *formatter).write_fmt(format_args!("{}", self.0))?;
|
||||
formatter.write_char('"')?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Quoted<&mut fmt::Formatter<'_>> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
Display::fmt(&s.escape_debug(), self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod private {
|
||||
use super::*;
|
||||
|
||||
pub trait Sealed {}
|
||||
|
||||
impl<T, E> Sealed for Result<T, E> where E: ext::Diag {}
|
||||
impl<T> Sealed for Option<T> {}
|
||||
}
|
||||
|
|
@ -0,0 +1,723 @@
|
|||
use core::any::TypeId;
|
||||
use core::fmt::{self, Debug, Display};
|
||||
use core::mem::{self, ManuallyDrop};
|
||||
use core::ptr::{self, NonNull};
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use super::Report;
|
||||
use super::ReportHandler;
|
||||
use crate::chain::Chain;
|
||||
use crate::Diagnostic;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
impl Report {
|
||||
/// Create a new error object from any error type.
|
||||
///
|
||||
/// The error type must be thread safe and `'static`, so that the `Report`
|
||||
/// will be as well.
|
||||
///
|
||||
/// If the error type does not provide a backtrace, a backtrace will be
|
||||
/// created here to ensure that a backtrace exists.
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub fn new<E>(error: E) -> Self
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
Report::from_std(error)
|
||||
}
|
||||
|
||||
/// Create a new error object from a printable error message.
|
||||
///
|
||||
/// If the argument implements std::error::Error, prefer `Report::new`
|
||||
/// instead which preserves the underlying error's cause chain and
|
||||
/// backtrace. If the argument may or may not implement std::error::Error
|
||||
/// now or in the future, use `miette!(err)` which handles either way
|
||||
/// correctly.
|
||||
///
|
||||
/// `Report::msg("...")` is equivalent to `miette!("...")` but occasionally
|
||||
/// convenient in places where a function is preferable over a macro, such
|
||||
/// as iterator or stream combinators:
|
||||
///
|
||||
/// ```
|
||||
/// # mod ffi {
|
||||
/// # pub struct Input;
|
||||
/// # pub struct Output;
|
||||
/// # pub async fn do_some_work(_: Input) -> Result<Output, &'static str> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # use ffi::{Input, Output};
|
||||
/// #
|
||||
/// use miette::{Report, Result};
|
||||
/// use futures::stream::{Stream, StreamExt, TryStreamExt};
|
||||
///
|
||||
/// async fn demo<S>(stream: S) -> Result<Vec<Output>>
|
||||
/// where
|
||||
/// S: Stream<Item = Input>,
|
||||
/// {
|
||||
/// stream
|
||||
/// .then(ffi::do_some_work) // returns Result<Output, &str>
|
||||
/// .map_err(Report::msg)
|
||||
/// .try_collect()
|
||||
/// .await
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub fn msg<M>(message: M) -> Self
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
Report::from_adhoc(message)
|
||||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub(crate) fn from_std<E>(error: E) -> Self
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
let vtable = &ErrorVTable {
|
||||
object_drop: object_drop::<E>,
|
||||
object_ref: object_ref::<E>,
|
||||
object_mut: object_mut::<E>,
|
||||
object_ref_stderr: object_ref_stderr::<E>,
|
||||
object_boxed: object_boxed::<E>,
|
||||
object_downcast: object_downcast::<E>,
|
||||
object_drop_rest: object_drop_front::<E>,
|
||||
};
|
||||
|
||||
// Safety: passing vtable that operates on the right type E.
|
||||
let handler = Some(super::capture_handler(&error));
|
||||
|
||||
unsafe { Report::construct(error, vtable, handler) }
|
||||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub(crate) fn from_adhoc<M>(message: M) -> Self
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
use super::wrapper::MessageError;
|
||||
let error: MessageError<M> = MessageError(message);
|
||||
let vtable = &ErrorVTable {
|
||||
object_drop: object_drop::<MessageError<M>>,
|
||||
object_ref: object_ref::<MessageError<M>>,
|
||||
object_mut: object_mut::<MessageError<M>>,
|
||||
object_ref_stderr: object_ref_stderr::<MessageError<M>>,
|
||||
object_boxed: object_boxed::<MessageError<M>>,
|
||||
object_downcast: object_downcast::<M>,
|
||||
object_drop_rest: object_drop_front::<M>,
|
||||
};
|
||||
|
||||
// Safety: MessageError is repr(transparent) so it is okay for the
|
||||
// vtable to allow casting the MessageError<M> to M.
|
||||
let handler = Some(super::capture_handler(&error));
|
||||
|
||||
unsafe { Report::construct(error, vtable, handler) }
|
||||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
let error: ContextError<D, E> = ContextError { msg, error };
|
||||
|
||||
let vtable = &ErrorVTable {
|
||||
object_drop: object_drop::<ContextError<D, E>>,
|
||||
object_ref: object_ref::<ContextError<D, E>>,
|
||||
object_mut: object_mut::<ContextError<D, E>>,
|
||||
object_ref_stderr: object_ref_stderr::<ContextError<D, E>>,
|
||||
object_boxed: object_boxed::<ContextError<D, E>>,
|
||||
object_downcast: context_downcast::<D, E>,
|
||||
object_drop_rest: context_drop_rest::<D, E>,
|
||||
};
|
||||
|
||||
// Safety: passing vtable that operates on the right type.
|
||||
let handler = Some(super::capture_handler(&error));
|
||||
|
||||
unsafe { Report::construct(error, vtable, handler) }
|
||||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
|
||||
use super::wrapper::BoxedError;
|
||||
let error = BoxedError(error);
|
||||
let handler = Some(super::capture_handler(&error));
|
||||
|
||||
let vtable = &ErrorVTable {
|
||||
object_drop: object_drop::<BoxedError>,
|
||||
object_ref: object_ref::<BoxedError>,
|
||||
object_mut: object_mut::<BoxedError>,
|
||||
object_ref_stderr: object_ref_stderr::<BoxedError>,
|
||||
object_boxed: object_boxed::<BoxedError>,
|
||||
object_downcast: object_downcast::<Box<dyn Diagnostic + Send + Sync>>,
|
||||
object_drop_rest: object_drop_front::<Box<dyn Diagnostic + Send + Sync>>,
|
||||
};
|
||||
|
||||
// Safety: BoxedError is repr(transparent) so it is okay for the vtable
|
||||
// to allow casting to Box<dyn StdError + Send + Sync>.
|
||||
unsafe { Report::construct(error, vtable, handler) }
|
||||
}
|
||||
|
||||
// Takes backtrace as argument rather than capturing it here so that the
|
||||
// user sees one fewer layer of wrapping noise in the backtrace.
|
||||
//
|
||||
// Unsafe because the given vtable must have sensible behavior on the error
|
||||
// value of type E.
|
||||
unsafe fn construct<E>(
|
||||
error: E,
|
||||
vtable: &'static ErrorVTable,
|
||||
handler: Option<Box<dyn ReportHandler>>,
|
||||
) -> Self
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
let inner = Box::new(ErrorImpl {
|
||||
vtable,
|
||||
handler,
|
||||
_object: error,
|
||||
});
|
||||
// Erase the concrete type of E from the compile-time type system. This
|
||||
// is equivalent to the safe unsize coercion from Box<ErrorImpl<E>> to
|
||||
// Box<ErrorImpl<dyn StdError + Send + Sync + 'static>> except that the
|
||||
// result is a thin pointer. The necessary behavior for manipulating the
|
||||
// underlying ErrorImpl<E> is preserved in the vtable provided by the
|
||||
// caller rather than a builtin fat pointer vtable.
|
||||
let erased = mem::transmute::<Box<ErrorImpl<E>>, Box<ErrorImpl<()>>>(inner);
|
||||
let inner = ManuallyDrop::new(erased);
|
||||
Report { inner }
|
||||
}
|
||||
|
||||
/// Create a new error from an error message to wrap the existing error.
|
||||
///
|
||||
/// For attaching a higher level error message to a `Result` as it is propagated, the
|
||||
/// [crate::WrapErr] extension trait may be more convenient than this function.
|
||||
///
|
||||
/// The primary reason to use `error.wrap_err(...)` instead of `result.wrap_err(...)` via the
|
||||
/// `WrapErr` trait would be if the message needs to depend on some data held by the underlying
|
||||
/// error:
|
||||
///
|
||||
pub fn wrap_err<D>(mut self, msg: D) -> Self
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
let handler = self.inner.handler.take();
|
||||
let error: ContextError<D, Report> = ContextError { msg, error: self };
|
||||
|
||||
let vtable = &ErrorVTable {
|
||||
object_drop: object_drop::<ContextError<D, Report>>,
|
||||
object_ref: object_ref::<ContextError<D, Report>>,
|
||||
object_mut: object_mut::<ContextError<D, Report>>,
|
||||
object_ref_stderr: object_ref_stderr::<ContextError<D, Report>>,
|
||||
object_boxed: object_boxed::<ContextError<D, Report>>,
|
||||
object_downcast: context_chain_downcast::<D>,
|
||||
object_drop_rest: context_chain_drop_rest::<D>,
|
||||
};
|
||||
|
||||
// Safety: passing vtable that operates on the right type.
|
||||
unsafe { Report::construct(error, vtable, handler) }
|
||||
}
|
||||
|
||||
/// An iterator of the chain of source errors contained by this Report.
|
||||
///
|
||||
/// This iterator will visit every error in the cause chain of this error
|
||||
/// object, beginning with the error that this error object was created
|
||||
/// from.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use miette::Report;
|
||||
/// use std::io;
|
||||
///
|
||||
/// pub fn underlying_io_error_kind(error: &Report) -> Option<io::ErrorKind> {
|
||||
/// for cause in error.chain() {
|
||||
/// if let Some(io_error) = cause.downcast_ref::<io::Error>() {
|
||||
/// return Some(io_error.kind());
|
||||
/// }
|
||||
/// }
|
||||
/// None
|
||||
/// }
|
||||
/// ```
|
||||
pub fn chain(&self) -> Chain<'_> {
|
||||
self.inner.chain()
|
||||
}
|
||||
|
||||
/// The lowest level cause of this error — this error's cause's
|
||||
/// cause's cause etc.
|
||||
///
|
||||
/// The root cause is the last error in the iterator produced by
|
||||
/// [`chain()`][Report::chain].
|
||||
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
|
||||
let mut chain = self.chain();
|
||||
let mut root_cause = chain.next().unwrap();
|
||||
for cause in chain {
|
||||
root_cause = cause;
|
||||
}
|
||||
root_cause
|
||||
}
|
||||
|
||||
/// Returns true if `E` is the type held by this error object.
|
||||
///
|
||||
/// For errors constructed from messages, this method returns true if `E` matches the type of
|
||||
/// the message `D` **or** the type of the error on which the message has been attached. For
|
||||
/// details about the interaction between message and downcasting, [see here].
|
||||
///
|
||||
/// [see here]: trait.WrapErr.html#effect-on-downcasting
|
||||
pub fn is<E>(&self) -> bool
|
||||
where
|
||||
E: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
self.downcast_ref::<E>().is_some()
|
||||
}
|
||||
|
||||
/// Attempt to downcast the error object to a concrete type.
|
||||
pub fn downcast<E>(self) -> Result<E, Self>
|
||||
where
|
||||
E: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
let target = TypeId::of::<E>();
|
||||
unsafe {
|
||||
// Use vtable to find NonNull<()> which points to a value of type E
|
||||
// somewhere inside the data structure.
|
||||
let addr = match (self.inner.vtable.object_downcast)(&self.inner, target) {
|
||||
Some(addr) => addr,
|
||||
None => return Err(self),
|
||||
};
|
||||
|
||||
// Prepare to read E out of the data structure. We'll drop the rest
|
||||
// of the data structure separately so that E is not dropped.
|
||||
let outer = ManuallyDrop::new(self);
|
||||
|
||||
// Read E from where the vtable found it.
|
||||
let error = ptr::read(addr.cast::<E>().as_ptr());
|
||||
|
||||
// Read Box<ErrorImpl<()>> from self. Can't move it out because
|
||||
// Report has a Drop impl which we want to not run.
|
||||
let inner = ptr::read(&outer.inner);
|
||||
let erased = ManuallyDrop::into_inner(inner);
|
||||
|
||||
// Drop rest of the data structure outside of E.
|
||||
(erased.vtable.object_drop_rest)(erased, target);
|
||||
|
||||
Ok(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcast this error object by reference.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::{Report, miette};
|
||||
/// # use std::fmt::{self, Display};
|
||||
/// # use std::task::Poll;
|
||||
/// #
|
||||
/// # #[derive(Debug)]
|
||||
/// # enum DataStoreError {
|
||||
/// # Censored(()),
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl Display for DataStoreError {
|
||||
/// # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl std::error::Error for DataStoreError {}
|
||||
/// #
|
||||
/// # const REDACTED_CONTENT: () = ();
|
||||
/// #
|
||||
/// # let error: Report = miette!("...");
|
||||
/// # let root_cause = &error;
|
||||
/// #
|
||||
/// # let ret =
|
||||
/// // If the error was caused by redaction, then return a tombstone instead
|
||||
/// // of the content.
|
||||
/// match root_cause.downcast_ref::<DataStoreError>() {
|
||||
/// Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
|
||||
/// None => Err(error),
|
||||
/// }
|
||||
/// # ;
|
||||
/// ```
|
||||
pub fn downcast_ref<E>(&self) -> Option<&E>
|
||||
where
|
||||
E: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
let target = TypeId::of::<E>();
|
||||
unsafe {
|
||||
// Use vtable to find NonNull<()> which points to a value of type E
|
||||
// somewhere inside the data structure.
|
||||
let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?;
|
||||
Some(&*addr.cast::<E>().as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcast this error object by mutable reference.
|
||||
pub fn downcast_mut<E>(&mut self) -> Option<&mut E>
|
||||
where
|
||||
E: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
let target = TypeId::of::<E>();
|
||||
unsafe {
|
||||
// Use vtable to find NonNull<()> which points to a value of type E
|
||||
// somewhere inside the data structure.
|
||||
let addr = (self.inner.vtable.object_downcast)(&self.inner, target)?;
|
||||
Some(&mut *addr.cast::<E>().as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the Handler for this Report.
|
||||
pub fn handler(&self) -> &dyn ReportHandler {
|
||||
self.inner.handler.as_ref().unwrap().as_ref()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the Handler for this Report.
|
||||
pub fn handler_mut(&mut self) -> &mut dyn ReportHandler {
|
||||
self.inner.handler.as_mut().unwrap().as_mut()
|
||||
}
|
||||
|
||||
/// Get a reference to the Handler for this Report.
|
||||
#[doc(hidden)]
|
||||
pub fn context(&self) -> &dyn ReportHandler {
|
||||
self.inner.handler.as_ref().unwrap().as_ref()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the Handler for this Report.
|
||||
#[doc(hidden)]
|
||||
pub fn context_mut(&mut self) -> &mut dyn ReportHandler {
|
||||
self.inner.handler.as_mut().unwrap().as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for Report
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn from(error: E) -> Self {
|
||||
Report::from_std(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Report {
|
||||
type Target = dyn Diagnostic + Send + Sync + 'static;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.diagnostic()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Report {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.inner.diagnostic_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Report {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.display(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Report {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.debug(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Report {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Read Box<ErrorImpl<()>> from self.
|
||||
let inner = ptr::read(&self.inner);
|
||||
let erased = ManuallyDrop::into_inner(inner);
|
||||
|
||||
// Invoke the vtable's drop behavior.
|
||||
(erased.vtable.object_drop)(erased);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ErrorVTable {
|
||||
object_drop: unsafe fn(Box<ErrorImpl<()>>),
|
||||
object_ref: unsafe fn(&ErrorImpl<()>) -> &(dyn Diagnostic + Send + Sync + 'static),
|
||||
object_mut: unsafe fn(&mut ErrorImpl<()>) -> &mut (dyn Diagnostic + Send + Sync + 'static),
|
||||
object_ref_stderr: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static),
|
||||
#[allow(clippy::type_complexity)]
|
||||
object_boxed: unsafe fn(Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static>,
|
||||
object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option<NonNull<()>>,
|
||||
object_drop_rest: unsafe fn(Box<ErrorImpl<()>>, TypeId),
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_drop<E>(e: Box<ErrorImpl<()>>) {
|
||||
// Cast back to ErrorImpl<E> so that the allocator receives the correct
|
||||
// Layout to deallocate the Box's memory.
|
||||
let unerased = mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e);
|
||||
drop(unerased);
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_drop_front<E>(e: Box<ErrorImpl<()>>, target: TypeId) {
|
||||
// Drop the fields of ErrorImpl other than E as well as the Box allocation,
|
||||
// without dropping E itself. This is used by downcast after doing a
|
||||
// ptr::read to take ownership of the E.
|
||||
let _ = target;
|
||||
let unerased = mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<ManuallyDrop<E>>>>(e);
|
||||
drop(unerased);
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_ref<E>(e: &ErrorImpl<()>) -> &(dyn Diagnostic + Send + Sync + 'static)
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
// Attach E's native StdError vtable onto a pointer to self._object.
|
||||
&(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_mut<E>(e: &mut ErrorImpl<()>) -> &mut (dyn Diagnostic + Send + Sync + 'static)
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
// Attach E's native StdError vtable onto a pointer to self._object.
|
||||
&mut (*(e as *mut ErrorImpl<()> as *mut ErrorImpl<E>))._object
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_ref_stderr<E>(e: &ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static)
|
||||
where
|
||||
E: StdError + Send + Sync + 'static,
|
||||
{
|
||||
// Attach E's native StdError vtable onto a pointer to self._object.
|
||||
&(*(e as *const ErrorImpl<()> as *const ErrorImpl<E>))._object
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_boxed<E>(e: Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static>
|
||||
where
|
||||
E: Diagnostic + Send + Sync + 'static,
|
||||
{
|
||||
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
|
||||
mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e)
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<E>.
|
||||
unsafe fn object_downcast<E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
|
||||
where
|
||||
E: 'static,
|
||||
{
|
||||
if TypeId::of::<E>() == target {
|
||||
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
|
||||
// pointer to its E field.
|
||||
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<E>;
|
||||
let addr = &(*unerased)._object as *const E as *mut ();
|
||||
Some(NonNull::new_unchecked(addr))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
|
||||
unsafe fn context_downcast<D, E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
|
||||
where
|
||||
D: 'static,
|
||||
E: 'static,
|
||||
{
|
||||
if TypeId::of::<D>() == target {
|
||||
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>;
|
||||
let addr = &(*unerased)._object.msg as *const D as *mut ();
|
||||
Some(NonNull::new_unchecked(addr))
|
||||
} else if TypeId::of::<E>() == target {
|
||||
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, E>>;
|
||||
let addr = &(*unerased)._object.error as *const E as *mut ();
|
||||
Some(NonNull::new_unchecked(addr))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, E>>.
|
||||
unsafe fn context_drop_rest<D, E>(e: Box<ErrorImpl<()>>, target: TypeId)
|
||||
where
|
||||
D: 'static,
|
||||
E: 'static,
|
||||
{
|
||||
// Called after downcasting by value to either the D or the E and doing a
|
||||
// ptr::read to take ownership of that value.
|
||||
if TypeId::of::<D>() == target {
|
||||
let unerased = mem::transmute::<
|
||||
Box<ErrorImpl<()>>,
|
||||
Box<ErrorImpl<ContextError<ManuallyDrop<D>, E>>>,
|
||||
>(e);
|
||||
drop(unerased);
|
||||
} else {
|
||||
let unerased = mem::transmute::<
|
||||
Box<ErrorImpl<()>>,
|
||||
Box<ErrorImpl<ContextError<D, ManuallyDrop<E>>>>,
|
||||
>(e);
|
||||
drop(unerased);
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
|
||||
unsafe fn context_chain_downcast<D>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
|
||||
where
|
||||
D: 'static,
|
||||
{
|
||||
let unerased = e as *const ErrorImpl<()> as *const ErrorImpl<ContextError<D, Report>>;
|
||||
if TypeId::of::<D>() == target {
|
||||
let addr = &(*unerased)._object.msg as *const D as *mut ();
|
||||
Some(NonNull::new_unchecked(addr))
|
||||
} else {
|
||||
// Recurse down the context chain per the inner error's vtable.
|
||||
let source = &(*unerased)._object.error;
|
||||
(source.inner.vtable.object_downcast)(&source.inner, target)
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: requires layout of *e to match ErrorImpl<ContextError<D, Report>>.
|
||||
unsafe fn context_chain_drop_rest<D>(e: Box<ErrorImpl<()>>, target: TypeId)
|
||||
where
|
||||
D: 'static,
|
||||
{
|
||||
// Called after downcasting by value to either the D or one of the causes
|
||||
// and doing a ptr::read to take ownership of that value.
|
||||
if TypeId::of::<D>() == target {
|
||||
let unerased = mem::transmute::<
|
||||
Box<ErrorImpl<()>>,
|
||||
Box<ErrorImpl<ContextError<ManuallyDrop<D>, Report>>>,
|
||||
>(e);
|
||||
// Drop the entire rest of the data structure rooted in the next Report.
|
||||
drop(unerased);
|
||||
} else {
|
||||
let unerased = mem::transmute::<
|
||||
Box<ErrorImpl<()>>,
|
||||
Box<ErrorImpl<ContextError<D, ManuallyDrop<Report>>>>,
|
||||
>(e);
|
||||
// Read out a ManuallyDrop<Box<ErrorImpl<()>>> from the next error.
|
||||
let inner = ptr::read(&unerased._object.error.inner);
|
||||
drop(unerased);
|
||||
let erased = ManuallyDrop::into_inner(inner);
|
||||
// Recursively drop the next error using the same target typeid.
|
||||
(erased.vtable.object_drop_rest)(erased, target);
|
||||
}
|
||||
}
|
||||
|
||||
// repr C to ensure that E remains in the final position.
|
||||
#[repr(C)]
|
||||
pub(crate) struct ErrorImpl<E> {
|
||||
vtable: &'static ErrorVTable,
|
||||
pub(crate) handler: Option<Box<dyn ReportHandler>>,
|
||||
// NOTE: Don't use directly. Use only through vtable. Erased type may have
|
||||
// different alignment.
|
||||
_object: E,
|
||||
}
|
||||
|
||||
// repr C to ensure that ContextError<D, E> has the same layout as
|
||||
// ContextError<ManuallyDrop<D>, E> and ContextError<D, ManuallyDrop<E>>.
|
||||
#[repr(C)]
|
||||
pub(crate) struct ContextError<D, E> {
|
||||
pub(crate) msg: D,
|
||||
pub(crate) error: E,
|
||||
}
|
||||
|
||||
impl<E> ErrorImpl<E> {
|
||||
fn erase(&self) -> &ErrorImpl<()> {
|
||||
// Erase the concrete type of E but preserve the vtable in self.vtable
|
||||
// for manipulating the resulting thin pointer. This is analogous to an
|
||||
// unsize coercion.
|
||||
unsafe { &*(self as *const ErrorImpl<E> as *const ErrorImpl<()>) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorImpl<()> {
|
||||
pub(crate) fn error(&self) -> &(dyn StdError + Send + Sync + 'static) {
|
||||
// Use vtable to attach E's native StdError vtable for the right
|
||||
// original type E.
|
||||
unsafe { &*(self.vtable.object_ref_stderr)(self) }
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostic(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
|
||||
// Use vtable to attach E's native StdError vtable for the right
|
||||
// original type E.
|
||||
unsafe { &*(self.vtable.object_ref)(self) }
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostic_mut(&mut self) -> &mut (dyn Diagnostic + Send + Sync + 'static) {
|
||||
// Use vtable to attach E's native StdError vtable for the right
|
||||
// original type E.
|
||||
unsafe { &mut *(self.vtable.object_mut)(self) }
|
||||
}
|
||||
|
||||
pub(crate) fn chain(&self) -> Chain<'_> {
|
||||
Chain::new(self.error())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> StdError for ErrorImpl<E>
|
||||
where
|
||||
E: StdError,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.erase().diagnostic().source()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Diagnostic for ErrorImpl<E> where E: Diagnostic {}
|
||||
|
||||
impl<E> Debug for ErrorImpl<E>
|
||||
where
|
||||
E: Debug,
|
||||
{
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.erase().debug(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Display for ErrorImpl<E>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.erase().diagnostic(), formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
|
||||
fn from(error: Report) -> Self {
|
||||
let outer = ManuallyDrop::new(error);
|
||||
unsafe {
|
||||
// Read Box<ErrorImpl<()>> from error. Can't move it out because
|
||||
// Report has a Drop impl which we want to not run.
|
||||
let inner = ptr::read(&outer.inner);
|
||||
let erased = ManuallyDrop::into_inner(inner);
|
||||
|
||||
// Use vtable to attach ErrorImpl<E>'s native StdError vtable for
|
||||
// the right original type E.
|
||||
(erased.vtable.object_boxed)(erased)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Report> for Box<dyn Diagnostic + 'static> {
|
||||
fn from(error: Report) -> Self {
|
||||
Box::<dyn Diagnostic + Send + Sync>::from(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<dyn Diagnostic + Send + Sync> for Report {
|
||||
fn as_ref(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<dyn Diagnostic> for Report {
|
||||
fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
|
||||
&**self
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
use super::error::ErrorImpl;
|
||||
use core::fmt;
|
||||
|
||||
impl ErrorImpl<()> {
|
||||
pub(crate) fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.handler
|
||||
.as_ref()
|
||||
.map(|handler| handler.display(self.error(), f))
|
||||
.unwrap_or_else(|| core::fmt::Display::fmt(self.diagnostic(), f))
|
||||
}
|
||||
|
||||
pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.handler
|
||||
.as_ref()
|
||||
.map(|handler| handler.debug(self.diagnostic(), f))
|
||||
.unwrap_or_else(|| core::fmt::Debug::fmt(self.diagnostic(), f))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::{Diagnostic, Report};
|
||||
|
||||
/// Convenience [Diagnostic] that can be used as an "anonymous" wrapper for
|
||||
/// Errors. This is intended to be paired with [IntoDiagnostic].
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
struct DiagnosticError(Box<dyn std::error::Error + Send + Sync + 'static>);
|
||||
impl Diagnostic for DiagnosticError {}
|
||||
|
||||
/**
|
||||
Convenience trait that adds a `.into_diagnostic()` method that converts a type to a `Result<T, DiagnosticError>`.
|
||||
*/
|
||||
pub trait IntoDiagnostic<T, E> {
|
||||
/// Converts [Result]-like types that return regular errors into a
|
||||
/// `Result` that returns a [Diagnostic].
|
||||
fn into_diagnostic(self) -> Result<T, Report>;
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for Result<T, E> {
|
||||
fn into_diagnostic(self) -> Result<T, Report> {
|
||||
self.map_err(|e| DiagnosticError(Box::new(e)).into())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
#![allow(missing_debug_implementations, missing_docs)]
|
||||
// Tagged dispatch mechanism for resolving the behavior of `miette!($expr)`.
|
||||
//
|
||||
// When miette! is given a single expr argument to turn into miette::Report, we
|
||||
// want the resulting Report to pick up the input's implementation of source()
|
||||
// and backtrace() if it has a std::error::Error impl, otherwise require nothing
|
||||
// more than Display and Debug.
|
||||
//
|
||||
// Expressed in terms of specialization, we want something like:
|
||||
//
|
||||
// trait EyreNew {
|
||||
// fn new(self) -> Report;
|
||||
// }
|
||||
//
|
||||
// impl<T> EyreNew for T
|
||||
// where
|
||||
// T: Display + Debug + Send + Sync + 'static,
|
||||
// {
|
||||
// default fn new(self) -> Report {
|
||||
// /* no std error impl */
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<T> EyreNew for T
|
||||
// where
|
||||
// T: std::error::Error + Send + Sync + 'static,
|
||||
// {
|
||||
// fn new(self) -> Report {
|
||||
// /* use std error's source() and backtrace() */
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Since specialization is not stable yet, instead we rely on autoref behavior
|
||||
// of method resolution to perform tagged dispatch. Here we have two traits
|
||||
// AdhocKind and TraitKind that both have an miette_kind() method. AdhocKind is
|
||||
// implemented whether or not the caller's type has a std error impl, while
|
||||
// TraitKind is implemented only when a std error impl does exist. The ambiguity
|
||||
// is resolved by AdhocKind requiring an extra autoref so that it has lower
|
||||
// precedence.
|
||||
//
|
||||
// The miette! macro will set up the call in this form:
|
||||
//
|
||||
// #[allow(unused_imports)]
|
||||
// use $crate::private::{AdhocKind, TraitKind};
|
||||
// let error = $msg;
|
||||
// (&error).miette_kind().new(error)
|
||||
|
||||
use super::Report;
|
||||
use core::fmt::{Debug, Display};
|
||||
|
||||
use crate::Diagnostic;
|
||||
|
||||
pub struct Adhoc;
|
||||
|
||||
pub trait AdhocKind: Sized {
|
||||
#[inline]
|
||||
fn miette_kind(&self) -> Adhoc {
|
||||
Adhoc
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {}
|
||||
|
||||
impl Adhoc {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub fn new<M>(self, message: M) -> Report
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
Report::from_adhoc(message)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Trait;
|
||||
|
||||
pub trait TraitKind: Sized {
|
||||
#[inline]
|
||||
fn miette_kind(&self) -> Trait {
|
||||
Trait
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> TraitKind for E where E: Into<Report> {}
|
||||
|
||||
impl Trait {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub fn new<E>(self, error: E) -> Report
|
||||
where
|
||||
E: Into<Report>,
|
||||
{
|
||||
error.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Boxed;
|
||||
|
||||
pub trait BoxedKind: Sized {
|
||||
#[inline]
|
||||
fn miette_kind(&self) -> Boxed {
|
||||
Boxed
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxedKind for Box<dyn Diagnostic + Send + Sync> {}
|
||||
|
||||
impl Boxed {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
|
||||
Report::from_boxed(error)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
/// Return early with an error.
|
||||
///
|
||||
/// This macro is equivalent to `return Err(From::from($err))`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::{bail, Result};
|
||||
/// #
|
||||
/// # fn has_permission(user: usize, resource: usize) -> bool {
|
||||
/// # true
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let user = 0;
|
||||
/// # let resource = 0;
|
||||
/// #
|
||||
/// if !has_permission(user, resource) {
|
||||
/// bail!("permission denied for accessing {}", resource);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::{bail, Result};
|
||||
/// # use thiserror::Error;
|
||||
/// #
|
||||
/// # const MAX_DEPTH: usize = 1;
|
||||
/// #
|
||||
/// #[derive(Error, Debug)]
|
||||
/// enum ScienceError {
|
||||
/// #[error("recursion limit exceeded")]
|
||||
/// RecursionLimitExceeded,
|
||||
/// # #[error("...")]
|
||||
/// # More = (stringify! {
|
||||
/// ...
|
||||
/// # }, 1).1,
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let depth = 0;
|
||||
/// # let err: &'static dyn std::error::Error = &ScienceError::RecursionLimitExceeded;
|
||||
/// #
|
||||
/// if depth > MAX_DEPTH {
|
||||
/// bail!(ScienceError::RecursionLimitExceeded);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($msg:literal $(,)?) => {
|
||||
return $crate::private::Err($crate::miette!($msg));
|
||||
};
|
||||
($err:expr $(,)?) => {
|
||||
return $crate::private::Err($crate::miette!($err));
|
||||
};
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
return $crate::private::Err($crate::miette!($fmt, $($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
/// Return early with an error if a condition is not satisfied.
|
||||
///
|
||||
/// This macro is equivalent to `if !$cond { return Err(From::from($err)); }`.
|
||||
///
|
||||
/// Analogously to `assert!`, `ensure!` takes a condition and exits the function
|
||||
/// if the condition fails. Unlike `assert!`, `ensure!` returns an `Error`
|
||||
/// rather than panicking.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::{ensure, Result};
|
||||
/// #
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let user = 0;
|
||||
/// #
|
||||
/// ensure!(user == 0, "only user 0 is allowed");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::{ensure, Result};
|
||||
/// # use thiserror::Error;
|
||||
/// #
|
||||
/// # const MAX_DEPTH: usize = 1;
|
||||
/// #
|
||||
/// #[derive(Error, Debug)]
|
||||
/// enum ScienceError {
|
||||
/// #[error("recursion limit exceeded")]
|
||||
/// RecursionLimitExceeded,
|
||||
/// # #[error("...")]
|
||||
/// # More = (stringify! {
|
||||
/// ...
|
||||
/// # }, 1).1,
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let depth = 0;
|
||||
/// #
|
||||
/// ensure!(depth <= MAX_DEPTH, ScienceError::RecursionLimitExceeded);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $msg:literal $(,)?) => {
|
||||
if !$cond {
|
||||
return $crate::private::Err($crate::miette!($msg));
|
||||
}
|
||||
};
|
||||
($cond:expr, $err:expr $(,)?) => {
|
||||
if !$cond {
|
||||
return $crate::private::Err($crate::miette!($err));
|
||||
}
|
||||
};
|
||||
($cond:expr, $fmt:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
return $crate::private::Err($crate::miette!($fmt, $($arg)*));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Construct an ad-hoc error from a string.
|
||||
///
|
||||
/// This evaluates to an `Error`. It can take either just a string, or a format
|
||||
/// string with arguments. It also can take any custom type which implements
|
||||
/// `Debug` and `Display`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # type V = ();
|
||||
/// #
|
||||
/// use miette::{miette, Result};
|
||||
///
|
||||
/// fn lookup(key: &str) -> Result<V> {
|
||||
/// if key.len() != 16 {
|
||||
/// return Err(miette!("key length must be 16 characters, got {:?}", key));
|
||||
/// }
|
||||
///
|
||||
/// // ...
|
||||
/// # Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! miette {
|
||||
($msg:literal $(,)?) => {
|
||||
// Handle $:literal as a special case to make cargo-expanded code more
|
||||
// concise in the common case.
|
||||
$crate::private::new_adhoc($msg)
|
||||
};
|
||||
($err:expr $(,)?) => ({
|
||||
use $crate::private::kind::*;
|
||||
let error = $err;
|
||||
(&error).miette_kind().new(error)
|
||||
});
|
||||
($fmt:expr, $($arg:tt)*) => {
|
||||
$crate::private::new_adhoc(format!($fmt, $($arg)*))
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,490 @@
|
|||
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
||||
#![allow(
|
||||
clippy::needless_doctest_main,
|
||||
clippy::new_ret_no_self,
|
||||
clippy::wrong_self_convention
|
||||
)]
|
||||
use core::fmt::Display;
|
||||
use core::mem::ManuallyDrop;
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use atty::Stream;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub use into_diagnostic::*;
|
||||
#[doc(hidden)]
|
||||
#[allow(unreachable_pub)]
|
||||
pub use Report as ErrReport;
|
||||
/// Compatibility re-export of `Report` for interop with `anyhow`
|
||||
#[allow(unreachable_pub)]
|
||||
pub use Report as Error;
|
||||
#[doc(hidden)]
|
||||
#[allow(unreachable_pub)]
|
||||
pub use ReportHandler as EyreContext;
|
||||
/// Compatibility re-export of `WrapErr` for interop with `anyhow`
|
||||
#[allow(unreachable_pub)]
|
||||
pub use WrapErr as Context;
|
||||
|
||||
use crate::{Diagnostic, GraphicalReportHandler, NarratableReportHandler};
|
||||
use error::ErrorImpl;
|
||||
|
||||
mod context;
|
||||
mod error;
|
||||
mod fmt;
|
||||
mod into_diagnostic;
|
||||
mod kind;
|
||||
mod macros;
|
||||
mod wrapper;
|
||||
|
||||
/**
|
||||
Core Diagnostic wrapper type.
|
||||
*/
|
||||
pub struct Report {
|
||||
inner: ManuallyDrop<Box<ErrorImpl<()>>>,
|
||||
}
|
||||
|
||||
type ErrorHook =
|
||||
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
|
||||
|
||||
static HOOK: OnceCell<ErrorHook> = OnceCell::new();
|
||||
|
||||
/// Error indicating that `set_hook` was unable to install the provided ErrorHook
|
||||
#[derive(Debug)]
|
||||
pub struct InstallError;
|
||||
|
||||
impl core::fmt::Display for InstallError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str("cannot install provided ErrorHook, a hook has already been installed")
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for InstallError {}
|
||||
impl Diagnostic for InstallError {}
|
||||
|
||||
/**
|
||||
Set the hook?
|
||||
*/
|
||||
pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {
|
||||
HOOK.set(hook).map_err(|_| InstallError)
|
||||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
#[cfg_attr(not(track_caller), allow(unused_mut))]
|
||||
fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
|
||||
let hook = HOOK.get_or_init(|| Box::new(get_default_printer)).as_ref();
|
||||
|
||||
#[cfg(track_caller)]
|
||||
{
|
||||
let mut handler = hook(error);
|
||||
handler.track_caller(std::panic::Location::caller());
|
||||
handler
|
||||
}
|
||||
#[cfg(not(track_caller))]
|
||||
{
|
||||
hook(error)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler + 'static> {
|
||||
let fancy = if let Ok(string) = std::env::var("NO_COLOR") {
|
||||
string == "0"
|
||||
} else if let Ok(string) = std::env::var("CLICOLOR") {
|
||||
string != "0" || string == "1"
|
||||
} else {
|
||||
atty::is(Stream::Stdout) && atty::is(Stream::Stderr) && !ci_info::is_ci()
|
||||
};
|
||||
if fancy {
|
||||
Box::new(GraphicalReportHandler::new())
|
||||
} else {
|
||||
Box::new(NarratableReportHandler)
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn ReportHandler {
|
||||
///
|
||||
pub fn is<T: ReportHandler>(&self) -> bool {
|
||||
// Get `TypeId` of the type this function is instantiated with.
|
||||
let t = core::any::TypeId::of::<T>();
|
||||
|
||||
// Get `TypeId` of the type in the trait object (`self`).
|
||||
let concrete = self.type_id();
|
||||
|
||||
// Compare both `TypeId`s on equality.
|
||||
t == concrete
|
||||
}
|
||||
|
||||
///
|
||||
pub fn downcast_ref<T: ReportHandler>(&self) -> Option<&T> {
|
||||
if self.is::<T>() {
|
||||
unsafe { Some(&*(self as *const dyn ReportHandler as *const T)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn downcast_mut<T: ReportHandler>(&mut self) -> Option<&mut T> {
|
||||
if self.is::<T>() {
|
||||
unsafe { Some(&mut *(self as *mut dyn ReportHandler as *mut T)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error Report Handler trait for customizing `miette::Report`
|
||||
pub trait ReportHandler: core::any::Any + Send + Sync {
|
||||
/// Define the report format
|
||||
///
|
||||
/// Used to override the report format of `miette::Report`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use miette::Diagnostic;
|
||||
/// use miette::ReportHandler;
|
||||
/// use indenter::indented;
|
||||
///
|
||||
/// pub struct Handler;
|
||||
///
|
||||
/// impl ReportHandler for Handler {
|
||||
/// fn debug(
|
||||
/// &self,
|
||||
/// error: &(dyn Diagnostic + 'static),
|
||||
/// f: &mut core::fmt::Formatter<'_>,
|
||||
/// ) -> core::fmt::Result {
|
||||
/// use core::fmt::Write as _;
|
||||
///
|
||||
/// if f.alternate() {
|
||||
/// return core::fmt::Debug::fmt(error, f);
|
||||
/// }
|
||||
///
|
||||
/// write!(f, "{}", error)?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn debug(
|
||||
&self,
|
||||
error: &(dyn Diagnostic + 'static),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result;
|
||||
|
||||
/// Override for the `Display` format
|
||||
fn display(
|
||||
&self,
|
||||
error: &(dyn StdError + 'static),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
write!(f, "{}", error)?;
|
||||
|
||||
if f.alternate() {
|
||||
for cause in crate::chain::Chain::new(error).skip(1) {
|
||||
write!(f, ": {}", cause)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store the location of the caller who constructed this error report
|
||||
#[allow(unused_variables)]
|
||||
fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {}
|
||||
}
|
||||
|
||||
/// Iterator of a chain of source errors.
|
||||
///
|
||||
/// This type is the iterator returned by [`Report::chain`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use miette::Report;
|
||||
/// use std::io;
|
||||
///
|
||||
/// pub fn underlying_io_error_kind(error: &Report) -> Option<io::ErrorKind> {
|
||||
/// for cause in error.chain() {
|
||||
/// if let Some(io_error) = cause.downcast_ref::<io::Error>() {
|
||||
/// return Some(io_error.kind());
|
||||
/// }
|
||||
/// }
|
||||
/// None
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Chain<'a> {
|
||||
state: crate::chain::ChainState<'a>,
|
||||
}
|
||||
|
||||
/// type alias for `Result<T, Report>`
|
||||
///
|
||||
/// This is a reasonable return type to use throughout your application but also for `fn main`; if
|
||||
/// you do, failures will be printed along with a backtrace if one was captured.
|
||||
///
|
||||
/// `miette::Result` may be used with one *or* two type parameters.
|
||||
///
|
||||
/// ```rust
|
||||
/// use miette::Result;
|
||||
///
|
||||
/// # const IGNORE: &str = stringify! {
|
||||
/// fn demo1() -> Result<T> {...}
|
||||
/// // ^ equivalent to std::result::Result<T, miette::Error>
|
||||
///
|
||||
/// fn demo2() -> Result<T, OtherError> {...}
|
||||
/// // ^ equivalent to std::result::Result<T, OtherError>
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # pub trait Deserialize {}
|
||||
/// #
|
||||
/// # mod serde_json {
|
||||
/// # use super::Deserialize;
|
||||
/// # use std::io;
|
||||
/// #
|
||||
/// # pub fn from_str<T: Deserialize>(json: &str) -> io::Result<T> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct ClusterMap;
|
||||
/// #
|
||||
/// # impl Deserialize for ClusterMap {}
|
||||
/// #
|
||||
/// use miette::{IntoDiagnostic, Result};
|
||||
///
|
||||
/// fn main() -> Result<()> {
|
||||
/// # return Ok(());
|
||||
/// let config = std::fs::read_to_string("cluster.json").into_diagnostic()?;
|
||||
/// let map: ClusterMap = serde_json::from_str(&config).into_diagnostic()?;
|
||||
/// println!("cluster info: {:#?}", map);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub type Result<T, E = Report> = core::result::Result<T, E>;
|
||||
|
||||
/// Provides the `wrap_err` method for `Result`.
|
||||
///
|
||||
/// This trait is sealed and cannot be implemented for types outside of
|
||||
/// `miette`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use miette::{WrapErr, IntoDiagnostic, Result};
|
||||
/// use std::fs;
|
||||
/// use std::path::PathBuf;
|
||||
///
|
||||
/// pub struct ImportantThing {
|
||||
/// path: PathBuf,
|
||||
/// }
|
||||
///
|
||||
/// impl ImportantThing {
|
||||
/// # const IGNORE: &'static str = stringify! {
|
||||
/// pub fn detach(&mut self) -> Result<()> {...}
|
||||
/// # };
|
||||
/// # fn detach(&mut self) -> Result<()> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// }
|
||||
///
|
||||
/// pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
|
||||
/// it.detach().wrap_err("Failed to detach the important thing")?;
|
||||
///
|
||||
/// let path = &it.path;
|
||||
/// let content = fs::read(path)
|
||||
/// .into_diagnostic()
|
||||
/// .wrap_err_with(|| format!("Failed to read instrs from {}", path.display()))?;
|
||||
///
|
||||
/// Ok(content)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// When printed, the outermost error would be printed first and the lower
|
||||
/// level underlying causes would be enumerated below.
|
||||
///
|
||||
/// ```console
|
||||
/// Error: Failed to read instrs from ./path/to/instrs.json
|
||||
///
|
||||
/// Caused by:
|
||||
/// No such file or directory (os error 2)
|
||||
/// ```
|
||||
///
|
||||
/// # Wrapping Types That Don't impl `Error` (e.g. `&str` and `Box<dyn Error>`)
|
||||
///
|
||||
/// Due to restrictions for coherence `Report` cannot impl `From` for types that don't impl
|
||||
/// `Error`. Attempts to do so will give "this type might implement Error in the future" as an
|
||||
/// error. As such, `wrap_err`, which uses `From` under the hood, cannot be used to wrap these
|
||||
/// types. Instead we encourage you to use the combinators provided for `Result` in `std`/`core`.
|
||||
///
|
||||
/// For example, instead of this:
|
||||
///
|
||||
/// ```rust,compile_fail
|
||||
/// use std::error::Error;
|
||||
/// use miette::{WrapErr, Report};
|
||||
///
|
||||
/// fn wrap_example(err: Result<(), Box<dyn Error + Send + Sync + 'static>>) -> Result<(), Report> {
|
||||
/// err.wrap_err("saw a downstream error")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// We encourage you to write this:
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::error::Error;
|
||||
/// use miette::{WrapErr, Report, miette};
|
||||
///
|
||||
/// fn wrap_example(err: Result<(), Box<dyn Error + Send + Sync + 'static>>) -> Result<(), Report> {
|
||||
/// err.map_err(|e| miette!(e)).wrap_err("saw a downstream error")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Effect on downcasting
|
||||
///
|
||||
/// After attaching a message of type `D` onto an error of type `E`, the resulting
|
||||
/// `miette::Error` may be downcast to `D` **or** to `E`.
|
||||
///
|
||||
/// That is, in codebases that rely on downcasting, Eyre's wrap_err supports
|
||||
/// both of the following use cases:
|
||||
///
|
||||
/// - **Attaching messages whose type is insignificant onto errors whose type
|
||||
/// is used in downcasts.**
|
||||
///
|
||||
/// In other error libraries whose wrap_err is not designed this way, it can
|
||||
/// be risky to introduce messages to existing code because new message might
|
||||
/// break existing working downcasts. In Eyre, any downcast that worked
|
||||
/// before adding the message will continue to work after you add a message, so
|
||||
/// you should freely wrap errors wherever it would be helpful.
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::bail;
|
||||
/// # use thiserror::Error;
|
||||
/// #
|
||||
/// # #[derive(Error, Debug)]
|
||||
/// # #[error("???")]
|
||||
/// # struct SuspiciousError;
|
||||
/// #
|
||||
/// # fn helper() -> Result<()> {
|
||||
/// # bail!(SuspiciousError);
|
||||
/// # }
|
||||
/// #
|
||||
/// use miette::{WrapErr, Result};
|
||||
///
|
||||
/// fn do_it() -> Result<()> {
|
||||
/// helper().wrap_err("Failed to complete the work")?;
|
||||
/// # const IGNORE: &str = stringify! {
|
||||
/// ...
|
||||
/// # };
|
||||
/// # unreachable!()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let err = do_it().unwrap_err();
|
||||
/// if let Some(e) = err.downcast_ref::<SuspiciousError>() {
|
||||
/// // If helper() returned SuspiciousError, this downcast will
|
||||
/// // correctly succeed even with the message in between.
|
||||
/// # return;
|
||||
/// }
|
||||
/// # panic!("expected downcast to succeed");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - **Attaching message whose type is used in downcasts onto errors whose
|
||||
/// type is insignificant.**
|
||||
///
|
||||
/// Some codebases prefer to use machine-readable messages to categorize
|
||||
/// lower level errors in a way that will be actionable to higher levels of
|
||||
/// the application.
|
||||
///
|
||||
/// ```
|
||||
/// # use miette::bail;
|
||||
/// # use thiserror::Error;
|
||||
/// #
|
||||
/// # #[derive(Error, Debug)]
|
||||
/// # #[error("???")]
|
||||
/// # struct HelperFailed;
|
||||
/// #
|
||||
/// # fn helper() -> Result<()> {
|
||||
/// # bail!("no such file or directory");
|
||||
/// # }
|
||||
/// #
|
||||
/// use miette::{WrapErr, Result};
|
||||
///
|
||||
/// fn do_it() -> Result<()> {
|
||||
/// helper().wrap_err(HelperFailed)?;
|
||||
/// # const IGNORE: &str = stringify! {
|
||||
/// ...
|
||||
/// # };
|
||||
/// # unreachable!()
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let err = do_it().unwrap_err();
|
||||
/// if let Some(e) = err.downcast_ref::<HelperFailed>() {
|
||||
/// // If helper failed, this downcast will succeed because
|
||||
/// // HelperFailed is the message that has been attached to
|
||||
/// // that error.
|
||||
/// # return;
|
||||
/// }
|
||||
/// # panic!("expected downcast to succeed");
|
||||
/// }
|
||||
/// ```
|
||||
pub trait WrapErr<T, E>: context::private::Sealed {
|
||||
/// Wrap the error value with a new adhoc error
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static;
|
||||
|
||||
/// Wrap the error value with a new adhoc error that is evaluated lazily
|
||||
/// only once an error does occur.
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn wrap_err_with<D, F>(self, f: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D;
|
||||
|
||||
/// Compatibility re-export of wrap_err for interop with `anyhow`
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn context<D>(self, msg: D) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static;
|
||||
|
||||
/// Compatibility re-export of wrap_err_with for interop with `anyhow`
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn with_context<D, F>(self, f: F) -> Result<T, Report>
|
||||
where
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D;
|
||||
}
|
||||
|
||||
// Not public API. Referenced by macro-generated code.
|
||||
#[doc(hidden)]
|
||||
pub mod private {
|
||||
use super::Report;
|
||||
use core::fmt::{Debug, Display};
|
||||
|
||||
pub use core::result::Result::Err;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod kind {
|
||||
pub use super::super::kind::{AdhocKind, TraitKind};
|
||||
|
||||
pub use super::super::kind::BoxedKind;
|
||||
}
|
||||
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
pub fn new_adhoc<M>(message: M) -> Report
|
||||
where
|
||||
M: Display + Debug + Send + Sync + 'static,
|
||||
{
|
||||
Report::from_adhoc(message)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
use core::fmt::{self, Debug, Display};
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use crate::Diagnostic;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct DisplayError<M>(pub(crate) M);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct MessageError<M>(pub(crate) M);
|
||||
|
||||
pub(crate) struct NoneError;
|
||||
|
||||
impl<M> Debug for DisplayError<M>
|
||||
where
|
||||
M: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Display for DisplayError<M>
|
||||
where
|
||||
M: Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
|
||||
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
|
||||
|
||||
impl<M> Debug for MessageError<M>
|
||||
where
|
||||
M: Display + Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Display for MessageError<M>
|
||||
where
|
||||
M: Display + Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
|
||||
impl<M> Diagnostic for MessageError<M> where M: Display + Debug + 'static {}
|
||||
|
||||
impl Debug for NoneError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt("Option was None", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NoneError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt("Option was None", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for NoneError {}
|
||||
impl Diagnostic for NoneError {}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
|
||||
|
||||
impl Debug for BoxedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoxedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for BoxedError {}
|
||||
impl Diagnostic for BoxedError {}
|
||||
|
|
@ -3,34 +3,29 @@ use std::fmt;
|
|||
use owo_colors::{OwoColorize, Style};
|
||||
|
||||
use crate::chain::Chain;
|
||||
use crate::printer::theme::*;
|
||||
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
|
||||
use crate::{SourceSpan, SpanContents};
|
||||
use crate::handlers::theme::*;
|
||||
use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
|
||||
use crate::{ReportHandler, SourceSpan, SpanContents};
|
||||
|
||||
/**
|
||||
A [DiagnosticReportPrinter] that displays a given [crate::DiagnosticReport] in a quasi-graphical way, using terminal colors, unicode drawing characters, and other such things.
|
||||
A [ReportHandler] that displays a given [crate::Report] in a quasi-graphical
|
||||
way, using terminal colors, unicode drawing characters, and other such things.
|
||||
|
||||
This is the default reporter bundled with `miette`.
|
||||
|
||||
This printer can be customized by using `new_themed()` and handing it a [GraphicalTheme] of your own creation (or using one of its own defaults!)
|
||||
This printer can be customized by using `new_themed()` and handing it a
|
||||
[GraphicalTheme] of your own creation (or using one of its own defaults!)
|
||||
|
||||
See [crate::set_printer] for more details on customizing your global printer.
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
use miette::{GraphicalReportPrinter, GraphicalTheme};
|
||||
miette::set_printer(GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor()));
|
||||
```
|
||||
See [crate::set_hook] for more details on customizing your global printer.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphicalReportPrinter {
|
||||
pub struct GraphicalReportHandler {
|
||||
pub(crate) linkify_code: bool,
|
||||
pub(crate) theme: GraphicalTheme,
|
||||
}
|
||||
|
||||
impl GraphicalReportPrinter {
|
||||
/// Create a new [GraphicalReportPrinter] with the default
|
||||
impl GraphicalReportHandler {
|
||||
/// Create a new [GraphicalReportHandler] with the default
|
||||
/// [GraphicalTheme]. This will use both unicode characters and colors.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
|
@ -39,7 +34,7 @@ impl GraphicalReportPrinter {
|
|||
}
|
||||
}
|
||||
|
||||
///Create a new [GraphicalReportPrinter] with a given [GraphicalTheme].
|
||||
///Create a new [GraphicalReportHandler] with a given [GraphicalTheme].
|
||||
pub fn new_themed(theme: GraphicalTheme) -> Self {
|
||||
Self {
|
||||
linkify_code: true,
|
||||
|
|
@ -54,15 +49,15 @@ impl GraphicalReportPrinter {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for GraphicalReportPrinter {
|
||||
impl Default for GraphicalReportHandler {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicalReportPrinter {
|
||||
impl GraphicalReportHandler {
|
||||
/// Render a [Diagnostic]. This function is mostly internal and meant to
|
||||
/// be called by the toplevel [DiagnosticReportPrinter] handler, but is
|
||||
/// be called by the toplevel [ReportHandler] handler, but is
|
||||
/// made public to make it easier (possible) to test in isolation from
|
||||
/// global state.
|
||||
pub fn render_report(
|
||||
|
|
@ -91,13 +86,18 @@ impl GraphicalReportPrinter {
|
|||
Some(Severity::Advice) => (self.theme.styles.advice, self.theme.characters.point_right),
|
||||
};
|
||||
write!(f, "{}", self.theme.characters.hbar.to_string().repeat(4))?;
|
||||
if self.linkify_code && diagnostic.url().is_some() {
|
||||
if self.linkify_code && diagnostic.url().is_some() && diagnostic.code().is_some() {
|
||||
let url = diagnostic.url().unwrap(); // safe
|
||||
let code = format!("{} (click for details)", diagnostic.code());
|
||||
let code = format!(
|
||||
"{} (click for details)",
|
||||
diagnostic
|
||||
.code()
|
||||
.expect("MIETTE BUG: already got checked for None")
|
||||
);
|
||||
let link = format!("\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", url, code);
|
||||
write!(f, "[{}]", link.style(self.theme.styles.code))?;
|
||||
} else {
|
||||
write!(f, "[{}]", diagnostic.code().style(self.theme.styles.code))?;
|
||||
} else if let Some(code) = diagnostic.code() {
|
||||
write!(f, "[{}]", code.style(self.theme.styles.code))?;
|
||||
}
|
||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(20),)?;
|
||||
writeln!(f)?;
|
||||
|
|
@ -510,7 +510,7 @@ impl GraphicalReportPrinter {
|
|||
}
|
||||
}
|
||||
|
||||
impl DiagnosticReportPrinter for GraphicalReportPrinter {
|
||||
impl ReportHandler for GraphicalReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*!
|
||||
Reporters included with `miette`.
|
||||
*/
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub use graphical::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use narratable::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use theme::*;
|
||||
|
||||
mod graphical;
|
||||
mod narratable;
|
||||
mod theme;
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::chain::Chain;
|
||||
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
|
||||
use crate::{SourceSpan, SpanContents};
|
||||
use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
|
||||
use crate::{ReportHandler, SourceSpan, SpanContents};
|
||||
|
||||
/**
|
||||
[DiagnosticReportPrinter] that renders plain text and avoids extraneous graphics.
|
||||
[ReportHandler] that renders plain text and avoids extraneous graphics.
|
||||
It's optimized for screen readers and braille users, but is also used in any
|
||||
non-graphical environments, such as non-TTY output.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NarratableReportPrinter;
|
||||
pub struct NarratableReportHandler;
|
||||
|
||||
impl NarratableReportPrinter {
|
||||
/// Create a new [NarratableReportPrinter]. There are no customization
|
||||
impl NarratableReportHandler {
|
||||
/// Create a new [NarratableReportHandler]. There are no customization
|
||||
/// options.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NarratableReportPrinter {
|
||||
impl Default for NarratableReportHandler {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NarratableReportPrinter {
|
||||
impl NarratableReportHandler {
|
||||
/// Render a [Diagnostic]. This function is mostly internal and meant to
|
||||
/// be called by the toplevel [DiagnosticReportPrinter] handler, but is
|
||||
/// be called by the toplevel [ReportHandler] handler, but is
|
||||
/// made public to make it easier (possible) to test in isolation from
|
||||
/// global state.
|
||||
pub fn render_report(
|
||||
|
|
@ -75,7 +75,9 @@ impl NarratableReportPrinter {
|
|||
if let Some(help) = diagnostic.help() {
|
||||
writeln!(f, "diagnostic help: {}", help)?;
|
||||
}
|
||||
writeln!(f, "diagnostic error code: {}", diagnostic.code())?;
|
||||
if let Some(code) = diagnostic.code() {
|
||||
writeln!(f, "diagnostic code: {}", code)?;
|
||||
}
|
||||
if let Some(url) = diagnostic.url() {
|
||||
writeln!(f, "For more details, see {}", url)?;
|
||||
}
|
||||
|
|
@ -192,7 +194,7 @@ impl NarratableReportPrinter {
|
|||
}
|
||||
}
|
||||
|
||||
impl DiagnosticReportPrinter for NarratableReportPrinter {
|
||||
impl ReportHandler for NarratableReportHandler {
|
||||
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
return fmt::Debug::fmt(diagnostic, f);
|
||||
|
|
@ -2,7 +2,7 @@ use atty::Stream;
|
|||
use owo_colors::Style;
|
||||
|
||||
/**
|
||||
Theme used by [crate::GraphicalReportPrinter] to render fancy [crate::Diagnostic] reports.
|
||||
Theme used by [crate::GraphicalReportHandler] to render fancy [crate::Diagnostic] reports.
|
||||
|
||||
A theme consists of two things: the set of characters to be used for drawing,
|
||||
and the [owo_colors::Style]s to be used to paint various items.
|
||||
|
|
@ -47,8 +47,8 @@ impl GraphicalTheme {
|
|||
/// A "basic" graphical theme that skips colors and unicode characters and
|
||||
/// just does monochrome ascii art. If you want a completely non-graphical
|
||||
/// rendering of your `Diagnostic`s, check out
|
||||
/// [crate::NarratableReportPrinter], or write your own
|
||||
/// [crate::DiagnosticReportPrinter]!
|
||||
/// [crate::NarratableReportHandler], or write your own
|
||||
/// [crate::ReportHandler]!
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
characters: ThemeCharacters::ascii(),
|
||||
|
|
@ -68,7 +68,7 @@ impl Default for GraphicalTheme {
|
|||
}
|
||||
|
||||
/**
|
||||
Styles for various parts of graphical rendering for the [crate::GraphicalReportPrinter].
|
||||
Styles for various parts of graphical rendering for the [crate::GraphicalReportHandler].
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThemeStyles {
|
||||
|
|
@ -150,7 +150,7 @@ impl ThemeStyles {
|
|||
// Most of these characters were taken from
|
||||
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
||||
|
||||
/// Characters to be used when drawing when using [crate::GraphicalReportPrinter].
|
||||
/// Characters to be used when drawing when using [crate::GraphicalReportHandler].
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ThemeCharacters {
|
||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -5,16 +5,18 @@
|
|||
pub use miette_derive::*;
|
||||
|
||||
pub use error::*;
|
||||
pub use printer::*;
|
||||
pub use eyreish::*;
|
||||
pub use handlers::*;
|
||||
pub use named_source::*;
|
||||
pub use protocol::*;
|
||||
pub use utils::*;
|
||||
|
||||
mod chain;
|
||||
mod error;
|
||||
mod printer;
|
||||
mod eyreish;
|
||||
mod handlers;
|
||||
mod named_source;
|
||||
mod protocol;
|
||||
mod source_impls;
|
||||
mod utils;
|
||||
|
||||
#[cfg(doctest)]
|
||||
mod compile_test;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
use crate::Source;
|
||||
|
||||
/// Utility struct for when you have a regular [Source] type, such as a String,
|
||||
/// that doesn't implement `name`, or if you want to override the `.name()`
|
||||
/// returned by the `Source`.
|
||||
#[derive(Debug)]
|
||||
pub struct NamedSource {
|
||||
source: Box<dyn Source + Send + Sync + 'static>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl NamedSource {
|
||||
/// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name].
|
||||
pub fn new(name: impl AsRef<str>, source: impl Source + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
source: Box::new(source),
|
||||
name: name.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference the inner [Source] type for this [NamedSource].
|
||||
pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) {
|
||||
&*self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for NamedSource {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &crate::SourceSpan,
|
||||
) -> Result<Box<dyn crate::SpanContents + 'a>, crate::MietteError> {
|
||||
self.source.read_span(span)
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<String> {
|
||||
Some(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*!
|
||||
Reporters included with `miette`.
|
||||
*/
|
||||
use atty::Stream;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::protocol::DiagnosticReportPrinter;
|
||||
use crate::MietteError;
|
||||
|
||||
// NOTE(zkat): I don't understand why these three are "unreachable" when
|
||||
// they're clearly being exported? Maybe a bug?
|
||||
#[allow(unreachable_pub)]
|
||||
pub use graphical_printer::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use narratable_printer::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use theme::*;
|
||||
|
||||
mod graphical_printer;
|
||||
mod narratable_printer;
|
||||
mod theme;
|
||||
|
||||
static REPORTER: OnceCell<Box<dyn DiagnosticReportPrinter + Send + Sync + 'static>> =
|
||||
OnceCell::new();
|
||||
|
||||
/// Set the global [DiagnosticReportPrinter] that will be used when you report
|
||||
/// using [crate::DiagnosticReport].
|
||||
pub fn set_printer(
|
||||
reporter: impl DiagnosticReportPrinter + Send + Sync + 'static,
|
||||
) -> Result<(), MietteError> {
|
||||
REPORTER
|
||||
.set(Box::new(reporter))
|
||||
.map_err(|_| MietteError::SetPrinterFailure)
|
||||
}
|
||||
|
||||
/// Used by [crate::DiagnosticReport] to fetch the reporter that will be used to
|
||||
/// print stuff out.
|
||||
pub(crate) fn get_printer() -> &'static (dyn DiagnosticReportPrinter + Send + Sync + 'static) {
|
||||
&**REPORTER.get_or_init(get_default_printer)
|
||||
}
|
||||
|
||||
fn get_default_printer() -> Box<dyn DiagnosticReportPrinter + Send + Sync + 'static> {
|
||||
let fancy = if let Ok(string) = std::env::var("NO_COLOR") {
|
||||
string == "0"
|
||||
} else if let Ok(string) = std::env::var("CLICOLOR") {
|
||||
string != "0" || string == "1"
|
||||
} else {
|
||||
atty::is(Stream::Stdout) && atty::is(Stream::Stderr) && !ci_info::is_ci()
|
||||
};
|
||||
if fancy {
|
||||
Box::new(GraphicalReportPrinter::new())
|
||||
} else {
|
||||
Box::new(NarratableReportPrinter)
|
||||
}
|
||||
}
|
||||
105
src/protocol.rs
105
src/protocol.rs
|
|
@ -13,7 +13,7 @@ use std::{
|
|||
use crate::MietteError;
|
||||
|
||||
/**
|
||||
Adds rich metadata to your Error that can be used by [DiagnosticReportPrinter] to print
|
||||
Adds rich metadata to your Error that can be used by [Report] to print
|
||||
really nice and human-friendly error messages.
|
||||
*/
|
||||
pub trait Diagnostic: std::error::Error {
|
||||
|
|
@ -22,9 +22,11 @@ pub trait Diagnostic: std::error::Error {
|
|||
/// the toplevel crate's documentation for easy searching. Rust path
|
||||
/// format (`foo::bar::baz`) is recommended, but more classic codes like
|
||||
/// `E0123` or Enums will work just fine.
|
||||
fn code<'a>(&'a self) -> Box<dyn Display + 'a>;
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Diagnostic severity. This may be used by [DiagnosticReportPrinter]s to change the
|
||||
/// Diagnostic severity. This may be used by [ReportHandler]s to change the
|
||||
/// display format of this diagnostic.
|
||||
///
|
||||
/// If `None`, reporters should treat this as [Severity::Error]
|
||||
|
|
@ -72,62 +74,69 @@ impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'st
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
When used with `?`/`From`, this will wrap any Diagnostics and, when
|
||||
formatted with `Debug`, will fetch the current [DiagnosticReportPrinter] and
|
||||
use it to format the inner [Diagnostic].
|
||||
*/
|
||||
pub struct DiagnosticReport {
|
||||
diagnostic: Box<dyn Diagnostic + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl DiagnosticReport {
|
||||
/// Return a reference to the inner [Diagnostic].
|
||||
pub fn inner(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
|
||||
&*self.diagnostic
|
||||
impl From<&str> for Box<dyn Diagnostic> {
|
||||
fn from(s: &str) -> Self {
|
||||
From::from(String::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DiagnosticReport {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
crate::get_printer().debug(&*self.diagnostic, f)
|
||||
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
|
||||
fn from(s: &str) -> Self {
|
||||
From::from(String::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Diagnostic + Send + Sync + 'static> From<T> for DiagnosticReport {
|
||||
fn from(diagnostic: T) -> Self {
|
||||
DiagnosticReport {
|
||||
diagnostic: Box::new(diagnostic),
|
||||
impl From<String> for Box<dyn Diagnostic> {
|
||||
fn from(s: String) -> Self {
|
||||
let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
|
||||
let err2: Box<dyn Diagnostic> = err1;
|
||||
err2
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Box<dyn Diagnostic + Send + Sync> {
|
||||
fn from(s: String) -> Self {
|
||||
struct StringError(String);
|
||||
|
||||
impl std::error::Error for StringError {}
|
||||
impl Diagnostic for StringError {}
|
||||
|
||||
impl Display for StringError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Purposefully skip printing "StringError(..)"
|
||||
impl fmt::Debug for StringError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(StringError(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
|
||||
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
||||
#[derive(thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
|
||||
impl fmt::Debug for BoxedDiagnostic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for BoxedDiagnostic {}
|
||||
|
||||
Box::new(BoxedDiagnostic(s))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Protocol for [Diagnostic] handlers, which are responsible for actually printing out Diagnostics.
|
||||
|
||||
Blatantly based on [EyreHandler](https://docs.rs/eyre/0.6.5/eyre/trait.EyreHandler.html) (thanks, Jane!)
|
||||
*/
|
||||
pub trait DiagnosticReportPrinter: core::any::Any + Send + Sync {
|
||||
/// Define the report format.
|
||||
fn debug(
|
||||
&self,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result;
|
||||
|
||||
/// Override for the `Display` format.
|
||||
fn display(
|
||||
&self,
|
||||
diagnostic: &(dyn Diagnostic),
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
write!(f, "{}", diagnostic)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
[Diagnostic] severity. Intended to be used by [DiagnosticReportPrinter]s to change the
|
||||
[Diagnostic] severity. Intended to be used by [ReportHandler]s to change the
|
||||
way different Diagnostics are displayed.
|
||||
*/
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
|
|
|
|||
86
src/utils.rs
86
src/utils.rs
|
|
@ -1,86 +0,0 @@
|
|||
use std::fmt;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{Diagnostic, DiagnosticReport, Source};
|
||||
|
||||
/// Convenience alias. This is intended to be used as the return type for `main()`
|
||||
pub type DiagnosticResult<T> = Result<T, DiagnosticReport>;
|
||||
|
||||
/// Convenience [Diagnostic] that can be used as an "anonymous" wrapper for
|
||||
/// Errors. This is intended to be paired with [IntoDiagnostic].
|
||||
#[derive(Debug, Error)]
|
||||
#[error("{}", self.error)]
|
||||
pub struct DiagnosticError {
|
||||
#[source]
|
||||
error: Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||
code: String,
|
||||
}
|
||||
|
||||
impl DiagnosticError {
|
||||
/// Return a reference to the inner Error type.
|
||||
pub fn inner(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
|
||||
&*self.error
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for DiagnosticError {
|
||||
fn code<'a>(&'a self) -> Box<dyn std::fmt::Display + 'a> {
|
||||
Box::new(&self.code)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Convenience trait that adds a `.into_diagnostic()` method that converts a type to a `Result<T, DiagnosticError>`.
|
||||
*/
|
||||
pub trait IntoDiagnostic<T, E> {
|
||||
/// Converts [Result]-like types that return regular errors into a
|
||||
/// `Result` that returns a [Diagnostic].
|
||||
fn into_diagnostic(self, code: impl fmt::Display) -> Result<T, DiagnosticError>;
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for Result<T, E> {
|
||||
fn into_diagnostic(self, code: impl fmt::Display) -> Result<T, DiagnosticError> {
|
||||
self.map_err(|e| DiagnosticError {
|
||||
error: Box::new(e),
|
||||
code: format!("{}", code),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility struct for when you have a regular [Source] type, such as a String,
|
||||
/// that doesn't implement `name`, or if you want to override the `.name()`
|
||||
/// returned by the `Source`.
|
||||
#[derive(Debug)]
|
||||
pub struct NamedSource {
|
||||
source: Box<dyn Source + Send + Sync + 'static>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl NamedSource {
|
||||
/// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name].
|
||||
pub fn new(name: impl AsRef<str>, source: impl Source + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
source: Box::new(source),
|
||||
name: name.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference the inner [Source] type for this [NamedSource].
|
||||
pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) {
|
||||
&*self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for NamedSource {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &crate::SourceSpan,
|
||||
) -> Result<Box<dyn crate::SpanContents + 'a>, crate::MietteError> {
|
||||
self.source.read_span(span)
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<String> {
|
||||
Some(self.name.clone())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
use miette::{bail, Result};
|
||||
use std::io;
|
||||
|
||||
pub fn bail_literal() -> Result<()> {
|
||||
bail!("oh no!");
|
||||
}
|
||||
|
||||
pub fn bail_fmt() -> Result<()> {
|
||||
bail!("{} {}!", "oh", "no");
|
||||
}
|
||||
|
||||
pub fn bail_error() -> Result<()> {
|
||||
bail!(io::Error::new(io::ErrorKind::Other, "oh no!"));
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#[rustversion::attr(not(nightly), ignore)]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/*.rs");
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ fn basic_struct() {
|
|||
)]
|
||||
struct Foo;
|
||||
|
||||
assert_eq!("foo::bar::baz".to_string(), Foo.code().to_string());
|
||||
assert_eq!("foo::bar::baz".to_string(), Foo.code().unwrap().to_string());
|
||||
|
||||
assert_eq!(Some(Severity::Error), Foo.severity());
|
||||
|
||||
|
|
@ -39,11 +39,11 @@ fn basic_enum() {
|
|||
Z { prop: String },
|
||||
}
|
||||
|
||||
assert_eq!("foo::x".to_string(), Foo::X.code().to_string());
|
||||
assert_eq!("foo::y".to_string(), Foo::Y(1).code().to_string());
|
||||
assert_eq!("foo::x".to_string(), Foo::X.code().unwrap().to_string());
|
||||
assert_eq!("foo::y".to_string(), Foo::Y(1).code().unwrap().to_string());
|
||||
assert_eq!(
|
||||
"foo::z".to_string(),
|
||||
Foo::Z { prop: "bar".into() }.code().to_string()
|
||||
Foo::Z { prop: "bar".into() }.code().unwrap().to_string()
|
||||
);
|
||||
|
||||
assert_eq!(Some(Severity::Warning), Foo::X.severity());
|
||||
|
|
@ -57,7 +57,10 @@ fn paren_code() {
|
|||
#[diagnostic(code("foo::bar::baz"))]
|
||||
struct FooStruct;
|
||||
|
||||
assert_eq!("foo::bar::baz".to_string(), FooStruct.code().to_string());
|
||||
assert_eq!(
|
||||
"foo::bar::baz".to_string(),
|
||||
FooStruct.code().unwrap().to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
|
|
@ -66,7 +69,7 @@ fn paren_code() {
|
|||
X,
|
||||
}
|
||||
|
||||
assert_eq!("foo::x".to_string(), FooEnum::X.code().to_string());
|
||||
assert_eq!("foo::x".to_string(), FooEnum::X.code().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -76,7 +79,10 @@ fn path_code() {
|
|||
#[diagnostic(code(foo::bar::baz))]
|
||||
struct FooStruct;
|
||||
|
||||
assert_eq!("foo::bar::baz".to_string(), FooStruct.code().to_string());
|
||||
assert_eq!(
|
||||
"foo::bar::baz".to_string(),
|
||||
FooStruct.code().unwrap().to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
|
|
@ -85,7 +91,7 @@ fn path_code() {
|
|||
X,
|
||||
}
|
||||
|
||||
assert_eq!("foo::x".to_string(), FooEnum::X.code().to_string());
|
||||
assert_eq!("foo::x".to_string(), FooEnum::X.code().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -341,7 +347,7 @@ impl ForwardsTo {
|
|||
|
||||
fn check_snippets(diag: &impl Diagnostic) {
|
||||
// check Diagnostic impl forwards all these methods
|
||||
assert_eq!(diag.code().to_string(), "foo::bar::baz");
|
||||
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::baz");
|
||||
assert_eq!(diag.url().unwrap().to_string(), "https://example.com");
|
||||
assert_eq!(diag.help().unwrap().to_string(), "help");
|
||||
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
||||
|
|
@ -413,7 +419,10 @@ fn test_transparent_enum_named() {
|
|||
check_snippets(&variant);
|
||||
|
||||
let bar_variant = Enum::BarVariant;
|
||||
assert_eq!(bar_variant.code().to_string(), "foo::bar::bar_variant");
|
||||
assert_eq!(
|
||||
bar_variant.code().unwrap().to_string(),
|
||||
"foo::bar::bar_variant"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
use std::error::Error as StdError;
|
||||
use std::fmt::{self, Display};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::Arc;
|
||||
|
||||
use miette::Diagnostic;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Flag {
|
||||
atomic: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Flag {
|
||||
pub fn new() -> Self {
|
||||
Flag {
|
||||
atomic: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> bool {
|
||||
self.atomic.load(SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DetectDrop {
|
||||
has_dropped: Flag,
|
||||
}
|
||||
|
||||
impl DetectDrop {
|
||||
pub fn new(has_dropped: &Flag) -> Self {
|
||||
DetectDrop {
|
||||
has_dropped: Flag {
|
||||
atomic: Arc::clone(&has_dropped.atomic),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for DetectDrop {}
|
||||
impl Diagnostic for DetectDrop {}
|
||||
|
||||
impl Display for DetectDrop {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "oh no!")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DetectDrop {
|
||||
fn drop(&mut self) {
|
||||
let already_dropped = self.has_dropped.atomic.swap(true, SeqCst);
|
||||
assert!(!already_dropped);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
use miette::{
|
||||
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
|
||||
NarratableReportPrinter, SourceSpan,
|
||||
Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource,
|
||||
NarratableReportHandler, Report, SourceSpan,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
fn fmt_report(diag: DiagnosticReport) -> String {
|
||||
fn fmt_report(diag: Report) -> String {
|
||||
let mut out = String::new();
|
||||
// Mostly for dev purposes.
|
||||
if std::env::var("STYLE").is_ok() {
|
||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode())
|
||||
.render_report(&mut out, diag.inner())
|
||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
} else {
|
||||
NarratableReportPrinter
|
||||
.render_report(&mut out, diag.inner())
|
||||
NarratableReportHandler
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
};
|
||||
out
|
||||
|
|
@ -53,7 +53,7 @@ snippet line 2: text
|
|||
snippet line 3: here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -95,7 +95,7 @@ snippet line 2: text
|
|||
snippet line 3: here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -141,7 +141,7 @@ snippet line 2: text text text text text
|
|||
snippet line 3: here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -183,7 +183,7 @@ snippet line 2: text
|
|||
snippet line 3: here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -237,7 +237,7 @@ snippet line 4: line4
|
|||
snippet line 6: line5
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -291,7 +291,7 @@ snippet line 4: line4
|
|||
snippet line 6: line5
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -338,7 +338,7 @@ snippet line 3: here
|
|||
snippet line 4: more here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -386,7 +386,7 @@ snippet line 2: text
|
|||
snippet line 3: here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -434,7 +434,7 @@ snippet line 2: text
|
|||
snippet line 3: here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
|
@ -469,7 +469,7 @@ Begin snippet starting at line 1, column 1: This is the part that broke
|
|||
snippet line 1: source_text_here
|
||||
|
||||
diagnostic help: try doing it better next time?
|
||||
diagnostic error code: oops::my::bad
|
||||
diagnostic code: oops::my::bad
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(out, expected);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
use miette::{
|
||||
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
|
||||
NarratableReportPrinter, SourceSpan,
|
||||
Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource,
|
||||
NarratableReportHandler, Report, SourceSpan,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
fn fmt_report(diag: DiagnosticReport) -> String {
|
||||
fn fmt_report(diag: Report) -> String {
|
||||
let mut out = String::new();
|
||||
// Mostly for dev purposes.
|
||||
if std::env::var("STYLE").is_ok() {
|
||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode())
|
||||
.render_report(&mut out, diag.inner())
|
||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
} else if std::env::var("NARRATED").is_ok() {
|
||||
NarratableReportPrinter
|
||||
.render_report(&mut out, diag.inner())
|
||||
NarratableReportHandler
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
} else {
|
||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor())
|
||||
.render_report(&mut out, diag.inner())
|
||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
};
|
||||
out
|
||||
|
|
@ -481,7 +481,7 @@ fn disable_url_links() -> Result<(), MietteError> {
|
|||
struct MyBad;
|
||||
let err = MyBad;
|
||||
let mut out = String::new();
|
||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor())
|
||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||
.without_code_linking()
|
||||
.render_report(&mut out, &err)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
use miette::Report;
|
||||
|
||||
#[test]
|
||||
fn test_send() {
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<Report>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync() {
|
||||
fn assert_sync<T: Sync>() {}
|
||||
assert_sync::<Report>();
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
use miette::{miette, Diagnostic, Report};
|
||||
use std::error::Error as StdError;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("outer")]
|
||||
struct MyError {
|
||||
source: io::Error,
|
||||
}
|
||||
impl Diagnostic for MyError {}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_str_diagnostic() {
|
||||
let error = Box::<dyn Diagnostic + Send + Sync>::from("oh no!");
|
||||
let error: Report = miette!(error);
|
||||
assert_eq!("oh no!", error.to_string());
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
error
|
||||
.downcast_ref::<Box<dyn Diagnostic + Send + Sync>>()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_str_stderr() {
|
||||
let error = Box::<dyn StdError + Send + Sync>::from("oh no!");
|
||||
let error: Report = miette!(error);
|
||||
assert_eq!("oh no!", error.to_string());
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
error
|
||||
.downcast_ref::<Box<dyn StdError + Send + Sync>>()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_thiserror() {
|
||||
let error = MyError {
|
||||
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
|
||||
};
|
||||
let error: Report = miette!(error);
|
||||
assert_eq!("oh no!", error.source().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boxed_miette() {
|
||||
let error: Report = miette!("oh no!").wrap_err("it failed");
|
||||
let error = miette!(error);
|
||||
assert_eq!("oh no!", error.source().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "I don't know why this isn't working but it needs fixing."]
|
||||
fn test_boxed_sources() {
|
||||
let error = MyError {
|
||||
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
|
||||
};
|
||||
let error = Box::<dyn Diagnostic + Send + Sync>::from(error);
|
||||
let error: Report = miette!(error).wrap_err("it failed");
|
||||
assert_eq!("it failed", error.to_string());
|
||||
assert_eq!("outer", error.source().unwrap().to_string());
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
error
|
||||
.source()
|
||||
.expect("outer")
|
||||
.source()
|
||||
.expect("inner")
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
use miette::{miette, Report};
|
||||
|
||||
fn error() -> Report {
|
||||
miette!(0).wrap_err(1).wrap_err(2).wrap_err(3)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
let e = error();
|
||||
let mut chain = e.chain();
|
||||
assert_eq!("3", chain.next().unwrap().to_string());
|
||||
assert_eq!("2", chain.next().unwrap().to_string());
|
||||
assert_eq!("1", chain.next().unwrap().to_string());
|
||||
assert_eq!("0", chain.next().unwrap().to_string());
|
||||
assert!(chain.next().is_none());
|
||||
assert!(chain.next_back().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rev() {
|
||||
let e = error();
|
||||
let mut chain = e.chain().rev();
|
||||
assert_eq!("0", chain.next().unwrap().to_string());
|
||||
assert_eq!("1", chain.next().unwrap().to_string());
|
||||
assert_eq!("2", chain.next().unwrap().to_string());
|
||||
assert_eq!("3", chain.next().unwrap().to_string());
|
||||
assert!(chain.next().is_none());
|
||||
assert!(chain.next_back().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let e = error();
|
||||
let mut chain = e.chain();
|
||||
assert_eq!(4, chain.len());
|
||||
assert_eq!("3", chain.next().unwrap().to_string());
|
||||
assert_eq!(3, chain.len());
|
||||
assert_eq!("0", chain.next_back().unwrap().to_string());
|
||||
assert_eq!(2, chain.len());
|
||||
assert_eq!("2", chain.next().unwrap().to_string());
|
||||
assert_eq!(1, chain.len());
|
||||
assert_eq!("1", chain.next_back().unwrap().to_string());
|
||||
assert_eq!(0, chain.len());
|
||||
assert!(chain.next().is_none());
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
mod drop;
|
||||
|
||||
use crate::drop::{DetectDrop, Flag};
|
||||
use miette::{Diagnostic, IntoDiagnostic, Report, Result, WrapErr};
|
||||
use std::fmt::{self, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
// https://github.com/dtolnay/miette/issues/18
|
||||
#[test]
|
||||
fn test_inference() -> Result<()> {
|
||||
let x = "1";
|
||||
let y: u32 = x.parse().into_diagnostic().context("...")?;
|
||||
assert_eq!(y, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! context_type {
|
||||
($name:ident) => {
|
||||
#[derive(Debug)]
|
||||
struct $name {
|
||||
message: &'static str,
|
||||
drop: DetectDrop,
|
||||
}
|
||||
|
||||
impl Display for $name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(self.message)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
context_type!(HighLevel);
|
||||
context_type!(MidLevel);
|
||||
|
||||
#[derive(Diagnostic, Error, Debug)]
|
||||
#[error("{message}")]
|
||||
#[diagnostic()] // TODO
|
||||
struct LowLevel {
|
||||
message: &'static str,
|
||||
drop: DetectDrop,
|
||||
}
|
||||
|
||||
struct Dropped {
|
||||
low: Flag,
|
||||
mid: Flag,
|
||||
high: Flag,
|
||||
}
|
||||
|
||||
impl Dropped {
|
||||
fn none(&self) -> bool {
|
||||
!self.low.get() && !self.mid.get() && !self.high.get()
|
||||
}
|
||||
|
||||
fn all(&self) -> bool {
|
||||
self.low.get() && self.mid.get() && self.high.get()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_chain() -> (Report, Dropped) {
|
||||
let dropped = Dropped {
|
||||
low: Flag::new(),
|
||||
mid: Flag::new(),
|
||||
high: Flag::new(),
|
||||
};
|
||||
|
||||
let low = LowLevel {
|
||||
message: "no such file or directory",
|
||||
drop: DetectDrop::new(&dropped.low),
|
||||
};
|
||||
|
||||
// impl Report for Result<T, E>
|
||||
let mid = Err::<(), LowLevel>(low)
|
||||
.wrap_err(MidLevel {
|
||||
message: "failed to load config",
|
||||
drop: DetectDrop::new(&dropped.mid),
|
||||
})
|
||||
.unwrap_err();
|
||||
|
||||
// impl Report for Result<T, Error>
|
||||
let high = Err::<(), Report>(mid)
|
||||
.wrap_err(HighLevel {
|
||||
message: "failed to start server",
|
||||
drop: DetectDrop::new(&dropped.high),
|
||||
})
|
||||
.unwrap_err();
|
||||
|
||||
(high, dropped)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downcast_ref() {
|
||||
let (err, dropped) = make_chain();
|
||||
|
||||
assert!(!err.is::<String>());
|
||||
assert!(err.downcast_ref::<String>().is_none());
|
||||
|
||||
assert!(err.is::<HighLevel>());
|
||||
let high = err.downcast_ref::<HighLevel>().unwrap();
|
||||
assert_eq!(high.to_string(), "failed to start server");
|
||||
|
||||
assert!(err.is::<MidLevel>());
|
||||
let mid = err.downcast_ref::<MidLevel>().unwrap();
|
||||
assert_eq!(mid.to_string(), "failed to load config");
|
||||
|
||||
assert!(err.is::<LowLevel>());
|
||||
let low = err.downcast_ref::<LowLevel>().unwrap();
|
||||
assert_eq!(low.to_string(), "no such file or directory");
|
||||
|
||||
assert!(dropped.none());
|
||||
drop(err);
|
||||
assert!(dropped.all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downcast_high() {
|
||||
let (err, dropped) = make_chain();
|
||||
|
||||
let err = err.downcast::<HighLevel>().unwrap();
|
||||
assert!(!dropped.high.get());
|
||||
assert!(dropped.low.get() && dropped.mid.get());
|
||||
|
||||
drop(err);
|
||||
assert!(dropped.all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downcast_mid() {
|
||||
let (err, dropped) = make_chain();
|
||||
|
||||
let err = err.downcast::<MidLevel>().unwrap();
|
||||
assert!(!dropped.mid.get());
|
||||
assert!(dropped.low.get() && dropped.high.get());
|
||||
|
||||
drop(err);
|
||||
assert!(dropped.all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downcast_low() {
|
||||
let (err, dropped) = make_chain();
|
||||
|
||||
let err = err.downcast::<LowLevel>().unwrap();
|
||||
assert!(!dropped.low.get());
|
||||
assert!(dropped.mid.get() && dropped.high.get());
|
||||
|
||||
drop(err);
|
||||
assert!(dropped.all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsuccessful_downcast() {
|
||||
let (err, dropped) = make_chain();
|
||||
|
||||
let err = err.downcast::<String>().unwrap_err();
|
||||
assert!(dropped.none());
|
||||
|
||||
drop(err);
|
||||
assert!(dropped.all());
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#[test]
|
||||
fn test_context() {
|
||||
use miette::{miette, Report};
|
||||
|
||||
let error: Report = miette!("oh no!");
|
||||
let _ = error.context();
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
mod drop;
|
||||
|
||||
use self::drop::{DetectDrop, Flag};
|
||||
use miette::{Diagnostic, Report, Result};
|
||||
|
||||
#[test]
|
||||
fn test_convert() {
|
||||
let has_dropped = Flag::new();
|
||||
let error: Report = Report::new(DetectDrop::new(&has_dropped));
|
||||
let box_dyn = Box::<dyn Diagnostic + Send + Sync>::from(error);
|
||||
assert_eq!("oh no!", box_dyn.to_string());
|
||||
drop(box_dyn);
|
||||
assert!(has_dropped.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_question_mark() -> Result<(), Box<dyn Diagnostic>> {
|
||||
fn f() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
f()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
mod common;
|
||||
mod drop;
|
||||
|
||||
use self::common::*;
|
||||
use self::drop::{DetectDrop, Flag};
|
||||
use miette::{Diagnostic, Report};
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn test_downcast() {
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_literal().unwrap_err().downcast::<&str>().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_fmt().unwrap_err().downcast::<String>().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_error()
|
||||
.unwrap_err()
|
||||
.downcast::<io::Error>()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downcast_ref() {
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
*bail_literal().unwrap_err().downcast_ref::<&str>().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_fmt().unwrap_err().downcast_ref::<String>().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_error()
|
||||
.unwrap_err()
|
||||
.downcast_ref::<io::Error>()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downcast_mut() {
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
*bail_literal().unwrap_err().downcast_mut::<&str>().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_fmt().unwrap_err().downcast_mut::<String>().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_error()
|
||||
.unwrap_err()
|
||||
.downcast_mut::<io::Error>()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop() {
|
||||
let has_dropped = Flag::new();
|
||||
let error: Report = Report::new(DetectDrop::new(&has_dropped));
|
||||
drop(error.downcast::<DetectDrop>().unwrap());
|
||||
assert!(has_dropped.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_alignment() {
|
||||
#[repr(align(64))]
|
||||
#[derive(Debug)]
|
||||
struct LargeAlignedError(&'static str);
|
||||
|
||||
impl Display for LargeAlignedError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for LargeAlignedError {}
|
||||
impl Diagnostic for LargeAlignedError {}
|
||||
|
||||
let error = Report::new(LargeAlignedError("oh no!"));
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
error.downcast_ref::<LargeAlignedError>().unwrap().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsuccessful_downcast() {
|
||||
let mut error = bail_error().unwrap_err();
|
||||
assert!(error.downcast_ref::<&str>().is_none());
|
||||
assert!(error.downcast_mut::<&str>().is_none());
|
||||
assert!(error.downcast::<&str>().is_err());
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
use miette::{bail, Result, WrapErr};
|
||||
use std::io;
|
||||
|
||||
fn f() -> Result<()> {
|
||||
bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
|
||||
}
|
||||
|
||||
fn g() -> Result<()> {
|
||||
f().wrap_err("f failed")
|
||||
}
|
||||
|
||||
fn h() -> Result<()> {
|
||||
g().wrap_err("g failed")
|
||||
}
|
||||
|
||||
const EXPECTED_ALTDISPLAY_F: &str = "oh no!";
|
||||
|
||||
const EXPECTED_ALTDISPLAY_G: &str = "f failed: oh no!";
|
||||
|
||||
const EXPECTED_ALTDISPLAY_H: &str = "g failed: f failed: oh no!";
|
||||
|
||||
const EXPECTED_DEBUG_F: &str = "oh no!";
|
||||
|
||||
const EXPECTED_DEBUG_G: &str = "\
|
||||
f failed
|
||||
|
||||
Caused by:
|
||||
oh no!\
|
||||
";
|
||||
|
||||
const EXPECTED_DEBUG_H: &str = "\
|
||||
g failed
|
||||
|
||||
Caused by:
|
||||
0: f failed
|
||||
1: oh no!\
|
||||
";
|
||||
|
||||
const EXPECTED_ALTDEBUG_F: &str = "\
|
||||
Custom {
|
||||
kind: PermissionDenied,
|
||||
error: \"oh no!\",
|
||||
}\
|
||||
";
|
||||
|
||||
const EXPECTED_ALTDEBUG_G: &str = "\
|
||||
Error {
|
||||
msg: \"f failed\",
|
||||
source: Custom {
|
||||
kind: PermissionDenied,
|
||||
error: \"oh no!\",
|
||||
},
|
||||
}\
|
||||
";
|
||||
|
||||
const EXPECTED_ALTDEBUG_H: &str = "\
|
||||
Error {
|
||||
msg: \"g failed\",
|
||||
source: Error {
|
||||
msg: \"f failed\",
|
||||
source: Custom {
|
||||
kind: PermissionDenied,
|
||||
error: \"oh no!\",
|
||||
},
|
||||
},
|
||||
}\
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!("g failed", h().unwrap_err().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_altdisplay() {
|
||||
assert_eq!(EXPECTED_ALTDISPLAY_F, format!("{:#}", f().unwrap_err()));
|
||||
assert_eq!(EXPECTED_ALTDISPLAY_G, format!("{:#}", g().unwrap_err()));
|
||||
assert_eq!(EXPECTED_ALTDISPLAY_H, format!("{:#}", h().unwrap_err()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "not really gonna work with the current printers"]
|
||||
#[cfg_attr(track_caller, ignore)]
|
||||
fn test_debug() {
|
||||
assert_eq!(EXPECTED_DEBUG_F, format!("{:?}", f().unwrap_err()));
|
||||
assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
|
||||
assert_eq!(EXPECTED_DEBUG_H, format!("{:?}", h().unwrap_err()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_altdebug() {
|
||||
assert_eq!(EXPECTED_ALTDEBUG_F, format!("{:#?}", f().unwrap_err()));
|
||||
assert_eq!(EXPECTED_ALTDEBUG_G, format!("{:#?}", g().unwrap_err()));
|
||||
assert_eq!(EXPECTED_ALTDEBUG_H, format!("{:#?}", h().unwrap_err()));
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
use std::panic::Location;
|
||||
|
||||
use miette::{Diagnostic, IntoDiagnostic, WrapErr};
|
||||
|
||||
struct LocationHandler {
|
||||
actual: Option<&'static str>,
|
||||
expected: &'static str,
|
||||
}
|
||||
|
||||
impl LocationHandler {
|
||||
fn new(expected: &'static str) -> Self {
|
||||
LocationHandler {
|
||||
actual: None,
|
||||
expected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl miette::ReportHandler for LocationHandler {
|
||||
fn debug(
|
||||
&self,
|
||||
_error: &(dyn Diagnostic + 'static),
|
||||
_f: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
// we assume that if the compiler is new enough to support
|
||||
// `track_caller` that we will always have `actual` be `Some`, so we can
|
||||
// safely skip the assertion if the location is `None` which should only
|
||||
// happen in older rust versions.
|
||||
if let Some(actual) = self.actual {
|
||||
assert_eq!(self.expected, actual);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_caller(&mut self, location: &'static Location<'static>) {
|
||||
dbg!(location);
|
||||
self.actual = Some(location.file());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_err() {
|
||||
let _ = miette::set_hook(Box::new(|_e| {
|
||||
let expected_location = file!();
|
||||
Box::new(LocationHandler::new(expected_location))
|
||||
}));
|
||||
|
||||
let err = std::fs::read_to_string("totally_fake_path")
|
||||
.into_diagnostic()
|
||||
.wrap_err("oopsie")
|
||||
.unwrap_err();
|
||||
|
||||
// should panic if the location isn't in our crate
|
||||
println!("{:?}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_err_with() {
|
||||
let _ = miette::set_hook(Box::new(|_e| {
|
||||
let expected_location = file!();
|
||||
Box::new(LocationHandler::new(expected_location))
|
||||
}));
|
||||
|
||||
let err = std::fs::read_to_string("totally_fake_path")
|
||||
.into_diagnostic()
|
||||
.wrap_err_with(|| "oopsie")
|
||||
.unwrap_err();
|
||||
|
||||
// should panic if the location isn't in our crate
|
||||
println!("{:?}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context() {
|
||||
let _ = miette::set_hook(Box::new(|_e| {
|
||||
let expected_location = file!();
|
||||
Box::new(LocationHandler::new(expected_location))
|
||||
}));
|
||||
|
||||
let err = std::fs::read_to_string("totally_fake_path")
|
||||
.into_diagnostic()
|
||||
.context("oopsie")
|
||||
.unwrap_err();
|
||||
|
||||
// should panic if the location isn't in our crate
|
||||
println!("{:?}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_context() {
|
||||
let _ = miette::set_hook(Box::new(|_e| {
|
||||
let expected_location = file!();
|
||||
Box::new(LocationHandler::new(expected_location))
|
||||
}));
|
||||
|
||||
let err = std::fs::read_to_string("totally_fake_path")
|
||||
.into_diagnostic()
|
||||
.with_context(|| "oopsie")
|
||||
.unwrap_err();
|
||||
|
||||
// should panic if the location isn't in our crate
|
||||
println!("{:?}", err);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#![allow(clippy::eq_op)]
|
||||
mod common;
|
||||
|
||||
use self::common::*;
|
||||
use miette::{ensure, Result};
|
||||
|
||||
#[test]
|
||||
fn test_messages() {
|
||||
assert_eq!("oh no!", bail_literal().unwrap_err().to_string());
|
||||
assert_eq!("oh no!", bail_fmt().unwrap_err().to_string());
|
||||
assert_eq!("oh no!", bail_error().unwrap_err().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ensure() {
|
||||
let f = || -> Result<()> {
|
||||
ensure!(1 + 1 == 2, "This is correct");
|
||||
Ok(())
|
||||
};
|
||||
assert!(f().is_ok());
|
||||
|
||||
let v = 1;
|
||||
let f = || -> Result<()> {
|
||||
ensure!(v + v == 2, "This is correct, v: {}", v);
|
||||
Ok(())
|
||||
};
|
||||
assert!(f().is_ok());
|
||||
|
||||
let f = || -> Result<()> {
|
||||
ensure!(v + v == 1, "This is not correct, v: {}", v);
|
||||
Ok(())
|
||||
};
|
||||
assert!(f().is_err());
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
mod drop;
|
||||
|
||||
use self::drop::{DetectDrop, Flag};
|
||||
use miette::Report;
|
||||
use std::marker::Unpin;
|
||||
use std::mem;
|
||||
|
||||
#[test]
|
||||
fn test_error_size() {
|
||||
assert_eq!(mem::size_of::<Report>(), mem::size_of::<usize>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_pointer_optimization() {
|
||||
assert_eq!(
|
||||
mem::size_of::<Result<(), Report>>(),
|
||||
mem::size_of::<usize>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_autotraits() {
|
||||
fn assert<E: Unpin + Send + Sync + 'static>() {}
|
||||
assert::<Report>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop() {
|
||||
let has_dropped = Flag::new();
|
||||
drop(Report::new(DetectDrop::new(&has_dropped)));
|
||||
assert!(has_dropped.get());
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
use miette::{miette, Report};
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TestError {
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl Display for TestError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TestError::Io(e) => Display::fmt(e, formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for TestError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
TestError::Io(io) => Some(io),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_literal_source() {
|
||||
let error: Report = miette!("oh no!");
|
||||
assert!(error.source().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_source() {
|
||||
let msg = "oh no!";
|
||||
let error = miette!(msg);
|
||||
assert!(error.source().is_none());
|
||||
|
||||
let msg = msg.to_owned();
|
||||
let error: Report = miette!(msg);
|
||||
assert!(error.source().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fmt_source() {
|
||||
let error: Report = miette!("{} {}!", "oh", "no");
|
||||
assert!(error.source().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Again with the io::Error source issue?"]
|
||||
fn test_io_source() {
|
||||
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
|
||||
let error: Report = miette!(TestError::Io(io));
|
||||
assert_eq!("oh no!", error.source().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_miette_from_miette() {
|
||||
let error: Report = miette!("oh no!").wrap_err("context");
|
||||
let error = miette!(error);
|
||||
assert_eq!("oh no!", error.source().unwrap().to_string());
|
||||
}
|
||||
Loading…
Reference in New Issue