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]
|
[dependencies]
|
||||||
thiserror = "1.0.26"
|
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"
|
once_cell = "1.8.0"
|
||||||
owo-colors = "2.0.0"
|
owo-colors = "2.0.0"
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
|
|
@ -22,5 +22,12 @@ ci_info = "0.14.2"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
semver = "1.0.4"
|
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]
|
[workspace]
|
||||||
members = ["miette-derive"]
|
members = ["miette-derive"]
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ args = ["--prepend", "CHANGELOG.md", "-u", "--tag", "${@}"]
|
||||||
workspace=false
|
workspace=false
|
||||||
install_crate="cargo-release"
|
install_crate="cargo-release"
|
||||||
command = "cargo"
|
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].
|
- Unique error codes on every [Diagnostic].
|
||||||
- Custom links to get more details on error codes.
|
- Custom links to get more details on error codes.
|
||||||
- Super handy derive macro for defining diagnostic metadata.
|
- 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`.
|
which can be returned from `main`.
|
||||||
- Generic support for arbitrary [Source]s for snippet data, with default support for `String`s included.
|
- 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
|
- Fancy graphical [diagnostic output](#about), using ANSI/Unicode text
|
||||||
- single- and multi-line highlighting support
|
- single- and multi-line highlighting support
|
||||||
|
|
@ -97,12 +97,12 @@ struct MyBad {
|
||||||
/*
|
/*
|
||||||
Now let's define a function!
|
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
|
throughout your app (but NOT your libraries! Those should always return concrete
|
||||||
types!).
|
types!).
|
||||||
*/
|
*/
|
||||||
use miette::{DiagnosticResult, NamedSource};
|
use miette::{Result, NamedSource};
|
||||||
fn this_fails() -> DiagnosticResult<()> {
|
fn this_fails() -> Result<()> {
|
||||||
// You can use plain strings as a `Source`, or anything that implements
|
// You can use plain strings as a `Source`, or anything that implements
|
||||||
// the one-method `Source` trait.
|
// the one-method `Source` trait.
|
||||||
let src = "source\n text\n here".to_string();
|
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!
|
and you're all set!
|
||||||
|
|
||||||
Note: You can swap out the default reporter for a custom one using `miette::set_reporter()`
|
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~
|
// kaboom~
|
||||||
this_fails()?;
|
this_fails()?;
|
||||||
|
|
||||||
|
|
@ -183,7 +183,7 @@ pub enum MyLibError {
|
||||||
|
|
||||||
Then, return this error type from all your fallible public APIs. It's a best
|
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
|
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
|
### ... 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
|
always need or care to define dedicated error wrappers for errors coming from
|
||||||
external libraries and tools.
|
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
|
[IntoDiagnostic]. They work in tandem to make it easy to convert regular
|
||||||
`std::error::Error`s into [Diagnostic]s. Additionally, there's a
|
`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
|
```rust
|
||||||
// my_app/lib/my_internal_file.rs
|
// my_app/lib/my_internal_file.rs
|
||||||
use miette::{IntoDiagnostic, DiagnosticResult};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
pub fn some_tool() -> DiagnosticResult<Version> {
|
pub fn some_tool() -> Result<Version> {
|
||||||
Ok("1.2.x".parse().into_diagnostic("my_app::semver::parse_error")?)
|
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()`
|
### ... in `main()`
|
||||||
|
|
||||||
`main()` is just like any other part of your application-internal code. Use
|
`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.
|
diagnostics automatically.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use miette::{DiagnosticResult, IntoDiagnostic};
|
use miette::{Result, IntoDiagnostic};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
fn pretend_this_is_main() -> DiagnosticResult<()> {
|
fn pretend_this_is_main() -> Result<()> {
|
||||||
let version: Version = "1.2.x".parse().into_diagnostic("my_app::semver::parse_error")?;
|
let version: Version = "1.2.x".parse().into_diagnostic()?;
|
||||||
println!("{}", version);
|
println!("{}", version);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +266,7 @@ use thiserror::Error;
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(my_app::my_error),
|
code(my_app::my_error),
|
||||||
// You can do formatting!
|
// 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;
|
struct MyErr;
|
||||||
```
|
```
|
||||||
|
|
@ -330,7 +347,7 @@ pub struct MyErrorType {
|
||||||
[`color-eyre`](https://crates.io/crates/color-eyre): these two enormously
|
[`color-eyre`](https://crates.io/crates/color-eyre): these two enormously
|
||||||
influential error handling libraries have pushed forward the experience of
|
influential error handling libraries have pushed forward the experience of
|
||||||
application-level error handling and error reporting. `miette`'s
|
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.
|
`Report` types.
|
||||||
- [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
|
- [`thiserror`](https://crates.io/crates/thiserror) for setting the standard
|
||||||
for library-level error definitions, and for being the inspiration behind
|
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).
|
`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
|
and some from [`thiserror`](https://github.com/dtolnay/thiserror), also under
|
||||||
the Apache License. Some code is taken from
|
the Apache License. Some code is taken from
|
||||||
[`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
[`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||||
|
|
|
||||||
|
|
@ -56,29 +56,30 @@ impl Code {
|
||||||
}| {
|
}| {
|
||||||
match args {
|
match args {
|
||||||
DiagnosticDefArgs::Transparent => {
|
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, .. }) => {
|
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { code, .. }) => {
|
||||||
let code = &code.0;
|
let code = &code.as_ref()?.0;
|
||||||
match fields {
|
Some(match fields {
|
||||||
syn::Fields::Named(_) => {
|
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(_) => {
|
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 => {
|
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! {
|
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 {
|
match self {
|
||||||
#(#code_pairs)*
|
#(#code_pairs)*
|
||||||
|
_ => std::option::Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -87,8 +88,8 @@ impl Code {
|
||||||
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||||
let code = &self.0;
|
let code = &self.0;
|
||||||
Some(quote! {
|
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>> {
|
||||||
std::boxed::Box::new(#code)
|
std::option::Option::Some(std::boxed::Box::new(#code))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@ pub enum DiagnosticDefArgs {
|
||||||
Concrete(DiagnosticConcreteArgs),
|
Concrete(DiagnosticConcreteArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct DiagnosticConcreteArgs {
|
pub struct DiagnosticConcreteArgs {
|
||||||
pub code: Code,
|
pub code: Option<Code>,
|
||||||
pub severity: Option<Severity>,
|
pub severity: Option<Severity>,
|
||||||
pub help: Option<Help>,
|
pub help: Option<Help>,
|
||||||
pub snippets: Option<Snippets>,
|
pub snippets: Option<Snippets>,
|
||||||
|
|
@ -44,7 +45,7 @@ pub struct DiagnosticConcreteArgs {
|
||||||
|
|
||||||
impl DiagnosticConcreteArgs {
|
impl DiagnosticConcreteArgs {
|
||||||
fn parse(
|
fn parse(
|
||||||
ident: &syn::Ident,
|
_ident: &syn::Ident,
|
||||||
fields: &syn::Fields,
|
fields: &syn::Fields,
|
||||||
attr: &syn::Attribute,
|
attr: &syn::Attribute,
|
||||||
args: impl Iterator<Item = DiagnosticArg>,
|
args: impl Iterator<Item = DiagnosticArg>,
|
||||||
|
|
@ -75,8 +76,7 @@ impl DiagnosticConcreteArgs {
|
||||||
}
|
}
|
||||||
let snippets = Snippets::from_fields(fields)?;
|
let snippets = Snippets::from_fields(fields)?;
|
||||||
let concrete = DiagnosticConcreteArgs {
|
let concrete = DiagnosticConcreteArgs {
|
||||||
code: code
|
code,
|
||||||
.ok_or_else(|| syn::Error::new(ident.span(), "Diagnostic code is required."))?,
|
|
||||||
help,
|
help,
|
||||||
severity,
|
severity,
|
||||||
snippets,
|
snippets,
|
||||||
|
|
@ -132,11 +132,12 @@ impl Diagnostic {
|
||||||
args,
|
args,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Also handle when there's multiple `#[diagnostic]` attrs?
|
Diagnostic::Struct {
|
||||||
return Err(syn::Error::new(
|
fields: data_struct.fields,
|
||||||
input.ident.span(),
|
ident: input.ident,
|
||||||
"#[diagnostic] attribute is required when deriving Diagnostic.",
|
generics: input.generics,
|
||||||
));
|
args: DiagnosticDefArgs::Concrete(Default::default()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
|
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
|
||||||
|
|
@ -206,7 +207,7 @@ impl Diagnostic {
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
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
|
#matcher
|
||||||
#field_name.code()
|
#field_name.code()
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +231,7 @@ impl Diagnostic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiagnosticDefArgs::Concrete(concrete) => {
|
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 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 sev_body = concrete.severity.as_ref().and_then(|x| x.gen_struct());
|
||||||
let snip_body = concrete
|
let snip_body = concrete
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ impl Severity {
|
||||||
fn severity(&self) -> std::option::Option<miette::Severity> {
|
fn severity(&self) -> std::option::Option<miette::Severity> {
|
||||||
match self {
|
match self {
|
||||||
#(#sev_pairs)*
|
#(#sev_pairs)*
|
||||||
_ => None,
|
_ => std::option::Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use ChainState::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub(crate) struct Chain<'a> {
|
pub struct Chain<'a> {
|
||||||
state: crate::chain::ChainState<'a>,
|
state: crate::chain::ChainState<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub(crate) enum ChainState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Chain<'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 {
|
Chain {
|
||||||
state: ChainState::Linked { next: Some(head) },
|
state: ChainState::Linked { next: Some(head) },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ pub enum MietteError {
|
||||||
)]
|
)]
|
||||||
OutOfBounds,
|
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.
|
/// 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))]
|
#[diagnostic(code(miette::set_printer_failed), url(docsrs))]
|
||||||
SetPrinterFailure,
|
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 owo_colors::{OwoColorize, Style};
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::chain::Chain;
|
||||||
use crate::printer::theme::*;
|
use crate::handlers::theme::*;
|
||||||
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
|
use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
|
||||||
use crate::{SourceSpan, SpanContents};
|
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 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.
|
See [crate::set_hook] for more details on customizing your global printer.
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
use miette::{GraphicalReportPrinter, GraphicalTheme};
|
|
||||||
miette::set_printer(GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor()));
|
|
||||||
```
|
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GraphicalReportPrinter {
|
pub struct GraphicalReportHandler {
|
||||||
pub(crate) linkify_code: bool,
|
pub(crate) linkify_code: bool,
|
||||||
pub(crate) theme: GraphicalTheme,
|
pub(crate) theme: GraphicalTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicalReportPrinter {
|
impl GraphicalReportHandler {
|
||||||
/// Create a new [GraphicalReportPrinter] with the default
|
/// Create a new [GraphicalReportHandler] with the default
|
||||||
/// [GraphicalTheme]. This will use both unicode characters and colors.
|
/// [GraphicalTheme]. This will use both unicode characters and colors.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
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 {
|
pub fn new_themed(theme: GraphicalTheme) -> Self {
|
||||||
Self {
|
Self {
|
||||||
linkify_code: true,
|
linkify_code: true,
|
||||||
|
|
@ -54,15 +49,15 @@ impl GraphicalReportPrinter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GraphicalReportPrinter {
|
impl Default for GraphicalReportHandler {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicalReportPrinter {
|
impl GraphicalReportHandler {
|
||||||
/// Render a [Diagnostic]. This function is mostly internal and meant to
|
/// 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
|
/// made public to make it easier (possible) to test in isolation from
|
||||||
/// global state.
|
/// global state.
|
||||||
pub fn render_report(
|
pub fn render_report(
|
||||||
|
|
@ -91,13 +86,18 @@ impl GraphicalReportPrinter {
|
||||||
Some(Severity::Advice) => (self.theme.styles.advice, self.theme.characters.point_right),
|
Some(Severity::Advice) => (self.theme.styles.advice, self.theme.characters.point_right),
|
||||||
};
|
};
|
||||||
write!(f, "{}", self.theme.characters.hbar.to_string().repeat(4))?;
|
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 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);
|
let link = format!("\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", url, code);
|
||||||
write!(f, "[{}]", link.style(self.theme.styles.code))?;
|
write!(f, "[{}]", link.style(self.theme.styles.code))?;
|
||||||
} else {
|
} else if let Some(code) = diagnostic.code() {
|
||||||
write!(f, "[{}]", diagnostic.code().style(self.theme.styles.code))?;
|
write!(f, "[{}]", code.style(self.theme.styles.code))?;
|
||||||
}
|
}
|
||||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(20),)?;
|
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(20),)?;
|
||||||
writeln!(f)?;
|
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 {
|
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return fmt::Debug::fmt(diagnostic, f);
|
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 std::fmt;
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::chain::Chain;
|
||||||
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, DiagnosticSnippet, Severity};
|
use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
|
||||||
use crate::{SourceSpan, SpanContents};
|
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
|
It's optimized for screen readers and braille users, but is also used in any
|
||||||
non-graphical environments, such as non-TTY output.
|
non-graphical environments, such as non-TTY output.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NarratableReportPrinter;
|
pub struct NarratableReportHandler;
|
||||||
|
|
||||||
impl NarratableReportPrinter {
|
impl NarratableReportHandler {
|
||||||
/// Create a new [NarratableReportPrinter]. There are no customization
|
/// Create a new [NarratableReportHandler]. There are no customization
|
||||||
/// options.
|
/// options.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NarratableReportPrinter {
|
impl Default for NarratableReportHandler {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NarratableReportPrinter {
|
impl NarratableReportHandler {
|
||||||
/// Render a [Diagnostic]. This function is mostly internal and meant to
|
/// 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
|
/// made public to make it easier (possible) to test in isolation from
|
||||||
/// global state.
|
/// global state.
|
||||||
pub fn render_report(
|
pub fn render_report(
|
||||||
|
|
@ -75,7 +75,9 @@ impl NarratableReportPrinter {
|
||||||
if let Some(help) = diagnostic.help() {
|
if let Some(help) = diagnostic.help() {
|
||||||
writeln!(f, "diagnostic help: {}", 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() {
|
if let Some(url) = diagnostic.url() {
|
||||||
writeln!(f, "For more details, see {}", 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 {
|
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
|
|
@ -2,7 +2,7 @@ use atty::Stream;
|
||||||
use owo_colors::Style;
|
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,
|
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.
|
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
|
/// A "basic" graphical theme that skips colors and unicode characters and
|
||||||
/// just does monochrome ascii art. If you want a completely non-graphical
|
/// just does monochrome ascii art. If you want a completely non-graphical
|
||||||
/// rendering of your `Diagnostic`s, check out
|
/// rendering of your `Diagnostic`s, check out
|
||||||
/// [crate::NarratableReportPrinter], or write your own
|
/// [crate::NarratableReportHandler], or write your own
|
||||||
/// [crate::DiagnosticReportPrinter]!
|
/// [crate::ReportHandler]!
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
Self {
|
Self {
|
||||||
characters: ThemeCharacters::ascii(),
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThemeStyles {
|
pub struct ThemeStyles {
|
||||||
|
|
@ -150,7 +150,7 @@ impl ThemeStyles {
|
||||||
// Most of these characters were taken from
|
// Most of these characters were taken from
|
||||||
// https://github.com/zesterer/ariadne/blob/e3cb394cb56ecda116a0a1caecd385a49e7f6662/src/draw.rs
|
// 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)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct ThemeCharacters {
|
pub struct ThemeCharacters {
|
||||||
10
src/lib.rs
10
src/lib.rs
|
|
@ -5,16 +5,18 @@
|
||||||
pub use miette_derive::*;
|
pub use miette_derive::*;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use printer::*;
|
pub use eyreish::*;
|
||||||
|
pub use handlers::*;
|
||||||
|
pub use named_source::*;
|
||||||
pub use protocol::*;
|
pub use protocol::*;
|
||||||
pub use utils::*;
|
|
||||||
|
|
||||||
mod chain;
|
mod chain;
|
||||||
mod error;
|
mod error;
|
||||||
mod printer;
|
mod eyreish;
|
||||||
|
mod handlers;
|
||||||
|
mod named_source;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod source_impls;
|
mod source_impls;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
#[cfg(doctest)]
|
#[cfg(doctest)]
|
||||||
mod compile_test;
|
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;
|
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.
|
really nice and human-friendly error messages.
|
||||||
*/
|
*/
|
||||||
pub trait Diagnostic: std::error::Error {
|
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
|
/// the toplevel crate's documentation for easy searching. Rust path
|
||||||
/// format (`foo::bar::baz`) is recommended, but more classic codes like
|
/// format (`foo::bar::baz`) is recommended, but more classic codes like
|
||||||
/// `E0123` or Enums will work just fine.
|
/// `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.
|
/// display format of this diagnostic.
|
||||||
///
|
///
|
||||||
/// If `None`, reporters should treat this as [Severity::Error]
|
/// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
impl From<&str> for Box<dyn Diagnostic> {
|
||||||
When used with `?`/`From`, this will wrap any Diagnostics and, when
|
fn from(s: &str) -> Self {
|
||||||
formatted with `Debug`, will fetch the current [DiagnosticReportPrinter] and
|
From::from(String::from(s))
|
||||||
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 std::fmt::Debug for DiagnosticReport {
|
impl<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn from(s: &str) -> Self {
|
||||||
crate::get_printer().debug(&*self.diagnostic, f)
|
From::from(String::from(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Diagnostic + Send + Sync + 'static> From<T> for DiagnosticReport {
|
impl From<String> for Box<dyn Diagnostic> {
|
||||||
fn from(diagnostic: T) -> Self {
|
fn from(s: String) -> Self {
|
||||||
DiagnosticReport {
|
let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
|
||||||
diagnostic: Box::new(diagnostic),
|
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.
|
[Diagnostic] severity. Intended to be used by [ReportHandler]s to change the
|
||||||
|
|
||||||
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
|
|
||||||
way different Diagnostics are displayed.
|
way different Diagnostics are displayed.
|
||||||
*/
|
*/
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[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;
|
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());
|
assert_eq!(Some(Severity::Error), Foo.severity());
|
||||||
|
|
||||||
|
|
@ -39,11 +39,11 @@ fn basic_enum() {
|
||||||
Z { prop: String },
|
Z { prop: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!("foo::x".to_string(), Foo::X.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().to_string());
|
assert_eq!("foo::y".to_string(), Foo::Y(1).code().unwrap().to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"foo::z".to_string(),
|
"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());
|
assert_eq!(Some(Severity::Warning), Foo::X.severity());
|
||||||
|
|
@ -57,7 +57,10 @@ fn paren_code() {
|
||||||
#[diagnostic(code("foo::bar::baz"))]
|
#[diagnostic(code("foo::bar::baz"))]
|
||||||
struct FooStruct;
|
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)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
|
|
@ -66,7 +69,7 @@ fn paren_code() {
|
||||||
X,
|
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]
|
#[test]
|
||||||
|
|
@ -76,7 +79,10 @@ fn path_code() {
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
struct FooStruct;
|
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)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
|
|
@ -85,7 +91,7 @@ fn path_code() {
|
||||||
X,
|
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]
|
#[test]
|
||||||
|
|
@ -341,7 +347,7 @@ impl ForwardsTo {
|
||||||
|
|
||||||
fn check_snippets(diag: &impl Diagnostic) {
|
fn check_snippets(diag: &impl Diagnostic) {
|
||||||
// check Diagnostic impl forwards all these methods
|
// 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.url().unwrap().to_string(), "https://example.com");
|
||||||
assert_eq!(diag.help().unwrap().to_string(), "help");
|
assert_eq!(diag.help().unwrap().to_string(), "help");
|
||||||
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
||||||
|
|
@ -413,7 +419,10 @@ fn test_transparent_enum_named() {
|
||||||
check_snippets(&variant);
|
check_snippets(&variant);
|
||||||
|
|
||||||
let bar_variant = Enum::BarVariant;
|
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]
|
#[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::{
|
use miette::{
|
||||||
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
|
Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource,
|
||||||
NarratableReportPrinter, SourceSpan,
|
NarratableReportHandler, Report, SourceSpan,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
fn fmt_report(diag: DiagnosticReport) -> String {
|
fn fmt_report(diag: Report) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
// Mostly for dev purposes.
|
// Mostly for dev purposes.
|
||||||
if std::env::var("STYLE").is_ok() {
|
if std::env::var("STYLE").is_ok() {
|
||||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode())
|
GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
|
||||||
.render_report(&mut out, diag.inner())
|
.render_report(&mut out, diag.as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
NarratableReportPrinter
|
NarratableReportHandler
|
||||||
.render_report(&mut out, diag.inner())
|
.render_report(&mut out, diag.as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
};
|
};
|
||||||
out
|
out
|
||||||
|
|
@ -53,7 +53,7 @@ snippet line 2: text
|
||||||
snippet line 3: here
|
snippet line 3: here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -95,7 +95,7 @@ snippet line 2: text
|
||||||
snippet line 3: here
|
snippet line 3: here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -141,7 +141,7 @@ snippet line 2: text text text text text
|
||||||
snippet line 3: here
|
snippet line 3: here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -183,7 +183,7 @@ snippet line 2: text
|
||||||
snippet line 3: here
|
snippet line 3: here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -237,7 +237,7 @@ snippet line 4: line4
|
||||||
snippet line 6: line5
|
snippet line 6: line5
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -291,7 +291,7 @@ snippet line 4: line4
|
||||||
snippet line 6: line5
|
snippet line 6: line5
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -338,7 +338,7 @@ snippet line 3: here
|
||||||
snippet line 4: more here
|
snippet line 4: more here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -386,7 +386,7 @@ snippet line 2: text
|
||||||
snippet line 3: here
|
snippet line 3: here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -434,7 +434,7 @@ snippet line 2: text
|
||||||
snippet line 3: here
|
snippet line 3: here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.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
|
snippet line 1: source_text_here
|
||||||
|
|
||||||
diagnostic help: try doing it better next time?
|
diagnostic help: try doing it better next time?
|
||||||
diagnostic error code: oops::my::bad
|
diagnostic code: oops::my::bad
|
||||||
"#
|
"#
|
||||||
.trim_start();
|
.trim_start();
|
||||||
assert_eq!(out, expected);
|
assert_eq!(out, expected);
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
use miette::{
|
use miette::{
|
||||||
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
|
Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource,
|
||||||
NarratableReportPrinter, SourceSpan,
|
NarratableReportHandler, Report, SourceSpan,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
fn fmt_report(diag: DiagnosticReport) -> String {
|
fn fmt_report(diag: Report) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
// Mostly for dev purposes.
|
// Mostly for dev purposes.
|
||||||
if std::env::var("STYLE").is_ok() {
|
if std::env::var("STYLE").is_ok() {
|
||||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode())
|
GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
|
||||||
.render_report(&mut out, diag.inner())
|
.render_report(&mut out, diag.as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else if std::env::var("NARRATED").is_ok() {
|
} else if std::env::var("NARRATED").is_ok() {
|
||||||
NarratableReportPrinter
|
NarratableReportHandler
|
||||||
.render_report(&mut out, diag.inner())
|
.render_report(&mut out, diag.as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor())
|
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||||
.render_report(&mut out, diag.inner())
|
.render_report(&mut out, diag.as_ref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
};
|
};
|
||||||
out
|
out
|
||||||
|
|
@ -481,7 +481,7 @@ fn disable_url_links() -> Result<(), MietteError> {
|
||||||
struct MyBad;
|
struct MyBad;
|
||||||
let err = MyBad;
|
let err = MyBad;
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
GraphicalReportPrinter::new_themed(GraphicalTheme::unicode_nocolor())
|
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||||
.without_code_linking()
|
.without_code_linking()
|
||||||
.render_report(&mut out, &err)
|
.render_report(&mut out, &err)
|
||||||
.unwrap();
|
.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