Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic diagnostic #262

Merged
merged 30 commits into from
May 13, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
09d7d15
Remove dot
gavrilikhin-d May 5, 2023
e6d2eed
Add skeleton
gavrilikhin-d May 5, 2023
ab20069
DynamicDiagnostic -> MietteDiagnostic
gavrilikhin-d May 6, 2023
01b34a4
Make `Severity::Error` to be default severity
gavrilikhin-d May 6, 2023
61d43f8
Add severity field
gavrilikhin-d May 6, 2023
53b21ea
Add help field
gavrilikhin-d May 6, 2023
a1f602c
Add url field
gavrilikhin-d May 6, 2023
c6e977b
Add labels field
gavrilikhin-d May 6, 2023
a3ee52a
Add convenience function to `LabeledSpan`
gavrilikhin-d May 6, 2023
8aaba99
Use convenience functions in examples
gavrilikhin-d May 6, 2023
021eb01
Adjust `miette!` a little bit
gavrilikhin-d May 6, 2023
b9a892f
Use `Option<Severity>`
gavrilikhin-d May 6, 2023
c2d793e
labels: `Option<Vec<_>>`
gavrilikhin-d May 6, 2023
bcf18f8
Fully implement support for `MietteDiagnostic`-like arguments in `mie…
gavrilikhin-d May 6, 2023
287ffc5
Add `miette_diagnostic!`
gavrilikhin-d May 6, 2023
4f0bc3e
Add `ensure!` support
gavrilikhin-d May 6, 2023
52e2dcb
Add `bail!` support
gavrilikhin-d May 6, 2023
7c4dd12
Add docs
gavrilikhin-d May 6, 2023
0e5512a
Add dot
gavrilikhin-d May 7, 2023
e35a63a
description -> message
gavrilikhin-d May 7, 2023
4148552
`miette_diagnostic!` -> `diagnostic!`
gavrilikhin-d May 7, 2023
b658720
Add `and_label(s)`
gavrilikhin-d May 7, 2023
cb5a1d3
Implement interpolation
gavrilikhin-d May 7, 2023
55f41a6
Remove literal case from ensure
gavrilikhin-d May 7, 2023
3e62212
Fix macro
gavrilikhin-d May 12, 2023
5e67953
Use `mut self` in builder functions
gavrilikhin-d May 12, 2023
5c94dd0
Sync README.md
gavrilikhin-d May 12, 2023
253a0f8
Update .tpl
gavrilikhin-d May 12, 2023
4520d66
Fix clippy
gavrilikhin-d May 13, 2023
7fb4cb2
Add and use `no-format-args-capture` flag
gavrilikhin-d May 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 💚

Expand Down
31 changes: 25 additions & 6 deletions src/eyreish/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,11 @@ macro_rules! ensure {
};
}

/// 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 = ();
/// #
Expand All @@ -147,6 +144,23 @@ macro_rules! ensure {
/// }
/// ```
///
/// With [`MietteDiagnostic`]-like arguments:
/// ```
/// use miette::{miette, LabeledSpan, Severity};
///
/// let source = "(2 + 2".to_string();
/// let report = miette!(
gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
/// "expected closing ')'",
/// // 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"
/// )
/// .with_source_code(source);
/// ```
///
/// ## `anyhow`/`eyre` Users
///
/// You can just replace `use`s of the `anyhow!`/`eyre!` macros with `miette!`.
Expand All @@ -162,6 +176,11 @@ macro_rules! miette {
let error = $err;
(&error).miette_kind().new(error)
});
($fmt:expr $(, $key:ident = $value:expr)* $(,)?) => {{
let mut diag = $crate::MietteDiagnostic::new(format!("{}", $fmt));
$(diag.$key = Some($value.into());)*
$crate::Report::from(diag)
}};
($fmt:expr, $($arg:tt)*) => {
$crate::private::new_adhoc(format!($fmt, $($arg)*))
};
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,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};
gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
//! use semver::Version;
//!
//! pub fn some_tool() -> Result<Version> {
Expand Down Expand Up @@ -624,6 +624,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::*;
Expand All @@ -638,6 +639,7 @@ mod handler;
mod handlers;
#[doc(hidden)]
pub mod macro_helpers;
mod miette_diagnostic;
mod named_source;
#[cfg(feature = "fancy")]
mod panic;
Expand Down
215 changes: 215 additions & 0 deletions src/miette_diagnostic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
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 description
pub description: String,
gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
/// 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.description)
}
}

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> {
gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
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 description.
///
/// # 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.description, "Oops, something went wrong!");
/// ```
pub fn new(description: impl Into<String>) -> Self {
Self {
description: description.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.description, "Oops, something went wrong!");
/// assert_eq!(diag.code, Some("foo::bar::baz".to_string()));
/// ```
pub fn with_code(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.description, "I warn you to stop!");
/// assert_eq!(diag.severity, Some(Severity::Warning));
/// ```
pub fn with_severity(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.description, "PC is not working");
/// assert_eq!(diag.help, Some("Try to reboot it again".to_string()));
/// ```
pub fn with_help(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.description, "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(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.description, "Wrong best language");
/// assert_eq!(diag.labels, Some(vec![label]));
/// ```
pub fn with_label(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.description, "Typos in 'hello world'");
/// assert_eq!(diag.labels, Some(labels));
/// ```
pub fn with_labels(self, labels: Vec<LabeledSpan>) -> Self {
Self {
labels: Some(labels),
..self
}
}
gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
}
57 changes: 56 additions & 1 deletion src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
Error,
}

impl Default for Severity {
fn default() -> Self {
Severity::Error
}
}

/**
Represents readable source code of some sort.

Expand Down Expand Up @@ -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.into(), 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)
}

gavrilikhin-d marked this conversation as resolved.
Show resolved Hide resolved
/// Gets the (optional) label string for this `LabeledSpan`.
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
Expand Down