Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ rust-version = "1.70.0"
exclude = ["images/", "tests/", "miette-derive/"]

[dependencies]
thiserror = "2.0.11"
miette-derive = { path = "miette-derive", version = "=7.5.0", optional = true }
unicode-width = "0.1.11"
cfg-if = "1.0.0"
Expand All @@ -30,6 +29,7 @@ serde = { version = "1.0.196", features = ["derive"], optional = true }
syntect = { version = "5.1.0", optional = true }

[dev-dependencies]
thiserror = "2.0.11"
semver = "1.0.21"

# Eyre devdeps
Expand Down
88 changes: 81 additions & 7 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
use std::{fmt, io};

use thiserror::Error;
use std::{
error::Error,
fmt::{self, Display},
io,
};

use crate::Diagnostic;

/**
Error enum for miette. Used by certain operations in the protocol.
*/
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum MietteError {
/// Wrapper around [`std::io::Error`]. This is returned when something went
/// wrong while reading a [`SourceCode`](crate::SourceCode).
#[error(transparent)]
IoError(#[from] io::Error),
IoError(io::Error),

/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
/// bounds of a given [`SourceCode`](crate::SourceCode).
#[error("The given offset is outside the bounds of its Source")]
OutOfBounds,
}

impl Display for MietteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MietteError::IoError(error) => write!(f, "{error}"),
MietteError::OutOfBounds => {
write!(f, "The given offset is outside the bounds of its Source")
}
}
}
}

impl Error for MietteError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
MietteError::IoError(error) => error.source(),
MietteError::OutOfBounds => None,
}
}
}

impl From<io::Error> for MietteError {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}

impl Diagnostic for MietteError {
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
match self {
Expand Down Expand Up @@ -49,3 +75,51 @@ impl Diagnostic for MietteError {
)))
}
}

#[cfg(test)]
pub(crate) mod tests {
use std::{error::Error, io::ErrorKind};

use super::*;

#[derive(Debug)]
pub(crate) struct TestError(pub(crate) io::Error);

impl Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "testing, testing...")
}
}

impl Error for TestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.0)
}
}

#[test]
fn io_error() {
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
let outer_error = TestError(inner_error);
let io_error = io::Error::new(ErrorKind::Other, outer_error);

let miette_error = MietteError::from(io_error);

assert_eq!(miette_error.to_string(), "testing, testing...");
assert_eq!(
miette_error.source().unwrap().to_string(),
"halt and catch fire"
);
}

#[test]
fn out_of_bounds() {
let miette_error = MietteError::OutOfBounds;

assert_eq!(
miette_error.to_string(),
"The given offset is outside the bounds of its Source"
);
assert_eq!(miette_error.source().map(ToString::to_string), None);
}
}
2 changes: 1 addition & 1 deletion src/eyreish/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ impl Report {
/// The root cause is the last error in the iterator produced by
/// [`chain()`](Report::chain).
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
self.chain().last().unwrap()
self.chain().next_back().unwrap()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was one of the clippy lints! Because that iterator is double-ended, you can just pop from the back to get the last element more efficiently!

Apparently calling .last() is wasteful in this case because it iterates through the whole iterator before it can return the last value (https://rust-lang.github.io/rust-clippy/stable/index.html#/next_back)

I hadn't thought about that before!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Huh! Good call

}

/// Returns true if `E` is the type held by this error object.
Expand Down
41 changes: 38 additions & 3 deletions src/eyreish/into_diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
use thiserror::Error;
use std::{error::Error, fmt::Display};

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)]
#[derive(Debug)]
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);

impl Display for DiagnosticError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = &self.0;
write!(f, "{msg}")
}
}
impl Error for DiagnosticError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
}

impl Diagnostic for DiagnosticError {}

