Add dynamic diagnostic (#262)

This commit is contained in:
Gavrilikhin Daniil 2023-05-14 04:59:43 +08:00 committed by GitHub
parent 675f3411e3
commit 024145dbdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 585 additions and 73 deletions

View File

@ -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

View File

@ -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. 💚

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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
}};
}

View File

@ -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;

250
src/miette_diagnostic.rs Normal file
View File

@ -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
}
}

View File

@ -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()

View File

@ -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]

View File

@ -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!",