Skip to content

Commit 521ef91

Browse files
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`
1 parent 59c8161 commit 521ef91

File tree

9 files changed

+213
-34
lines changed

9 files changed

+213
-34
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ rust-version = "1.70.0"
1313
exclude = ["images/", "tests/", "miette-derive/"]
1414

1515
[dependencies]
16-
thiserror = "2.0.11"
1716
miette-derive = { path = "miette-derive", version = "=7.5.0", optional = true }
1817
unicode-width = "0.1.11"
1918
cfg-if = "1.0.0"
@@ -30,6 +29,7 @@ serde = { version = "1.0.196", features = ["derive"], optional = true }
3029
syntect = { version = "5.1.0", optional = true }
3130

3231
[dev-dependencies]
32+
thiserror = "2.0.11"
3333
semver = "1.0.21"
3434

3535
# Eyre devdeps

src/error.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,51 @@
1-
use std::{fmt, io};
2-
3-
use thiserror::Error;
1+
use std::{
2+
error::Error,
3+
fmt::{self, Display},
4+
io,
5+
};
46

57
use crate::Diagnostic;
68

79
/**
810
Error enum for miette. Used by certain operations in the protocol.
911
*/
10-
#[derive(Debug, Error)]
12+
#[derive(Debug)]
1113
pub enum MietteError {
1214
/// Wrapper around [`std::io::Error`]. This is returned when something went
1315
/// wrong while reading a [`SourceCode`](crate::SourceCode).
14-
#[error(transparent)]
15-
IoError(#[from] io::Error),
16+
IoError(io::Error),
1617

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

23+
impl Display for MietteError {
24+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25+
match self {
26+
MietteError::IoError(error) => write!(f, "{error}"),
27+
MietteError::OutOfBounds => {
28+
write!(f, "The given offset is outside the bounds of its Source")
29+
}
30+
}
31+
}
32+
}
33+
34+
impl Error for MietteError {
35+
fn source(&self) -> Option<&(dyn Error + 'static)> {
36+
match self {
37+
MietteError::IoError(error) => error.source(),
38+
MietteError::OutOfBounds => None,
39+
}
40+
}
41+
}
42+
43+
impl From<io::Error> for MietteError {
44+
fn from(value: io::Error) -> Self {
45+
Self::IoError(value)
46+
}
47+
}
48+
2349
impl Diagnostic for MietteError {
2450
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
2551
match self {
@@ -49,3 +75,51 @@ impl Diagnostic for MietteError {
4975
)))
5076
}
5177
}
78+
79+
#[cfg(test)]
80+
pub(crate) mod tests {
81+
use std::{error::Error, io::ErrorKind};
82+
83+
use super::*;
84+
85+
#[derive(Debug)]
86+
pub(crate) struct TestError(pub(crate) io::Error);
87+
88+
impl Display for TestError {
89+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90+
write!(f, "testing, testing...")
91+
}
92+
}
93+
94+
impl Error for TestError {
95+
fn source(&self) -> Option<&(dyn Error + 'static)> {
96+
Some(&self.0)
97+
}
98+
}
99+
100+
#[test]
101+
fn io_error() {
102+
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
103+
let outer_error = TestError(inner_error);
104+
let io_error = io::Error::new(ErrorKind::Other, outer_error);
105+
106+
let miette_error = MietteError::from(io_error);
107+
108+
assert_eq!(miette_error.to_string(), "testing, testing...");
109+
assert_eq!(
110+
miette_error.source().unwrap().to_string(),
111+
"halt and catch fire"
112+
);
113+
}
114+
115+
#[test]
116+
fn out_of_bounds() {
117+
let miette_error = MietteError::OutOfBounds;
118+
119+
assert_eq!(
120+
miette_error.to_string(),
121+
"The given offset is outside the bounds of its Source"
122+
);
123+
assert_eq!(miette_error.source().map(ToString::to_string), None);
124+
}
125+
}

src/eyreish/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ impl Report {
280280
/// The root cause is the last error in the iterator produced by
281281
/// [`chain()`](Report::chain).
282282
pub fn root_cause(&self) -> &(dyn StdError + 'static) {
283-
self.chain().last().unwrap()
283+
self.chain().next_back().unwrap()
284284
}
285285

286286
/// Returns true if `E` is the type held by this error object.

src/eyreish/into_diagnostic.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
use thiserror::Error;
1+
use std::{error::Error, fmt::Display};
22

33
use crate::{Diagnostic, Report};
44

55
/// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for
66
/// Errors. This is intended to be paired with [`IntoDiagnostic`].
7-
#[derive(Debug, Error)]
8-
#[error(transparent)]
7+
#[derive(Debug)]
98
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
9+
10+
impl Display for DiagnosticError {
11+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12+
let msg = &self.0;
13+
write!(f, "{msg}")
14+
}
15+
}
16+
impl Error for DiagnosticError {
17+
fn source(&self) -> Option<&(dyn Error + 'static)> {
18+
self.0.source()
19+
}
20+
}
21+
1022
impl Diagnostic for DiagnosticError {}
1123

1224
/**
@@ -31,3 +43,26 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
3143
self.map_err(|e| DiagnosticError(Box::new(e)).into())
3244
}
3345
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use std::io::{self, ErrorKind};
50+
51+
use super::*;
52+
53+
use crate::error::tests::TestError;
54+
55+
#[test]
56+
fn diagnostic_error() {
57+
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
58+
let outer_error: Result<(), _> = Err(TestError(inner_error));
59+
60+
let diagnostic_error = outer_error.into_diagnostic().unwrap_err();
61+
62+
assert_eq!(diagnostic_error.to_string(), "testing, testing...");
63+
assert_eq!(
64+
diagnostic_error.source().unwrap().to_string(),
65+
"halt and catch fire"
66+
);
67+
}
68+
}

src/handler.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ impl HighlighterOption {
424424
highlighter: Option<MietteHighlighter>,
425425
supports_color: bool,
426426
) -> HighlighterOption {
427-
if color == Some(false) || (color == None && !supports_color) {
427+
if color == Some(false) || (color.is_none() && !supports_color) {
428428
return HighlighterOption::Disable;
429429
}
430430
highlighter
@@ -433,6 +433,9 @@ impl HighlighterOption {
433433
}
434434
}
435435

436+
// NOTE: This is manually implemented so that it's clearer what's going on with
437+
// the conditional compilation — clippy isn't picking up the `cfg` stuff here
438+
#[allow(clippy::derivable_impls)]
436439
impl Default for HighlighterOption {
437440
fn default() -> Self {
438441
cfg_if! {

src/highlighters/syntect.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ impl SyntectHighlighter {
9696
}
9797
}
9898
// finally, attempt to guess syntax based on first line
99-
return self.syntax_set.find_syntax_by_first_line(
99+
self.syntax_set.find_syntax_by_first_line(
100100
std::str::from_utf8(contents.data())
101101
.ok()?
102102
.split('\n')
103103
.next()?,
104-
);
104+
)
105105
}
106106
}
107107

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

118-
impl<'h> HighlighterState for SyntectHighlighterState<'h> {
118+
impl HighlighterState for SyntectHighlighterState<'_> {
119119
fn highlight_line<'s>(&mut self, line: &'s str) -> Vec<Styled<&'s str>> {
120120
if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) {
121121
let use_bg_color = self.use_bg_color;

src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
//! - [... handler options](#-handler-options)
5252
//! - [... dynamic diagnostics](#-dynamic-diagnostics)
5353
//! - [... syntax highlighting](#-syntax-highlighting)
54+
//! - [... primary label](#-primary-label)
5455
//! - [... collection of labels](#-collection-of-labels)
5556
//! - [Acknowledgements](#acknowledgements)
5657
//! - [License](#license)
@@ -690,6 +691,37 @@
690691
//! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
691692
//! method. See the [`highlighters`] module docs for more details.
692693
//!
694+
//! ### ... primary label
695+
//!
696+
//! You can use the `primary` parameter to `label` to indicate that the label
697+
//! is the primary label.
698+
//!
699+
//! ```rust,ignore
700+
//! #[derive(Debug, Diagnostic, Error)]
701+
//! #[error("oops!")]
702+
//! struct MyError {
703+
//! #[label(primary, "main issue")]
704+
//! primary_span: SourceSpan,
705+
//!
706+
//! #[label("other label")]
707+
//! other_span: SourceSpan,
708+
//! }
709+
//! ```
710+
//!
711+
//! The `primary` parameter can be used at most once:
712+
//!
713+
//! ```rust,ignore
714+
//! #[derive(Debug, Diagnostic, Error)]
715+
//! #[error("oops!")]
716+
//! struct MyError {
717+
//! #[label(primary, "main issue")]
718+
//! primary_span: SourceSpan,
719+
//!
720+
//! #[label(primary, "other label")] // Error: Cannot have more than one primary label.
721+
//! other_span: SourceSpan,
722+
//! }
723+
//! ```
724+
//!
693725
//! ### ... collection of labels
694726
//!
695727
//! When the number of labels is unknown, you can use a collection of `SourceSpan`