/**
Expand All @@ -31,3 +43,26 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
self.map_err(|e| DiagnosticError(Box::new(e)).into())
}
}

#[cfg(test)]
mod tests {
use std::io::{self, ErrorKind};

use super::*;

use crate::error::tests::TestError;

#[test]
fn diagnostic_error() {
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
let outer_error: Result<(), _> = Err(TestError(inner_error));

let diagnostic_error = outer_error.into_diagnostic().unwrap_err();

assert_eq!(diagnostic_error.to_string(), "testing, testing...");
assert_eq!(
diagnostic_error.source().unwrap().to_string(),
"halt and catch fire"
);
}
}
5 changes: 4 additions & 1 deletion src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ impl HighlighterOption {
highlighter: Option<MietteHighlighter>,
supports_color: bool,
) -> HighlighterOption {
if color == Some(false) || (color == None && !supports_color) {
if color == Some(false) || (color.is_none() && !supports_color) {
return HighlighterOption::Disable;
}
highlighter
Expand All @@ -433,6 +433,9 @@ impl HighlighterOption {
}
}

// NOTE: This is manually implemented so that it's clearer what's going on with
// the conditional compilation — clippy isn't picking up the `cfg` stuff here
#[allow(clippy::derivable_impls)]
impl Default for HighlighterOption {
fn default() -> Self {
cfg_if! {
Expand Down
6 changes: 3 additions & 3 deletions src/highlighters/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ impl SyntectHighlighter {
}
}
// finally, attempt to guess syntax based on first line
return self.syntax_set.find_syntax_by_first_line(
self.syntax_set.find_syntax_by_first_line(
std::str::from_utf8(contents.data())
.ok()?
.split('\n')
.next()?,
);
)
}
}

Expand All @@ -115,7 +115,7 @@ pub(crate) struct SyntectHighlighterState<'h> {
use_bg_color: bool,
}

impl<'h> HighlighterState for SyntectHighlighterState<'h> {
impl HighlighterState for SyntectHighlighterState<'_> {
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
let use_bg_color = self.use_bg_color;
Expand Down
32 changes: 32 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
//! - [... handler options](#-handler-options)
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
//! - [... syntax highlighting](#-syntax-highlighting)
//! - [... primary label](#-primary-label)
//! - [... collection of labels](#-collection-of-labels)
//! - [Acknowledgements](#acknowledgements)
//! - [License](#license)
Expand Down Expand Up @@ -690,6 +691,37 @@
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
//! method. See the [`highlighters`] module docs for more details.
//!
//! ### ... primary label
//!
//! You can use the `primary` parameter to `label` to indicate that the label
//! is the primary label.
//!
//! ```rust,ignore
//! #[derive(Debug, Diagnostic, Error)]
//! #[error("oops!")]
//! struct MyError {
//! #[label(primary, "main issue")]
//! primary_span: SourceSpan,
//!
//! #[label("other label")]
//! other_span: SourceSpan,
//! }
//! ```
//!
//! The `primary` parameter can be used at most once:
//!
//! ```rust,ignore
//! #[derive(Debug, Diagnostic, Error)]
//! #[error("oops!")]
//! struct MyError {
//! #[label(primary, "main issue")]
//! primary_span: SourceSpan,
//!
//! #[label(primary, "other label")] // Error: Cannot have more than one primary label.
//! other_span: SourceSpan,
//! }
//! ```
//!
//! ### ... collection of labels
//!
//! When the number of labels is unknown, you can use a collection of `SourceSpan`
Expand Down
56 changes: 51 additions & 5 deletions src/panic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::{error::Error, fmt::Display};

use backtrace::Backtrace;
use thiserror::Error;

use crate::{self as miette, Context, Diagnostic, Result};
use crate::{Context, Diagnostic, Result};

/// Tells miette to render panics using its rendering engine.
pub fn set_panic_hook() {
Expand All @@ -25,11 +26,27 @@ pub fn set_panic_hook() {
}));
}

#[derive(Debug, Error, Diagnostic)]
#[error("{0}{panic}", panic = Panic::backtrace())]
#[diagnostic(help("set the `RUST_BACKTRACE=1` environment variable to display a backtrace."))]
#[derive(Debug)]
struct Panic(String);

impl Display for Panic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = &self.0;
let panic = Panic::backtrace();
write!(f, "{msg}{panic}")
}
}

impl Error for Panic {}

impl Diagnostic for Panic {
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Some(Box::new(
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace.",
))
}
}

impl Panic {
fn backtrace() -> String {
use std::fmt::Write;
Expand Down Expand Up @@ -84,3 +101,32 @@ impl Panic {
"".into()
}
}

#[cfg(test)]
mod tests {
use std::error::Error;

use super::*;

#[test]
fn panic() {
let panic = Panic("ruh roh raggy".to_owned());

assert_eq!(panic.to_string(), "ruh roh raggy");
assert!(panic.source().is_none());
assert!(panic.code().is_none());
assert!(panic.severity().is_none());
assert_eq!(
panic.help().map(|h| h.to_string()),
Some(
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace."
.to_owned()
)
);
assert!(panic.url().is_none());
assert!(panic.source_code().is_none());
assert!(panic.labels().is_none());
assert!(panic.related().is_none());
assert!(panic.diagnostic_source().is_none());
}
}
15 changes: 2 additions & 13 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::MietteError;
use crate::{DiagnosticError, MietteError};

/// Adds rich metadata to your Error that can be used by
/// [`Report`](crate::Report) to print really nice and human-friendly error
Expand Down Expand Up @@ -174,18 +174,7 @@ impl From<String> for Box<dyn Diagnostic + Send + Sync> {

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))
Box::new(DiagnosticError(s))
}
}

Expand Down