fix(deps): miette can now be used without syn (#436)

* style(clippy): quickly fix up clippy lints

* refactor(miette): remove `thiserror` from `error.rs`

* fix(fancy): `fancy` no longer depends on `derive`

Before this change, the `fancy` feature required the `derive` feature to
compile. Despite this, `derive` was not listed as a required feature to
use `fancy`. This meant that building with:
`cargo build --no-default-features --features fancy` would result in a
compilation error!

Now `fancy` can be used without `derive` enabled, and another use of
`thiserror` has been removed!

* refactor(miette): remove `thiserror` from `into_diagnostic.rs`

* refactor(miette): reuse `DiagnosticError` in `protocol.rs`

* refactor(miette): make `thiserror` a dev-dependency

* fix(miette): correctly forward error sources

* fix(miette): match `TestError` visibility with `mod tests`

* fix(miette): maintain 1.70 MSRV

* fix(miette): another fix for MSRV 1.70

* docs(miette): sync README and `rustdoc`
This commit is contained in:
Brooks Rady 2025-04-26 19:34:21 +01:00 committed by GitHub
parent 59c81617de
commit 521ef91f77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 214 additions and 35 deletions

View File

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

View File

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

View File

@ -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()
}
/// Returns true if `E` is the type held by this error object.

View File

@ -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 {}
/**
@ -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"
);
}
}

View File

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

View File

@ -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()?,
);
)
}
}
@ -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;

View File

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

View File

@ -1,7 +1,8 @@
use backtrace::Backtrace;
use thiserror::Error;
use std::{error::Error, fmt::Display};
use crate::{self as miette, Context, Diagnostic, Result};
use backtrace::Backtrace;
use crate::{Context, Diagnostic, Result};
/// Tells miette to render panics using its rendering engine.
pub fn set_panic_hook() {
@ -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;
@ -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());
}
}

View File

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