src/panic.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use std::{error::Error, fmt::Display};
2+
13
use backtrace::Backtrace;
2-
use thiserror::Error;
34

4-
use crate::{self as miette, Context, Diagnostic, Result};
5+
use crate::{Context, Diagnostic, Result};
56

67
/// Tells miette to render panics using its rendering engine.
78
pub fn set_panic_hook() {
@@ -25,11 +26,27 @@ pub fn set_panic_hook() {
2526
}));
2627
}
2728

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

32+
impl Display for Panic {
33+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34+
let msg = &self.0;
35+
let panic = Panic::backtrace();
36+
write!(f, "{msg}{panic}")
37+
}
38+
}
39+
40+
impl Error for Panic {}
41+
42+
impl Diagnostic for Panic {
43+
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
44+
Some(Box::new(
45+
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace.",
46+
))
47+
}
48+
}
49+
3350
impl Panic {
3451
fn backtrace() -> String {
3552
use std::fmt::Write;
@@ -84,3 +101,32 @@ impl Panic {
84101
"".into()
85102
}
86103
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use std::error::Error;
108+
109+
use super::*;
110+
111+
#[test]
112+
fn panic() {
113+
let panic = Panic("ruh roh raggy".to_owned());
114+
115+
assert_eq!(panic.to_string(), "ruh roh raggy");
116+
assert!(panic.source().is_none());
117+
assert!(panic.code().is_none());
118+
assert!(panic.severity().is_none());
119+
assert_eq!(
120+
panic.help().map(|h| h.to_string()),
121+
Some(
122+
"set the `RUST_BACKTRACE=1` environment variable to display a backtrace."
123+
.to_owned()
124+
)
125+
);
126+
assert!(panic.url().is_none());
127+
assert!(panic.source_code().is_none());
128+
assert!(panic.labels().is_none());
129+
assert!(panic.related().is_none());
130+
assert!(panic.diagnostic_source().is_none());
131+
}
132+
}

src/protocol.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{
1212
#[cfg(feature = "serde")]
1313
use serde::{Deserialize, Serialize};
1414

15-
use crate::MietteError;
15+
use crate::{DiagnosticError, MietteError};
1616

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

175175
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
176176
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
177-
#[derive(thiserror::Error)]
178-
#[error(transparent)]
179-
struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
180-
impl fmt::Debug for BoxedDiagnostic {
181-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182-
fmt::Debug::fmt(&self.0, f)
183-
}
184-
}
185-
186-
impl Diagnostic for BoxedDiagnostic {}
187-
188-
Box::new(BoxedDiagnostic(s))
177+
Box::new(DiagnosticError(s))
189178
}
190179
}
191180

0 commit comments

Comments
 (0)