mirror of https://github.com/zkat/miette.git
Add dynamic diagnostic (#262)
This commit is contained in:
parent
675f3411e3
commit
024145dbdd
|
|
@ -43,7 +43,11 @@ jobs:
|
|||
- name: Clippy
|
||||
run: cargo clippy --all -- -D warnings
|
||||
- name: Run tests
|
||||
if: matrix.rust == 'stable'
|
||||
run: cargo test --all --verbose --features fancy
|
||||
- name: Run tests
|
||||
if: matrix.rust == '1.56.0'
|
||||
run: cargo test --all --verbose --features fancy no-format-args-capture
|
||||
|
||||
miri:
|
||||
name: Miri
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
## Introduction
|
||||
|
||||
Thank you so much for your interest in contributing!. All types of contributions are encouraged and valued. See the [table of contents](#toc) for different ways to help and details about how this project handles them!📝
|
||||
Thank you so much for your interest in contributing! All types of contributions are encouraged and valued. See the [table of contents](#toc) for different ways to help and details about how this project handles them!📝
|
||||
|
||||
Please make sure to read the relevant section before making your contribution! It will make it a lot easier for us maintainers to make the most of it and smooth out the experience for all involved. 💚
|
||||
|
||||
|
|
|
|||
11
Cargo.toml
11
Cargo.toml
|
|
@ -42,7 +42,16 @@ lazy_static = "1.4"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
fancy-no-backtrace = ["owo-colors", "is-terminal", "textwrap", "terminal_size", "supports-hyperlinks", "supports-color", "supports-unicode"]
|
||||
no-format-args-capture = []
|
||||
fancy-no-backtrace = [
|
||||
"owo-colors",
|
||||
"is-terminal",
|
||||
"textwrap",
|
||||
"terminal_size",
|
||||
"supports-hyperlinks",
|
||||
"supports-color",
|
||||
"supports-unicode",
|
||||
]
|
||||
fancy = ["fancy-no-backtrace", "backtrace", "backtrace-ext"]
|
||||
|
||||
[workspace]
|
||||
|
|
|
|||
67
README.md
67
README.md
|
|
@ -4,7 +4,7 @@
|
|||
You run miette? You run her code like the software? Oh. Oh! Error code for
|
||||
coder! Error code for One Thousand Lines!
|
||||
|
||||
### About
|
||||
## About
|
||||
|
||||
`miette` is a diagnostic library for Rust. It includes a series of
|
||||
traits/protocols that allow you to hook into its error reporting facilities,
|
||||
|
|
@ -32,7 +32,7 @@ output like in the screenshots above.** You should only do this in your
|
|||
toplevel crate, as the fancy feature pulls in a number of dependencies that
|
||||
libraries and such might not want.
|
||||
|
||||
### Table of Contents <!-- omit in toc -->
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [About](#about)
|
||||
- [Features](#features)
|
||||
|
|
@ -47,10 +47,11 @@ libraries and such might not want.
|
|||
- [... multiple related errors](#-multiple-related-errors)
|
||||
- [... delayed source code](#-delayed-source-code)
|
||||
- [... handler options](#-handler-options)
|
||||
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [License](#license)
|
||||
|
||||
### Features
|
||||
## Features
|
||||
|
||||
- Generic [`Diagnostic`] protocol, compatible (and dependent on)
|
||||
[`std::error::Error`].
|
||||
|
|
@ -75,7 +76,7 @@ the following features:
|
|||
- Cause chain printing
|
||||
- Turns diagnostic codes into links in [supported terminals](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
|
||||
|
||||
### Installing
|
||||
## Installing
|
||||
|
||||
```sh
|
||||
$ cargo add miette
|
||||
|
|
@ -87,7 +88,7 @@ If you want to use the fancy printer in all these screenshots:
|
|||
$ cargo add miette --features fancy
|
||||
```
|
||||
|
||||
### Example
|
||||
## Example
|
||||
|
||||
```rust
|
||||
/*
|
||||
|
|
@ -169,9 +170,9 @@ diagnostic help: Change int or string to be the right types and try again.
|
|||
diagnostic code: nu::parser::unsupported_operation
|
||||
For more details, see https://docs.rs/nu-parser/0.1.0/nu-parser/enum.ParseError.html#variant.UnsupportedOperation">
|
||||
|
||||
### Using
|
||||
## Using
|
||||
|
||||
#### ... in libraries
|
||||
### ... in libraries
|
||||
|
||||
`miette` is _fully compatible_ with library usage. Consumers who don't know
|
||||
about, or don't want, `miette` features can safely use its error types as
|
||||
|
|
@ -205,7 +206,7 @@ Then, return this error type from all your fallible public APIs. It's a best
|
|||
practice to wrap any "external" error types in your error `enum` instead of
|
||||
using something like [`Report`] in a library.
|
||||
|
||||
#### ... in application code
|
||||
### ... in application code
|
||||
|
||||
Application code tends to work a little differently than libraries. You
|
||||
don't always need or care to define dedicated error wrappers for errors
|
||||
|
|
@ -247,11 +248,11 @@ pub fn some_tool() -> Result<Version> {
|
|||
}
|
||||
```
|
||||
|
||||
To construct your own simple adhoc error use the `miette::miette!` macro:
|
||||
To construct your own simple adhoc error use the [`miette!`] macro:
|
||||
|
||||
```rust
|
||||
// my_app/lib/my_internal_file.rs
|
||||
use miette::{IntoDiagnostic, Result, WrapErr, miette};
|
||||
use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||
use semver::Version;
|
||||
|
||||
pub fn some_tool() -> Result<Version> {
|
||||
|
|
@ -262,9 +263,7 @@ pub fn some_tool() -> Result<Version> {
|
|||
}
|
||||
```
|
||||
|
||||
There are also similar `miette::bail!` and `miette::ensure!` macros.
|
||||
|
||||
#### ... in `main()`
|
||||
### ... in `main()`
|
||||
|
||||
`main()` is just like any other part of your application-internal code. Use
|
||||
`Result` as your return value, and it will pretty-print your diagnostics
|
||||
|
|
@ -294,7 +293,7 @@ enabled:
|
|||
miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||
```
|
||||
|
||||
#### ... diagnostic code URLs
|
||||
### ... diagnostic code URLs
|
||||
|
||||
`miette` supports providing a URL for individual diagnostics. This URL will
|
||||
be displayed as an actual link in supported terminals, like so:
|
||||
|
|
@ -347,7 +346,7 @@ use thiserror::Error;
|
|||
struct MyErr;
|
||||
```
|
||||
|
||||
#### ... snippets
|
||||
### ... snippets
|
||||
|
||||
Along with its general error handling and reporting features, `miette` also
|
||||
includes facilities for adding error spans/annotations/labels to your
|
||||
|
|
@ -395,8 +394,7 @@ pub struct MyErrorType {
|
|||
}
|
||||
```
|
||||
|
||||
##### ... help text
|
||||
|
||||
#### ... help text
|
||||
`miette` provides two facilities for supplying help text for your errors:
|
||||
|
||||
The first is the `#[help()]` format attribute that applies to structs or
|
||||
|
|
@ -432,7 +430,7 @@ let err = Foo {
|
|||
};
|
||||
```
|
||||
|
||||
#### ... multiple related errors
|
||||
### ... multiple related errors
|
||||
|
||||
`miette` supports collecting multiple errors into a single diagnostic, and
|
||||
printing them all together nicely.
|
||||
|
|
@ -452,7 +450,7 @@ struct MyError {
|
|||
}
|
||||
```
|
||||
|
||||
#### ... delayed source code
|
||||
### ... delayed source code
|
||||
|
||||
Sometimes it makes sense to add source code to the error message later.
|
||||
One option is to use [`with_source_code()`](Report::with_source_code)
|
||||
|
|
@ -535,7 +533,7 @@ fn main() -> miette::Result<()> {
|
|||
}
|
||||
```
|
||||
|
||||
#### ... Diagnostic-based error sources.
|
||||
### ... Diagnostic-based error sources.
|
||||
|
||||
When one uses the `#[source]` attribute on a field, that usually comes
|
||||
from `thiserror`, and implements a method for
|
||||
|
|
@ -568,7 +566,7 @@ struct MyError {
|
|||
struct OtherError;
|
||||
```
|
||||
|
||||
#### ... handler options
|
||||
### ... handler options
|
||||
|
||||
[`MietteHandler`] is the default handler, and is very customizable. In
|
||||
most cases, you can simply use [`MietteHandlerOpts`] to tweak its behavior
|
||||
|
|
@ -587,13 +585,32 @@ miette::set_hook(Box::new(|_| {
|
|||
.build(),
|
||||
)
|
||||
}))
|
||||
|
||||
```
|
||||
|
||||
See the docs for [`MietteHandlerOpts`] for more details on what you can
|
||||
customize!
|
||||
|
||||
### Acknowledgements
|
||||
### ... dynamic diagnostics
|
||||
|
||||
If you...
|
||||
- ...don't know all the possible errors upfront
|
||||
- ...need to serialize/deserialize errors
|
||||
then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||
[`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
|
||||
```rust
|
||||
let source = "2 + 2 * 2 = 8".to_string();
|
||||
let report = miette!(
|
||||
labels = vec[
|
||||
LabeledSpan::at(12..13, "this should be 6"),
|
||||
],
|
||||
help = "'*' has greater precedence than '+'",
|
||||
"Wrong answer"
|
||||
).with_source_code(source);
|
||||
println!("{:?}", report)
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
`miette` was not developed in a void. It owes enormous credit to various
|
||||
other projects and their authors:
|
||||
|
|
@ -612,7 +629,7 @@ other projects and their authors:
|
|||
- [`ariadne`](https://crates.io/crates/ariadne) for pushing forward how
|
||||
_pretty_ these diagnostics can really look!
|
||||
|
||||
### License
|
||||
## License
|
||||
|
||||
`miette` is released to the Rust community under the [Apache license
|
||||
2.0](./LICENSE).
|
||||
|
|
@ -623,11 +640,13 @@ under the Apache License. Some code is taken from
|
|||
[`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||
|
||||
[`miette!`]: https://docs.rs/miette/latest/miette/macro.miette.html
|
||||
[`diagnostic!`]: https://docs.rs/miette/latest/miette/macro.diagnostic.html
|
||||
[`std::error::Error`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html
|
||||
[`Diagnostic`]: https://docs.rs/miette/latest/miette/trait.Diagnostic.html
|
||||
[`IntoDiagnostic`]: https://docs.rs/miette/latest/miette/trait.IntoDiagnostic.html
|
||||
[`MietteHandlerOpts`]: https://docs.rs/miette/latest/miette/struct.MietteHandlerOpts.html
|
||||
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
||||
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
||||
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
|
||||
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
{{readme}}
|
||||
|
||||
[`miette!`]: https://docs.rs/miette/latest/miette/macro.miette.html
|
||||
[`diagnostic!`]: https://docs.rs/miette/latest/miette/macro.diagnostic.html
|
||||
[`std::error::Error`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html
|
||||
[`Diagnostic`]: https://docs.rs/miette/latest/miette/struct.Diagnostic.html
|
||||
[`Diagnostic`]: https://docs.rs/miette/latest/miette/trait.Diagnostic.html
|
||||
[`IntoDiagnostic`]: https://docs.rs/miette/latest/miette/trait.IntoDiagnostic.html
|
||||
[`MietteHandlerOpts`]: https://docs.rs/miette/latest/miette/struct.MietteHandlerOpts.html
|
||||
[`MietteHandler`]: https://docs.rs/miette/latest/miette/struct.MietteHandler.html
|
||||
[`MietteDiagnostic`]: https://docs.rs/miette/latest/miette/struct.MietteDiagnostic.html
|
||||
[`Report`]: https://docs.rs/miette/latest/miette/struct.Report.html
|
||||
[`ReportHandler`]: https://docs.rs/miette/latest/miette/struct.ReportHandler.html
|
||||
[`Result`]: https://docs.rs/miette/latest/miette/type.Result.html
|
||||
|
|
|
|||
|
|
@ -16,7 +16,14 @@
|
|||
/// # let resource = 0;
|
||||
/// #
|
||||
/// if !has_permission(user, resource) {
|
||||
/// bail!("permission denied for accessing {}", resource);
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#" bail!("permission denied for accessing {resource}");"#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#" bail!("permission denied for accessing {}", resource);"#
|
||||
)]
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
|
|
@ -48,17 +55,37 @@
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use miette::{bail, Result, Severity};
|
||||
///
|
||||
/// fn divide(x: f64, y: f64) -> Result<f64> {
|
||||
/// if y.abs() < 1e-3 {
|
||||
/// bail!(
|
||||
/// severity = Severity::Warning,
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#" "dividing by value ({y}) close to 0""#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#" "dividing by value ({}) close to 0", y"#
|
||||
)]
|
||||
/// );
|
||||
/// }
|
||||
/// Ok(x / y)
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($msg:literal $(,)?) => {
|
||||
return $crate::private::Err($crate::miette!($msg));
|
||||
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
|
||||
return $crate::private::Err(
|
||||
$crate::miette!($($key = $value,)* $fmt $($arg)*)
|
||||
);
|
||||
};
|
||||
($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.
|
||||
|
|
@ -105,11 +132,33 @@ macro_rules! bail {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use miette::{ensure, Result, Severity};
|
||||
///
|
||||
/// fn divide(x: f64, y: f64) -> Result<f64> {
|
||||
/// ensure!(
|
||||
/// y.abs() >= 1e-3,
|
||||
/// severity = Severity::Warning,
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#" "dividing by value ({y}) close to 0""#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#" "dividing by value ({}) close to 0", y"#
|
||||
)]
|
||||
/// );
|
||||
/// Ok(x / y)
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! ensure {
|
||||
($cond:expr, $msg:literal $(,)?) => {
|
||||
($cond:expr, $($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
return $crate::private::Err($crate::miette!($msg));
|
||||
return $crate::private::Err(
|
||||
$crate::miette!($($key = $value,)* $fmt $($arg)*)
|
||||
);
|
||||
}
|
||||
};
|
||||
($cond:expr, $err:expr $(,)?) => {
|
||||
|
|
@ -117,34 +166,57 @@ macro_rules! ensure {
|
|||
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.
|
||||
/// Construct an ad-hoc [`Report`].
|
||||
///
|
||||
/// 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
|
||||
/// # Examples
|
||||
///
|
||||
/// With string literal and interpolation:
|
||||
/// ```
|
||||
/// # type V = ();
|
||||
/// #
|
||||
/// use miette::{miette, Result};
|
||||
/// # use miette::miette;
|
||||
/// let x = 1;
|
||||
/// let y = 2;
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#"let report = miette!("{x} + {} = {z}", y, z = x + y);"#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#"let report = miette!("{} + {} = {z}", x, y, z = x + y);"#
|
||||
)]
|
||||
///
|
||||
/// fn lookup(key: &str) -> Result<V> {
|
||||
/// if key.len() != 16 {
|
||||
/// return Err(miette!("key length must be 16 characters, got {:?}", key));
|
||||
/// }
|
||||
/// assert_eq!(report.to_string().as_str(), "1 + 2 = 3");
|
||||
///
|
||||
/// // ...
|
||||
/// # Ok(())
|
||||
/// }
|
||||
/// let z = x + y;
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#"let report = miette!("{x} + {y} = {z}");"#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#"let report = miette!("{} + {} = {}", x, y, z);"#
|
||||
)]
|
||||
/// assert_eq!(report.to_string().as_str(), "1 + 2 = 3");
|
||||
/// ```
|
||||
///
|
||||
/// With [`diagnostic!`]-like arguments:
|
||||
/// ```
|
||||
/// use miette::{miette, LabeledSpan, Severity};
|
||||
///
|
||||
/// let source = "(2 + 2".to_string();
|
||||
/// let report = miette!(
|
||||
/// // Those fields are optional
|
||||
/// severity = Severity::Error,
|
||||
/// code = "expected::rparen",
|
||||
/// help = "always close your parens",
|
||||
/// labels = vec![LabeledSpan::at_offset(6, "here")],
|
||||
/// url = "https://example.com",
|
||||
/// // Rest of the arguments are passed to `format!`
|
||||
/// // to form diagnostic message
|
||||
/// "expected closing ')'"
|
||||
/// )
|
||||
/// .with_source_code(source);
|
||||
/// ```
|
||||
///
|
||||
/// ## `anyhow`/`eyre` Users
|
||||
|
|
@ -152,17 +224,69 @@ macro_rules! ensure {
|
|||
/// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`.
|
||||
#[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)
|
||||
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {
|
||||
$crate::Report::from(
|
||||
$crate::diagnostic!($($key = $value,)* $fmt $($arg)*)
|
||||
)
|
||||
};
|
||||
($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)*))
|
||||
};
|
||||
}
|
||||
|
||||
/// Construct a [`MietteDiagnostic`] in more user-friendly way.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{diagnostic, LabeledSpan, Severity};
|
||||
///
|
||||
/// let source = "(2 + 2".to_string();
|
||||
/// let diag = diagnostic!(
|
||||
/// // Those fields are optional
|
||||
/// severity = Severity::Error,
|
||||
/// code = "expected::rparen",
|
||||
/// help = "always close your parens",
|
||||
/// labels = vec![LabeledSpan::at_offset(6, "here")],
|
||||
/// url = "https://example.com",
|
||||
/// // Rest of the arguments are passed to `format!`
|
||||
/// // to form diagnostic message
|
||||
/// "expected closing ')'",
|
||||
/// );
|
||||
/// ```
|
||||
/// Diagnostic without any fields:
|
||||
/// ```
|
||||
/// # use miette::diagnostic;
|
||||
/// let x = 1;
|
||||
/// let y = 2;
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#" let diag = diagnostic!("{x} + {} = {z}", y, z = x + y);"#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#" let diag = diagnostic!("{} + {} = {z}", x, y, z = x + y);"#
|
||||
)]
|
||||
/// assert_eq!(diag.message, "1 + 2 = 3");
|
||||
///
|
||||
/// let z = x + y;
|
||||
#[cfg_attr(
|
||||
not(feature = "no-format-args-capture"),
|
||||
doc = r#"let diag = diagnostic!("{x} + {y} = {z}");"#
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "no-format-args-capture",
|
||||
doc = r#"let diag = diagnostic!("{} + {} = {}", x, y, z);"#
|
||||
)]
|
||||
/// assert_eq!(diag.message, "1 + 2 = 3");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! diagnostic {
|
||||
($($key:ident = $value:expr,)* $fmt:literal $($arg:tt)*) => {{
|
||||
let mut diag = $crate::MietteDiagnostic::new(format!($fmt $($arg)*));
|
||||
$(diag.$key = Some($value.into());)*
|
||||
diag
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
27
src/lib.rs
27
src/lib.rs
|
|
@ -46,6 +46,7 @@
|
|||
//! - [... multiple related errors](#-multiple-related-errors)
|
||||
//! - [... delayed source code](#-delayed-source-code)
|
||||
//! - [... handler options](#-handler-options)
|
||||
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||
//! - [Acknowledgements](#acknowledgements)
|
||||
//! - [License](#license)
|
||||
//!
|
||||
|
|
@ -249,7 +250,7 @@
|
|||
//! To construct your own simple adhoc error use the [miette!] macro:
|
||||
//! ```rust
|
||||
//! // my_app/lib/my_internal_file.rs
|
||||
//! use miette::{IntoDiagnostic, Result, WrapErr, miette};
|
||||
//! use miette::{miette, IntoDiagnostic, Result, WrapErr};
|
||||
//! use semver::Version;
|
||||
//!
|
||||
//! pub fn some_tool() -> Result<Version> {
|
||||
|
|
@ -590,6 +591,28 @@
|
|||
//! See the docs for [`MietteHandlerOpts`] for more details on what you can
|
||||
//! customize!
|
||||
//!
|
||||
//! ### ... dynamic diagnostics
|
||||
//!
|
||||
//! If you...
|
||||
//! - ...don't know all the possible errors upfront
|
||||
//! - ...need to serialize/deserialize errors
|
||||
//! then you may want to use [`miette!`], [`diagnostic!`] macros or
|
||||
//! [`MietteDiagnostic`] directly to create diagnostic on the fly.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! # use miette::{miette, LabeledSpan, Report};
|
||||
//!
|
||||
//! let source = "2 + 2 * 2 = 8".to_string();
|
||||
//! let report = miette!(
|
||||
//! labels = vec[
|
||||
//! LabeledSpan::at(12..13, "this should be 6"),
|
||||
//! ],
|
||||
//! help = "'*' has greater precedence than '+'",
|
||||
//! "Wrong answer"
|
||||
//! ).with_source_code(source);
|
||||
//! println!("{:?}", report)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Acknowledgements
|
||||
//!
|
||||
//! `miette` was not developed in a void. It owes enormous credit to various
|
||||
|
|
@ -624,6 +647,7 @@ pub use eyreish::*;
|
|||
#[cfg(feature = "fancy-no-backtrace")]
|
||||
pub use handler::*;
|
||||
pub use handlers::*;
|
||||
pub use miette_diagnostic::*;
|
||||
pub use named_source::*;
|
||||
#[cfg(feature = "fancy")]
|
||||
pub use panic::*;
|
||||
|
|
@ -638,6 +662,7 @@ mod handler;
|
|||
mod handlers;
|
||||
#[doc(hidden)]
|
||||
pub mod macro_helpers;
|
||||
mod miette_diagnostic;
|
||||
mod named_source;
|
||||
#[cfg(feature = "fancy")]
|
||||
mod panic;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
use crate::{Diagnostic, LabeledSpan, Severity};
|
||||
|
||||
/// Diagnostic that can be created at runtime.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MietteDiagnostic {
|
||||
/// Displayed diagnostic message
|
||||
pub message: String,
|
||||
/// Unique diagnostic code to look up more information
|
||||
/// about this Diagnostic. Ideally also globally unique, and documented
|
||||
/// in the toplevel crate's documentation for easy searching.
|
||||
/// Rust path format (`foo::bar::baz`) is recommended, but more classic
|
||||
/// codes like `E0123` will work just fine
|
||||
pub code: Option<String>,
|
||||
/// [`Diagnostic`] severity. Intended to be used by
|
||||
/// [`ReportHandler`](crate::ReportHandler)s to change the way different
|
||||
/// [`Diagnostic`]s are displayed. Defaults to [`Severity::Error`]
|
||||
pub severity: Option<Severity>,
|
||||
/// Additional help text related to this Diagnostic
|
||||
pub help: Option<String>,
|
||||
/// URL to visit for a more detailed explanation/help about this
|
||||
/// [`Diagnostic`].
|
||||
pub url: Option<String>,
|
||||
/// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
|
||||
pub labels: Option<Vec<LabeledSpan>>,
|
||||
}
|
||||
|
||||
impl Display for MietteDiagnostic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MietteDiagnostic {}
|
||||
|
||||
impl Diagnostic for MietteDiagnostic {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
self.code
|
||||
.as_ref()
|
||||
.map(Box::new)
|
||||
.map(|c| c as Box<dyn Display>)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Option<Severity> {
|
||||
self.severity
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
self.help
|
||||
.as_ref()
|
||||
.map(Box::new)
|
||||
.map(|c| c as Box<dyn Display>)
|
||||
}
|
||||
|
||||
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||
self.url
|
||||
.as_ref()
|
||||
.map(Box::new)
|
||||
.map(|c| c as Box<dyn Display>)
|
||||
}
|
||||
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
||||
self.labels
|
||||
.as_ref()
|
||||
.map(|ls| ls.iter().cloned())
|
||||
.map(Box::new)
|
||||
.map(|b| b as Box<dyn Iterator<Item = LabeledSpan>>)
|
||||
}
|
||||
}
|
||||
|
||||
impl MietteDiagnostic {
|
||||
/// Create a new dynamic diagnostic with the given message.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, MietteDiagnostic, Severity};
|
||||
///
|
||||
/// let diag = MietteDiagnostic::new("Oops, something went wrong!");
|
||||
/// assert_eq!(diag.to_string(), "Oops, something went wrong!");
|
||||
/// assert_eq!(diag.message, "Oops, something went wrong!");
|
||||
/// ```
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
labels: None,
|
||||
severity: None,
|
||||
code: None,
|
||||
help: None,
|
||||
url: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return new diagnostic with the given code.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, MietteDiagnostic};
|
||||
///
|
||||
/// let diag = MietteDiagnostic::new("Oops, something went wrong!").with_code("foo::bar::baz");
|
||||
/// assert_eq!(diag.message, "Oops, something went wrong!");
|
||||
/// assert_eq!(diag.code, Some("foo::bar::baz".to_string()));
|
||||
/// ```
|
||||
pub fn with_code(mut self, code: impl Into<String>) -> Self {
|
||||
self.code = Some(code.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with the given severity.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, MietteDiagnostic, Severity};
|
||||
///
|
||||
/// let diag = MietteDiagnostic::new("I warn you to stop!").with_severity(Severity::Warning);
|
||||
/// assert_eq!(diag.message, "I warn you to stop!");
|
||||
/// assert_eq!(diag.severity, Some(Severity::Warning));
|
||||
/// ```
|
||||
pub fn with_severity(mut self, severity: Severity) -> Self {
|
||||
self.severity = Some(severity);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with the given help message.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, MietteDiagnostic};
|
||||
///
|
||||
/// let diag = MietteDiagnostic::new("PC is not working").with_help("Try to reboot it again");
|
||||
/// assert_eq!(diag.message, "PC is not working");
|
||||
/// assert_eq!(diag.help, Some("Try to reboot it again".to_string()));
|
||||
/// ```
|
||||
pub fn with_help(mut self, help: impl Into<String>) -> Self {
|
||||
self.help = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with the given URL.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, MietteDiagnostic};
|
||||
///
|
||||
/// let diag = MietteDiagnostic::new("PC is not working")
|
||||
/// .with_url("https://letmegooglethat.com/?q=Why+my+pc+doesn%27t+work");
|
||||
/// assert_eq!(diag.message, "PC is not working");
|
||||
/// assert_eq!(
|
||||
/// diag.url,
|
||||
/// Some("https://letmegooglethat.com/?q=Why+my+pc+doesn%27t+work".to_string())
|
||||
/// );
|
||||
/// ```
|
||||
pub fn with_url(mut self, url: impl Into<String>) -> Self {
|
||||
self.url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with the given label.
|
||||
///
|
||||
/// Discards previous labels
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
|
||||
///
|
||||
/// let source = "cpp is the best language";
|
||||
///
|
||||
/// let label = LabeledSpan::at(0..3, "This should be Rust");
|
||||
/// let diag = MietteDiagnostic::new("Wrong best language").with_label(label.clone());
|
||||
/// assert_eq!(diag.message, "Wrong best language");
|
||||
/// assert_eq!(diag.labels, Some(vec![label]));
|
||||
/// ```
|
||||
pub fn with_label(mut self, label: impl Into<LabeledSpan>) -> Self {
|
||||
self.labels = Some(vec![label.into()]);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with the given labels.
|
||||
///
|
||||
/// Discards previous labels
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
|
||||
///
|
||||
/// let source = "helo wrld";
|
||||
///
|
||||
/// let labels = vec![
|
||||
/// LabeledSpan::at_offset(3, "add 'l'"),
|
||||
/// LabeledSpan::at_offset(6, "add 'r'"),
|
||||
/// ];
|
||||
/// let diag = MietteDiagnostic::new("Typos in 'hello world'").with_labels(labels.clone());
|
||||
/// assert_eq!(diag.message, "Typos in 'hello world'");
|
||||
/// assert_eq!(diag.labels, Some(labels));
|
||||
/// ```
|
||||
pub fn with_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
||||
self.labels = Some(labels.into_iter().collect());
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with new label added to the existing ones.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
|
||||
///
|
||||
/// let source = "helo wrld";
|
||||
///
|
||||
/// let label1 = LabeledSpan::at_offset(3, "add 'l'");
|
||||
/// let label2 = LabeledSpan::at_offset(6, "add 'r'");
|
||||
/// let diag = MietteDiagnostic::new("Typos in 'hello world'")
|
||||
/// .and_label(label1.clone())
|
||||
/// .and_label(label2.clone());
|
||||
/// assert_eq!(diag.message, "Typos in 'hello world'");
|
||||
/// assert_eq!(diag.labels, Some(vec![label1, label2]));
|
||||
/// ```
|
||||
pub fn and_label(mut self, label: impl Into<LabeledSpan>) -> Self {
|
||||
let mut labels = self.labels.unwrap_or_default();
|
||||
labels.push(label.into());
|
||||
self.labels = Some(labels);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return new diagnostic with new labels added to the existing ones.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::{Diagnostic, LabeledSpan, MietteDiagnostic};
|
||||
///
|
||||
/// let source = "helo wrld";
|
||||
///
|
||||
/// let label1 = LabeledSpan::at_offset(3, "add 'l'");
|
||||
/// let label2 = LabeledSpan::at_offset(6, "add 'r'");
|
||||
/// let label3 = LabeledSpan::at_offset(9, "add '!'");
|
||||
/// let diag = MietteDiagnostic::new("Typos in 'hello world!'")
|
||||
/// .and_label(label1.clone())
|
||||
/// .and_labels([label2.clone(), label3.clone()]);
|
||||
/// assert_eq!(diag.message, "Typos in 'hello world!'");
|
||||
/// assert_eq!(diag.labels, Some(vec![label1, label2, label3]));
|
||||
/// ```
|
||||
pub fn and_labels(mut self, labels: impl IntoIterator<Item = LabeledSpan>) -> Self {
|
||||
let mut all_labels = self.labels.unwrap_or_default();
|
||||
all_labels.extend(labels.into_iter());
|
||||
self.labels = Some(all_labels);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -160,7 +160,7 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Sen
|
|||
/**
|
||||
[`Diagnostic`] severity. Intended to be used by
|
||||
[`ReportHandler`](crate::ReportHandler)s to change the way different
|
||||
[`Diagnostic`]s are displayed.
|
||||
[`Diagnostic`]s are displayed. Defaults to [`Severity::Error`].
|
||||
*/
|
||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Severity {
|
||||
|
|
@ -169,9 +169,16 @@ pub enum Severity {
|
|||
/// Warning. Please take note.
|
||||
Warning,
|
||||
/// Critical failure. The program cannot continue.
|
||||
/// This is the default severity, if you don't specify another one.
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for Severity {
|
||||
fn default() -> Self {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Represents readable source code of some sort.
|
||||
|
||||
|
|
@ -218,6 +225,54 @@ impl LabeledSpan {
|
|||
}
|
||||
}
|
||||
|
||||
/// Makes a new label at specified span
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::LabeledSpan;
|
||||
///
|
||||
/// let source = "Cpp is the best";
|
||||
/// let label = LabeledSpan::at(0..3, "should be Rust");
|
||||
/// assert_eq!(
|
||||
/// label,
|
||||
/// LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
|
||||
/// )
|
||||
/// ```
|
||||
pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
|
||||
Self::new_with_span(Some(label.into()), span)
|
||||
}
|
||||
|
||||
/// Makes a new label that points at a specific offset.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::LabeledSpan;
|
||||
///
|
||||
/// let source = "(2 + 2";
|
||||
/// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
|
||||
/// assert_eq!(
|
||||
/// label,
|
||||
/// LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
|
||||
/// )
|
||||
/// ```
|
||||
pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
|
||||
Self::new(Some(label.into()), offset, 0)
|
||||
}
|
||||
|
||||
/// Makes a new label without text, that underlines a specific span.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use miette::LabeledSpan;
|
||||
///
|
||||
/// let source = "You have an eror here";
|
||||
/// let label = LabeledSpan::underline(12..16);
|
||||
/// assert_eq!(label, LabeledSpan::new(None, 12, 4))
|
||||
/// ```
|
||||
pub fn underline(span: impl Into<SourceSpan>) -> Self {
|
||||
Self::new_with_span(None, span)
|
||||
}
|
||||
|
||||
/// Gets the (optional) label string for this `LabeledSpan`.
|
||||
pub fn label(&self) -> Option<&str> {
|
||||
self.label.as_deref()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use miette::{miette, Report};
|
||||
|
||||
fn error() -> Report {
|
||||
miette!(0).wrap_err(1).wrap_err(2).wrap_err(3)
|
||||
miette!("0").wrap_err(1).wrap_err(2).wrap_err(3)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ mod drop;
|
|||
|
||||
use self::common::*;
|
||||
use self::drop::{DetectDrop, Flag};
|
||||
use miette::{Diagnostic, Report};
|
||||
use miette::{Diagnostic, MietteDiagnostic, Report};
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
|
@ -12,11 +12,19 @@ use std::io;
|
|||
fn test_downcast() {
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_literal().unwrap_err().downcast::<&str>().unwrap(),
|
||||
bail_literal()
|
||||
.unwrap_err()
|
||||
.downcast::<MietteDiagnostic>()
|
||||
.unwrap()
|
||||
.message,
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_fmt().unwrap_err().downcast::<String>().unwrap(),
|
||||
bail_fmt()
|
||||
.unwrap_err()
|
||||
.downcast::<MietteDiagnostic>()
|
||||
.unwrap()
|
||||
.message,
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
|
|
@ -32,11 +40,19 @@ fn test_downcast() {
|
|||
fn test_downcast_ref() {
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
*bail_literal().unwrap_err().downcast_ref::<&str>().unwrap(),
|
||||
bail_literal()
|
||||
.unwrap_err()
|
||||
.downcast_ref::<MietteDiagnostic>()
|
||||
.unwrap()
|
||||
.message,
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_fmt().unwrap_err().downcast_ref::<String>().unwrap(),
|
||||
bail_fmt()
|
||||
.unwrap_err()
|
||||
.downcast_ref::<MietteDiagnostic>()
|
||||
.unwrap()
|
||||
.message,
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
|
|
@ -52,11 +68,19 @@ fn test_downcast_ref() {
|
|||
fn test_downcast_mut() {
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
*bail_literal().unwrap_err().downcast_mut::<&str>().unwrap(),
|
||||
bail_literal()
|
||||
.unwrap_err()
|
||||
.downcast_mut::<MietteDiagnostic>()
|
||||
.unwrap()
|
||||
.message,
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
bail_fmt().unwrap_err().downcast_mut::<String>().unwrap(),
|
||||
bail_fmt()
|
||||
.unwrap_err()
|
||||
.downcast_mut::<MietteDiagnostic>()
|
||||
.unwrap()
|
||||
.message,
|
||||
);
|
||||
assert_eq!(
|
||||
"oh no!",
|
||||
|
|
|
|||
Loading…
Reference in New Issue