This commit is contained in:
François Garillot 2026-06-03 13:58:11 +09:00 committed by GitHub
commit f834b05a26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 562 additions and 258 deletions

View File

@ -68,6 +68,21 @@ jobs:
- name: Check wasm target
run: cargo check --target wasm32-unknown-unknown --features fancy-no-syscall
no-std:
name: Check no-std build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: wasm32-unknown-unknown
- name: Check no-std core build
run: cargo check --target wasm32-unknown-unknown --no-default-features
- name: Check no-std with fancy-no-syscall
run: cargo check --target wasm32-unknown-unknown --no-default-features --features fancy-no-syscall
miri:
name: Miri
runs-on: ubuntu-latest
@ -84,6 +99,20 @@ jobs:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
run: cargo miri test --all --verbose --features fancy
feature-check:
name: Feature combinations check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Check feature powerset
run: cargo hack check --feature-powerset --no-dev-deps --skip default
minimal_versions:
name: Minimal versions check
runs-on: ${{ matrix.os }}

View File

@ -16,6 +16,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true }
unicode-width = "0.2.0"
cfg-if = "1.0.0"
spin = { version = "0.9", default-features = false, features = ["once"] }
owo-colors = { version = "4.0.0", optional = true }
textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true }
@ -31,21 +32,20 @@ syntect = { version = "5.1.0", optional = true }
[dev-dependencies]
thiserror = "2.0.11"
semver = "1.0.21"
# Eyre devdeps
futures = { version = "0.3", default-features = false }
indenter = "0.3.3"
rustversion = "1.0"
trybuild = { version = "1.0.89", features = ["diff"] }
syn = { version = "2.0.87", features = ["full"] }
regex = "1.10"
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113"
strip-ansi-escapes = "0.2.0"
[features]
default = ["derive"]
default = ["derive", "std"]
std = []
derive = ["dep:miette-derive"]
no-format-args-capture = []
fancy-base = [
@ -57,6 +57,7 @@ fancy-no-syscall = [
]
fancy-no-backtrace = [
"fancy-base",
"std",
"dep:terminal_size",
"dep:supports-hyperlinks",
"dep:supports-color",

View File

@ -58,8 +58,8 @@ diagnostic error code: ruget::api::bad_json
### Features
- Generic [`Diagnostic`] protocol, compatible (and dependent on)
[`std::error::Error`].
- Generic [`Diagnostic`] protocol, compatible with `std::error::Error`.
Works without the standard library: Just turn off the default `std` feature and you can use `miette` in places like embedded systems or web browsers that don't have the full standard library. You still need `alloc` for memory management.
- Unique error codes on every [`Diagnostic`].
- Custom links to get more details on error codes.
- Super handy derive macro for defining diagnostic metadata.
@ -93,6 +93,19 @@ If you want to use the fancy printer in all these screenshots:
$ cargo add miette --features fancy
```
For computers without the standard library (like microcontrollers or web browsers):
```sh
$ cargo add miette --no-default-features --features derive
```
Available features you can turn on or off:
- `std` (on by default): Use the standard library
- `derive`: Lets you automatically create error types
- `fancy`: Shows pretty error messages with colors
- `fancy-no-syscall`: Pretty errors without using system calls
- `fancy-no-backtrace`: Pretty errors without showing the call stack
### Example
```rust

View File

@ -2,6 +2,8 @@
//! so the decoding source will be annotated with the decoding error,
//! providing contextual information about the error.
extern crate alloc;
use miette::{IntoDiagnostic, SourceOffset};
use serde_json::{self, json};

View File

@ -56,13 +56,13 @@ impl Code {
let code = &code.as_ref()?.0;
Some(match fields {
syn::Fields::Named(_) => {
quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
quote! { Self::#ident { .. } => core::option::Option::Some(miette::__alloc::Box::new(#code)), }
}
syn::Fields::Unnamed(_) => {
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
quote! { Self::#ident(..) => core::option::Option::Some(miette::__alloc::Box::new(#code)), }
}
syn::Fields::Unit => {
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), }
quote! { Self::#ident => core::option::Option::Some(miette::__alloc::Box::new(#code)), }
}
})
},
@ -72,8 +72,8 @@ impl Code {
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
let code = &self.0;
Some(quote! {
fn code(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
std::option::Option::Some(std::boxed::Box::new(#code))
fn code(&self) -> core::option::Option<miette::__alloc::Box<dyn core::fmt::Display + '_>> {
core::option::Option::Some(miette::__alloc::Box::new(#code))
}
})
}

View File

@ -59,7 +59,7 @@ impl DiagnosticSource {
};
quote! {
Self::#ident #display_pat => {
std::option::Option::Some(std::borrow::Borrow::borrow(#rel))
core::option::Option::Some(miette::__alloc::borrow::Borrow::borrow(#rel))
}
}
})
@ -70,8 +70,8 @@ impl DiagnosticSource {
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
let rel = &self.0;
Some(quote! {
fn diagnostic_source<'a>(&'a self) -> std::option::Option<&'a dyn miette::Diagnostic> {
std::option::Option::Some(std::borrow::Borrow::borrow(&self.#rel))
fn diagnostic_source<'a>(&'a self) -> core::option::Option<&'a dyn miette::Diagnostic> {
core::option::Option::Some(miette::__alloc::borrow::Borrow::borrow(&self.#rel))
}
})
}

View File

@ -58,34 +58,34 @@ impl WhichFn {
pub fn signature(&self) -> TokenStream {
match self {
Self::Code => quote! {
fn code(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
fn code(& self) -> Option<miette::__alloc::Box<dyn core::fmt::Display + '_>>
},
Self::Help => quote! {
fn help(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
fn help(& self) -> Option<miette::__alloc::Box<dyn core::fmt::Display + '_>>
},
Self::Url => quote! {
fn url(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
fn url(& self) -> Option<miette::__alloc::Box<dyn core::fmt::Display + '_>>
},
Self::Severity => quote! {
fn severity(&self) -> std::option::Option<miette::Severity>
fn severity(&self) -> Option<miette::Severity>
},
Self::Related => quote! {
fn related(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &dyn miette::Diagnostic> + '_>>
fn related(&self) -> Option<miette::__alloc::Box<dyn Iterator<Item = &dyn miette::Diagnostic> + '_>>
},
Self::Labels => quote! {
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
fn labels(&self) -> Option<miette::__alloc::Box<dyn Iterator<Item = miette::LabeledSpan> + '_>>
},
Self::SourceCode => quote! {
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
fn source_code(&self) -> Option<&dyn miette::SourceCode>
},
Self::DiagnosticSource => quote! {
fn diagnostic_source(&self) -> std::option::Option<&dyn miette::Diagnostic>
fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic>
},
}
}
pub fn catchall_arm(&self) -> TokenStream {
quote! { _ => std::option::Option::None }
quote! { _ => Option::None }
}
}

View File

@ -94,7 +94,7 @@ impl Help {
Help::Display(display) => {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
Some(quote! {
Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
Self::#ident #display_pat => Option::Some(miette::__alloc::Box::new(format!(#fmt #args))),
})
}
Help::Field(member, ty) => {
@ -108,7 +108,7 @@ impl Help {
Some(quote! {
Self::#ident #display_pat => {
use miette::macro_helpers::ToOption;
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> std::boxed::Box<dyn std::fmt::Display + '_> { std::boxed::Box::new(format!("{}", #var)) })
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> miette::__alloc::Box<dyn core::fmt::Display + '_> { miette::__alloc::Box::new(format!("{}", #var)) })
},
})
}
@ -123,21 +123,21 @@ impl Help {
Help::Display(display) => {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
Some(quote! {
fn help(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
fn help(&self) -> Option<miette::__alloc::Box<dyn core::fmt::Display + '_>> {
#[allow(unused_variables, deprecated)]
let Self #display_pat = self;
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
Option::Some(miette::__alloc::Box::new(format!(#fmt #args)))
}
})
}
Help::Field(member, ty) => {
let var = quote! { __miette_internal_var };
Some(quote! {
fn help(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
fn help(&self) -> Option<miette::__alloc::Box<dyn core::fmt::Display + '_>> {
#[allow(unused_variables, deprecated)]
let Self #display_pat = self;
use miette::macro_helpers::ToOption;
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> std::boxed::Box<dyn std::fmt::Display + '_> { std::boxed::Box::new(format!("{}", #var)) })
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> miette::__alloc::Box<dyn core::fmt::Display + '_> { miette::__alloc::Box::new(format!("{}", #var)) })
}
})
}

View File

@ -175,9 +175,9 @@ impl Labels {
let var = quote! { __miette_internal_var };
let display = if let Some(display) = label {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
quote! { std::option::Option::Some(format!(#fmt #args)) }
quote! { Option::Some(format!(#fmt #args)) }
} else {
quote! { std::option::Option::None }
quote! { Option::None }
};
let ctor = if *lbl_ty == LabelType::Primary {
quote! { miette::LabeledSpan::new_primary_with_span }
@ -205,9 +205,9 @@ impl Labels {
}
let display = if let Some(display) = label {
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
quote! { std::option::Option::Some(format!(#fmt #args)) }
quote! { Option::Some(format!(#fmt #args)) }
} else {
quote! { std::option::Option::None }
quote! { Option::None }
};
Some(quote! {
.chain({
@ -226,7 +226,7 @@ impl Labels {
Some(quote! {
#[allow(unused_variables)]
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
fn labels(&self) -> Option<miette::__alloc::Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
use miette::macro_helpers::ToOption;
let Self #display_pat = self;
@ -236,7 +236,7 @@ impl Labels {
.into_iter()
#(#collections_chain)*;
std::option::Option::Some(Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
Option::Some(miette::__alloc::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
}
})
}
@ -322,7 +322,7 @@ impl Labels {
]
.into_iter()
#(#collections_chain)*;
std::option::Option::Some(std::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
Option::Some(miette::__alloc::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap)))
}
}),
}

View File

@ -55,7 +55,7 @@ impl Related {
};
quote! {
Self::#ident #display_pat => {
std::option::Option::Some(std::boxed::Box::new(
Option::Some(miette::__alloc::Box::new(
#rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x })
))
}
@ -68,9 +68,9 @@ impl Related {
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
let rel = &self.0;
Some(quote! {
fn related<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
use ::core::borrow::Borrow;
std::option::Option::Some(std::boxed::Box::new(
fn related<'a>(&'a self) -> Option<miette::__alloc::Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
use miette::__alloc::borrow::Borrow;
Option::Some(miette::__alloc::Box::new(
self.#rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x.borrow() })
))
}

View File

@ -71,9 +71,7 @@ impl Severity {
syn::Fields::Unnamed(_) => quote! { (..) },
syn::Fields::Unit => quote! {},
};
Some(
quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), },
)
Some(quote! { Self::#ident #fields => Option::Some(miette::Severity::#severity), })
},
)
}

View File

@ -74,7 +74,7 @@ impl SourceCode {
Some(quote! {
#[allow(unused_variables)]
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
let Self #display_pat = self;
#ret
}
@ -101,7 +101,7 @@ impl SourceCode {
}
} else {
quote! {
std::option::Option::Some(#field)
Option::Some(#field)
}
};
match &fields {

View File

@ -96,7 +96,7 @@ impl Url {
}
};
Some(quote! {
Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
Self::#ident #pat => Option::Some(miette::__alloc::Box::new(format!(#fmt #args))),
})
},
)
@ -129,10 +129,10 @@ impl Url {
}
};
Some(quote! {
fn url(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
fn url(&self) -> Option<miette::__alloc::Box<dyn core::fmt::Display + '_>> {
#[allow(unused_variables, deprecated)]
let Self #pat = self;
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
Option::Some(miette::__alloc::Box::new(format!(#fmt #args)))
}
})
}

View File

@ -3,8 +3,10 @@ Iterate over error `.source()` chains.
NOTE: This module is taken wholesale from <https://crates.io/crates/eyre>.
*/
use std::error::Error as StdError;
use std::vec;
extern crate alloc;
use crate::StdError;
use alloc::vec::{self, Vec};
use ChainState::*;

View File

@ -18,7 +18,7 @@ impl<'a> DiagnosticChain<'a> {
}
}
pub(crate) fn from_stderror(head: &'a (dyn std::error::Error + 'static)) -> Self {
pub(crate) fn from_stderror(head: &'a (dyn crate::StdError + 'static)) -> Self {
DiagnosticChain {
state: Some(ErrorKind::StdError(head)),
}
@ -59,7 +59,7 @@ impl ExactSizeIterator for DiagnosticChain<'_> {
#[derive(Clone)]
pub(crate) enum ErrorKind<'a> {
Diagnostic(&'a dyn Diagnostic),
StdError(&'a (dyn std::error::Error + 'static)),
StdError(&'a (dyn crate::StdError + 'static)),
}
impl<'a> ErrorKind<'a> {
@ -74,8 +74,8 @@ impl<'a> ErrorKind<'a> {
}
}
impl std::fmt::Debug for ErrorKind<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Debug for ErrorKind<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ErrorKind::Diagnostic(d) => d.fmt(f),
ErrorKind::StdError(e) => e.fmt(f),
@ -83,8 +83,8 @@ impl std::fmt::Debug for ErrorKind<'_> {
}
}
impl std::fmt::Display for ErrorKind<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Display for ErrorKind<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ErrorKind::Diagnostic(d) => d.fmt(f),
ErrorKind::StdError(e) => e.fmt(f),

View File

@ -1,11 +1,16 @@
/*!
Default trait implementations for [`Diagnostic`].
*/
extern crate alloc;
use std::{convert::Infallible, fmt::Display};
use alloc::boxed::Box;
use core::{convert::Infallible, fmt::Display};
use crate::{Diagnostic, LabeledSpan, Severity, SourceCode};
// Note: Infallible implements core::error::Error since Rust 1.81,
// so we don't need to provide our own StdError impl.
impl Diagnostic for Infallible {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
match *self {}

View File

@ -1,8 +1,10 @@
use std::{
error::Error,
fmt::{self, Display},
io,
};
extern crate alloc;
use alloc::boxed::Box;
use core::error::Error;
use core::fmt::{self, Display};
#[cfg(feature = "std")]
use std::io;
use crate::Diagnostic;
@ -11,8 +13,9 @@ Error enum for miette. Used by certain operations in the protocol.
*/
#[derive(Debug)]
pub enum MietteError {
/// Wrapper around [`std::io::Error`]. This is returned when something went
/// Wrapper around [`io::Error`]. This is returned when something went
/// wrong while reading a [`SourceCode`](crate::SourceCode).
#[cfg(feature = "std")]
IoError(io::Error),
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
@ -23,6 +26,7 @@ pub enum MietteError {
impl Display for MietteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "std")]
MietteError::IoError(error) => write!(f, "{error}"),
MietteError::OutOfBounds => {
write!(f, "The given offset is outside the bounds of its Source")
@ -34,12 +38,14 @@ impl Display for MietteError {
impl Error for MietteError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
#[cfg(feature = "std")]
MietteError::IoError(error) => error.source(),
MietteError::OutOfBounds => None,
}
}
}
#[cfg(feature = "std")]
impl From<io::Error> for MietteError {
fn from(value: io::Error) -> Self {
Self::IoError(value)
@ -49,6 +55,7 @@ impl From<io::Error> for MietteError {
impl Diagnostic for MietteError {
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
match self {
#[cfg(feature = "std")]
MietteError::IoError(_) => Some(Box::new("miette::io_error")),
MietteError::OutOfBounds => Some(Box::new("miette::span_out_of_bounds")),
}
@ -56,6 +63,7 @@ impl Diagnostic for MietteError {
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
match self {
#[cfg(feature = "std")]
MietteError::IoError(_) => None,
MietteError::OutOfBounds => Some(Box::new(
"Double-check your spans. Do you have an off-by-one error?",
@ -66,24 +74,32 @@ impl Diagnostic for MietteError {
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
let crate_version = env!("CARGO_PKG_VERSION");
let variant = match self {
#[cfg(feature = "std")]
MietteError::IoError(_) => "#variant.IoError",
MietteError::OutOfBounds => "#variant.OutOfBounds",
};
Some(Box::new(format!(
Some(Box::new(alloc::format!(
"https://docs.rs/miette/{}/miette/enum.MietteError.html{}",
crate_version, variant,
crate_version,
variant,
)))
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::{error::Error, io::ErrorKind};
#[cfg(not(feature = "std"))]
use crate::StdError as Error;
use alloc::string::ToString;
use super::*;
#[derive(Debug)]
#[cfg(feature = "std")]
pub(crate) struct TestError(pub(crate) io::Error);
#[cfg(not(feature = "std"))]
#[derive(Debug)]
pub(crate) struct TestError(pub(crate) &'static str);
impl Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -91,17 +107,26 @@ pub(crate) mod tests {
}
}
#[cfg(feature = "std")]
impl Error for TestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.0)
}
}
#[cfg(not(feature = "std"))]
impl Error for TestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
#[cfg(feature = "std")]
#[test]
fn io_error() {
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
let inner_error = io::Error::other("halt and catch fire");
let outer_error = TestError(inner_error);
let io_error = io::Error::new(ErrorKind::Other, outer_error);
let io_error = io::Error::other(outer_error);
let miette_error = MietteError::from(io_error);

View File

@ -1,8 +1,12 @@
extern crate alloc;
use super::error::{ContextError, ErrorImpl};
use super::{Report, WrapErr};
use core::convert::Infallible;
use core::fmt::{self, Debug, Display, Write};
use std::error::Error as StdError;
use crate::StdError;
use alloc::boxed::Box;
use crate::{Diagnostic, LabeledSpan};
@ -10,7 +14,7 @@ mod ext {
use super::*;
pub trait Diag {
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
fn ext_report<D>(self, msg: D) -> Report
where
D: Display + Send + Sync + 'static;
@ -20,6 +24,7 @@ mod ext {
where
E: Diagnostic + Send + Sync + 'static,
{
#[track_caller]
fn ext_report<D>(self, msg: D) -> Report
where
D: Display + Send + Sync + 'static,
@ -29,6 +34,7 @@ mod ext {
}
impl Diag for Report {
#[track_caller]
fn ext_report<D>(self, msg: D) -> Report
where
D: Display + Send + Sync + 'static,
@ -38,7 +44,8 @@ mod ext {
}
}
impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
impl<T> WrapErr<T, Infallible> for Option<T> {
#[track_caller]
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -49,6 +56,7 @@ impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
}
}
#[track_caller]
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -60,6 +68,7 @@ impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
}
}
#[track_caller]
fn context<D>(self, msg: D) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -67,6 +76,7 @@ impl<T> WrapErr<T, std::convert::Infallible> for Option<T> {
self.wrap_err(msg)
}
#[track_caller]
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -80,6 +90,7 @@ impl<T, E> WrapErr<T, E> for Result<T, E>
where
E: ext::Diag + Send + Sync + 'static,
{
#[track_caller]
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -90,6 +101,7 @@ where
}
}
#[track_caller]
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -101,6 +113,7 @@ where
}
}
#[track_caller]
fn context<D>(self, msg: D) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -108,6 +121,7 @@ where
self.wrap_err(msg)
}
#[track_caller]
fn with_context<D, F>(self, msg: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,

View File

@ -1,8 +1,11 @@
extern crate alloc;
use crate::StdError;
use alloc::boxed::Box;
use core::any::TypeId;
use core::fmt::{self, Debug, Display};
use core::mem::ManuallyDrop;
use core::ptr::{self, NonNull};
use std::error::Error as StdError;
use super::ptr::{Mut, Own, Ref};
use super::Report;
@ -20,7 +23,7 @@ impl Report {
///
/// If the error type does not provide a backtrace, a backtrace will be
/// created here to ensure that a backtrace exists.
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub fn new<E>(error: E) -> Self
where
@ -66,7 +69,7 @@ impl Report {
/// .await
/// }
/// ```
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub fn msg<M>(message: M) -> Self
where
@ -82,12 +85,11 @@ impl Report {
///
/// Boxed `Diagnostic`s don't implement `Diagnostic` themselves due to trait coherence issues.
/// This method allows you to create a `Report` from a boxed `Diagnostic`.
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
pub fn new_boxed(error: Box<dyn Diagnostic + Send + Sync + 'static>) -> Self {
Report::from_boxed(error)
}
#[cfg_attr(track_caller, track_caller)]
#[cold]
pub(crate) fn from_std<E>(error: E) -> Self
where
@ -109,7 +111,7 @@ impl Report {
unsafe { Report::construct(error, vtable, handler) }
}
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub(crate) fn from_adhoc<M>(message: M) -> Self
where
@ -134,7 +136,7 @@ impl Report {
unsafe { Report::construct(error, vtable, handler) }
}
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub(crate) fn from_msg<D, E>(msg: D, error: E) -> Self
where
@ -154,12 +156,12 @@ impl Report {
};
// Safety: passing vtable that operates on the right type.
let handler = Some(super::capture_handler(&error));
let handler = Some(super::capture_handler_with_location(&error));
unsafe { Report::construct(error, vtable, handler) }
}
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub(crate) fn from_boxed(error: Box<dyn Diagnostic + Send + Sync>) -> Self {
use super::wrapper::BoxedError;
@ -429,7 +431,7 @@ impl Report {
/// Construct a [`Report`] directly from an error-like type
pub fn from_err<E>(err: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
E: StdError + Send + Sync + 'static,
{
super::DiagnosticError(Box::new(err)).into()
}
@ -439,7 +441,6 @@ impl<E> From<E> for Report
where
E: Diagnostic + Send + Sync + 'static,
{
#[cfg_attr(track_caller, track_caller)]
#[cold]
fn from(error: E) -> Self {
Report::from_std(error)
@ -816,7 +817,7 @@ impl AsRef<dyn StdError> for Report {
}
}
impl std::borrow::Borrow<dyn Diagnostic> for Report {
impl core::borrow::Borrow<dyn Diagnostic> for Report {
fn borrow(&self) -> &(dyn Diagnostic + 'static) {
self.as_ref()
}

View File

@ -1,14 +1,17 @@
use std::{error::Error, fmt::Display};
extern crate alloc;
use alloc::boxed::Box;
use core::error::Error;
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)]
pub(crate) struct DiagnosticError(pub(crate) Box<dyn std::error::Error + Send + Sync + 'static>);
pub(crate) struct DiagnosticError(pub(crate) Box<dyn Error + Send + Sync + 'static>);
impl Display for DiagnosticError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Display for DiagnosticError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let msg = &self.0;
write!(f, "{msg}")
}
@ -35,10 +38,12 @@ inaccessible. If you have a type implementing [`Diagnostic`] consider simply ret
pub trait IntoDiagnostic<T, E> {
/// Converts [`Result`] types that return regular [`std::error::Error`]s
/// into a [`Result`] that returns a [`Diagnostic`].
#[track_caller]
fn into_diagnostic(self) -> Result<T, Report>;
}
impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for Result<T, E> {
impl<T, E: Error + Send + Sync + 'static> IntoDiagnostic<T, E> for Result<T, E> {
#[track_caller]
fn into_diagnostic(self) -> Result<T, Report> {
self.map_err(|e| DiagnosticError(Box::new(e)).into())
}
@ -46,15 +51,21 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
#[cfg(test)]
mod tests {
use std::io::{self, ErrorKind};
extern crate alloc;
use super::*;
use alloc::string::ToString;
use super::IntoDiagnostic;
#[cfg(feature = "std")]
use std::io;
#[cfg(feature = "std")]
use crate::error::tests::TestError;
#[cfg(feature = "std")]
#[test]
fn diagnostic_error() {
let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire");
let inner_error = io::Error::other("halt and catch fire");
let outer_error: Result<(), _> = Err(TestError(inner_error));
let diagnostic_error = outer_error.into_diagnostic().unwrap_err();

View File

@ -45,9 +45,12 @@
// let error = $msg;
// (&error).miette_kind().new(error)
use super::Report;
extern crate alloc;
use alloc::boxed::Box;
use core::fmt::{Debug, Display};
use super::Report;
use crate::Diagnostic;
pub struct Adhoc;
@ -62,7 +65,7 @@ pub trait AdhocKind: Sized {
impl<T> AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {}
impl Adhoc {
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub fn new<M>(self, message: M) -> Report
where
@ -84,7 +87,7 @@ pub trait TraitKind: Sized {
impl<E> TraitKind for E where E: Into<Report> {}
impl Trait {
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub fn new<E>(self, error: E) -> Report
where
@ -106,7 +109,7 @@ pub trait BoxedKind: Sized {
impl BoxedKind for Box<dyn Diagnostic + Send + Sync> {}
impl Boxed {
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub fn new(self, error: Box<dyn Diagnostic + Send + Sync>) -> Report {
Report::from_boxed(error)

View File

@ -4,9 +4,13 @@
clippy::new_ret_no_self,
clippy::wrong_self_convention
)]
extern crate alloc;
use alloc::boxed::Box;
use core::fmt::Display;
use std::error::Error as StdError;
use core::error::Error as StdError;
#[cfg(feature = "std")]
use std::sync::OnceLock;
#[allow(unreachable_pub)]
@ -61,8 +65,16 @@ unsafe impl Send for Report {}
pub type ErrorHook =
Box<dyn Fn(&(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> + Sync + Send + 'static>;
#[cfg(feature = "std")]
static HOOK: OnceLock<ErrorHook> = OnceLock::new();
#[cfg(not(feature = "std"))]
static HOOK: spin::Once<ErrorHook> = spin::Once::new();
fn default_hook() -> ErrorHook {
Box::new(get_default_printer)
}
/// Error indicating that [`set_hook()`] was unable to install the provided
/// [`ErrorHook`].
#[derive(Debug)]
@ -77,28 +89,51 @@ impl core::fmt::Display for InstallError {
impl StdError for InstallError {}
impl Diagnostic for InstallError {}
/**
Set the error hook.
*/
/// Set the error hook.
#[cfg(feature = "std")]
pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {
HOOK.set(hook).map_err(|_| InstallError)
}
#[cfg_attr(track_caller, track_caller)]
#[cfg_attr(not(track_caller), allow(unused_mut))]
fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
let hook = HOOK.get_or_init(|| Box::new(get_default_printer)).as_ref();
#[cfg(track_caller)]
{
let mut handler = hook(error);
handler.track_caller(std::panic::Location::caller());
handler
/// Set the error hook.
#[cfg(not(feature = "std"))]
pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {
HOOK.call_once(|| hook);
Ok(())
}
#[cfg(not(track_caller))]
{
#[cfg(feature = "std")]
pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
let hook = HOOK.get_or_init(default_hook);
hook(error)
}
#[cfg(not(feature = "std"))]
pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler> {
let hook = HOOK.call_once(default_hook);
hook(error)
}
#[track_caller]
#[cfg(feature = "std")]
pub(crate) fn capture_handler_with_location(
error: &(dyn Diagnostic + 'static),
) -> Box<dyn ReportHandler> {
let hook = HOOK.get_or_init(default_hook);
let mut handler = hook(error);
handler.track_caller(core::panic::Location::caller());
handler
}
#[track_caller]
#[cfg(not(feature = "std"))]
pub(crate) fn capture_handler_with_location(
error: &(dyn Diagnostic + 'static),
) -> Box<dyn ReportHandler> {
let hook = HOOK.call_once(default_hook);
let mut handler = hook(error);
handler.track_caller(core::panic::Location::caller());
handler
}
fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box<dyn ReportHandler + 'static> {
@ -193,7 +228,7 @@ pub trait ReportHandler: core::any::Any + Send + Sync {
/// Store the location of the caller who constructed this error report
#[allow(unused_variables)]
fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {}
fn track_caller(&mut self, location: &'static core::panic::Location<'static>) {}
}
/// type alias for `Result<T, Report>`
@ -428,27 +463,27 @@ pub type Result<T, E = Report> = core::result::Result<T, E>;
/// ```
pub trait WrapErr<T, E>: context::private::Sealed {
/// Wrap the error value with a new adhoc error
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
where
D: Display + Send + Sync + 'static;
/// Wrap the error value with a new adhoc error that is evaluated lazily
/// only once an error does occur.
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
fn wrap_err_with<D, F>(self, f: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
/// Compatibility re-export of `wrap_err()` for interop with `anyhow`
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
fn context<D>(self, msg: D) -> Result<T, Report>
where
D: Display + Send + Sync + 'static;
/// Compatibility re-export of `wrap_err_with()` for interop with `anyhow`
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
fn with_context<D, F>(self, f: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
@ -470,7 +505,7 @@ pub mod private {
pub use super::super::kind::BoxedKind;
}
#[cfg_attr(track_caller, track_caller)]
#[track_caller]
#[cold]
pub fn new_adhoc<M>(message: M) -> Report
where

View File

@ -1,4 +1,8 @@
use std::{marker::PhantomData, ptr::NonNull};
extern crate alloc;
use alloc::boxed::Box;
use core::marker::PhantomData;
use core::ptr::NonNull;
#[repr(transparent)]
/// A raw pointer that owns its pointee

View File

@ -1,6 +1,9 @@
extern crate alloc;
use core::fmt::{self, Debug, Display};
use std::error::Error as StdError;
use crate::StdError;
use alloc::boxed::Box;
use crate::{Diagnostic, LabeledSpan, Report, SourceCode};
@ -107,16 +110,6 @@ impl StdError for BoxedError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.0.source()
}
fn description(&self) -> &str {
#[allow(deprecated)]
self.0.description()
}
fn cause(&self) -> Option<&dyn StdError> {
#[allow(deprecated)]
self.0.cause()
}
}
pub(crate) struct WithSourceCode<E, C> {
@ -216,8 +209,16 @@ impl<C> StdError for WithSourceCode<Report, C> {
}
}
#[cfg(test)]
#[cfg(all(test, feature = "std"))]
mod tests {
extern crate alloc;
use alloc::boxed::Box;
#[cfg(feature = "fancy")]
use alloc::format;
use alloc::string::{String, ToString};
#[cfg(feature = "fancy")]
use alloc::{vec, vec::Vec};
use thiserror::Error;
use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan};
@ -231,7 +232,7 @@ mod tests {
impl Diagnostic for Inner {
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
Some(Box::new(std::iter::once(LabeledSpan::underline(self.at))))
Some(Box::new(core::iter::once(LabeledSpan::underline(self.at))))
}
fn source_code(&self) -> Option<&dyn SourceCode> {

View File

@ -1,3 +1,7 @@
extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use crate::highlighters::Highlighter;
use crate::highlighters::MietteHighlighter;
use crate::protocol::Diagnostic;
@ -8,7 +12,7 @@ use crate::ReportHandler;
use crate::ThemeCharacters;
use crate::ThemeStyles;
use cfg_if::cfg_if;
use std::fmt;
use core::fmt;
/// Settings to control the color format used for graphical rendering.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
@ -341,17 +345,28 @@ impl MietteHandlerOpts {
}
}
#[allow(clippy::manual_unwrap_or)]
pub(crate) fn is_graphical(&self) -> bool {
if let Some(force_narrated) = self.force_narrated {
!force_narrated
} else if let Some(force_graphical) = self.force_graphical {
force_graphical
} else if let Ok(env) = std::env::var("NO_GRAPHICS") {
} else {
#[cfg(feature = "fancy-no-backtrace")]
{
if let Ok(env) = std::env::var("NO_GRAPHICS") {
env == "0"
} else {
true
}
}
#[cfg(not(feature = "fancy-no-backtrace"))]
{
// fancy-base or fancy-no-syscall without std: default to graphical
true
}
}
}
// Detects known terminal apps based on env variables and returns true if
// they support rendering links.
@ -464,60 +479,63 @@ impl From<HighlighterOption> for MietteHighlighter {
}
mod syscall {
use cfg_if::cfg_if;
#[inline]
pub(super) fn terminal_width() -> Option<usize> {
cfg_if! {
if #[cfg(any(feature = "fancy-no-syscall", miri))] {
None
} else {
#[cfg(all(feature = "fancy-no-backtrace", not(miri)))]
{
terminal_size::terminal_size().map(|size| size.0 .0 as usize)
}
#[cfg(any(not(feature = "fancy-no-backtrace"), miri))]
{
None
}
}
#[inline]
pub(super) fn supports_hyperlinks() -> bool {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
false
} else {
#[cfg(feature = "fancy-no-backtrace")]
{
supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr)
}
#[cfg(not(feature = "fancy-no-backtrace"))]
{
false
}
}
#[inline]
pub(super) fn supports_color() -> bool {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
false
} else {
#[cfg(feature = "fancy-no-backtrace")]
{
supports_color::on(supports_color::Stream::Stderr).is_some()
}
#[cfg(not(feature = "fancy-no-backtrace"))]
{
false
}
}
#[inline]
pub(super) fn supports_color_has_16m() -> Option<bool> {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
None
} else {
#[cfg(feature = "fancy-no-backtrace")]
{
supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m)
}
#[cfg(not(feature = "fancy-no-backtrace"))]
{
None
}
}
#[inline]
pub(super) fn supports_unicode() -> bool {
cfg_if! {
if #[cfg(feature = "fancy-no-syscall")] {
false
} else {
#[cfg(feature = "fancy-no-backtrace")]
{
supports_unicode::on(supports_unicode::Stream::Stderr)
}
#[cfg(not(feature = "fancy-no-backtrace"))]
{
false
}
}
}

View File

@ -1,4 +1,7 @@
use std::fmt;
extern crate alloc;
use alloc::vec::Vec;
use core::fmt;
use crate::{protocol::Diagnostic, ReportHandler};
@ -34,25 +37,25 @@ impl DebugReportHandler {
diagnostic: &dyn Diagnostic,
) -> fmt::Result {
let mut diag = f.debug_struct("Diagnostic");
diag.field("message", &format!("{}", diagnostic));
diag.field("message", &alloc::format!("{}", diagnostic));
if let Some(code) = diagnostic.code() {
diag.field("code", &code.to_string());
diag.field("code", &alloc::format!("{}", code));
}
if let Some(severity) = diagnostic.severity() {
diag.field("severity", &format!("{:?}", severity));
diag.field("severity", &alloc::format!("{:?}", severity));
}
if let Some(url) = diagnostic.url() {
diag.field("url", &url.to_string());
diag.field("url", &alloc::format!("{}", url));
}
if let Some(help) = diagnostic.help() {
diag.field("help", &help.to_string());
diag.field("help", &alloc::format!("{}", help));
}
if let Some(labels) = diagnostic.labels() {
let labels: Vec<_> = labels.collect();
diag.field("labels", &format!("{:?}", labels));
diag.field("labels", &alloc::format!("{:?}", labels));
}
if let Some(cause) = diagnostic.diagnostic_source() {
diag.field("caused by", &format!("{:?}", cause));
diag.field("caused by", &alloc::format!("{:?}", cause));
}
diag.finish()?;
writeln!(f)?;

View File

@ -1,4 +1,10 @@
use std::fmt::{self, Write};
extern crate alloc;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::{self, Write};
use owo_colors::{OwoColorize, Style, StyledList};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@ -582,7 +588,7 @@ impl GraphicalReportHandler {
// The snippets will overlap, so we create one Big Chunky Boi
let left_end = left.offset() + left.len();
let right_end = right.offset() + right.len();
let new_end = std::cmp::max(left_end, right_end);
let new_end = core::cmp::max(left_end, right_end);
let new_span = LabeledSpan::new(
left.label().map(String::from),
@ -649,7 +655,7 @@ impl GraphicalReportHandler {
num_highlights += 1;
}
}
max_gutter = std::cmp::max(max_gutter, num_highlights);
max_gutter = core::cmp::max(max_gutter, num_highlights);
}
// Oh and one more thing: We need to figure out how much room our line
@ -1173,7 +1179,7 @@ impl GraphicalReportHandler {
.style(hl.style)
.to_string(),
);
highest = std::cmp::max(highest, end);
highest = core::cmp::max(highest, end);
(hl, vbar_offset)
})

View File

@ -1,4 +1,7 @@
use std::fmt::{self, Write};
extern crate alloc;
use alloc::string::ToString;
use core::fmt::{self, Write};
use crate::{
diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode,

View File

@ -1,10 +1,15 @@
use std::fmt;
extern crate alloc;
use core::fmt;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::diagnostic_chain::DiagnosticChain;
use crate::protocol::{Diagnostic, Severity};
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
/**
[`ReportHandler`] that renders plain text and avoids extraneous graphics.
@ -290,7 +295,7 @@ impl NarratableReportHandler {
let context_data = source
.read_span(context_span, self.context_lines, self.context_lines)
.map_err(|_| fmt::Error)?;
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
let context = core::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
let mut line = context_data.line();
let mut column = context_data.column();
let mut offset = context_data.span().offset();

View File

@ -1,5 +1,7 @@
use std::io::IsTerminal;
extern crate alloc;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use owo_colors::Style;
/**
@ -69,6 +71,9 @@ impl GraphicalTheme {
impl Default for GraphicalTheme {
fn default() -> Self {
#[cfg(feature = "fancy-no-backtrace")]
{
use std::io::IsTerminal;
match std::env::var("NO_COLOR") {
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
Self::none()
@ -77,6 +82,11 @@ impl Default for GraphicalTheme {
_ => Self::unicode(),
}
}
#[cfg(not(feature = "fancy-no-backtrace"))]
{
Self::unicode_nocolor()
}
}
}
/**

View File

@ -1,3 +1,8 @@
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use owo_colors::Style;
use crate::SpanContents;

View File

@ -11,7 +11,12 @@
//! * `syntect-highlighter` - Enables [`syntect`](https://docs.rs/syntect/latest/syntect/) syntax highlighting support via the [`SyntectHighlighter`]
//!
use std::{ops::Deref, sync::Arc};
use core::ops::Deref;
extern crate alloc;
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::SpanContents;
use owo_colors::Styled;
@ -78,12 +83,12 @@ impl MietteHighlighter {
}
impl Default for MietteHighlighter {
#[cfg(feature = "syntect-highlighter")]
fn default() -> Self {
#[cfg(feature = "syntect-highlighter")]
{
use std::io::IsTerminal;
match std::env::var("NO_COLOR") {
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
//TODO: should use ANSI styling instead of 24-bit truecolor here
Self(Arc::new(SyntectHighlighter::default()))
}
Ok(string) if string != "0" => MietteHighlighter::nocolor(),
@ -91,10 +96,11 @@ impl Default for MietteHighlighter {
}
}
#[cfg(not(feature = "syntect-highlighter"))]
fn default() -> Self {
{
MietteHighlighter::nocolor()
}
}
}
impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
fn from(value: T) -> Self {
@ -102,8 +108,8 @@ impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
}
}
impl std::fmt::Debug for MietteHighlighter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Debug for MietteHighlighter {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "MietteHighlighter(...)")
}
}

View File

@ -1,5 +1,11 @@
use core::str;
use std::path::Path;
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
// all syntect imports are explicitly qualified, but their paths are shortened for convenience
#[allow(clippy::module_inception)]
mod syntect {
@ -97,7 +103,7 @@ impl SyntectHighlighter {
}
// finally, attempt to guess syntax based on first line
self.syntax_set.find_syntax_by_first_line(
std::str::from_utf8(contents.data())
core::str::from_utf8(contents.data())
.ok()?
.split('\n')
.next()?,

View File

@ -1,6 +1,13 @@
#![no_std]
#![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
#![warn(unreachable_pub, rust_2018_idioms)]
#![deny(
clippy::alloc_instead_of_core,
clippy::std_instead_of_core,
clippy::std_instead_of_alloc
)]
#![allow(unexpected_cfgs)]
//! You run miette? You run her code like the software? Oh. Oh! Error code for
//! coder! Error code for One Thousand Lines!
//!
@ -58,8 +65,8 @@
//!
//! ## Features
//!
//! - Generic [`Diagnostic`] protocol, compatible (and dependent on)
//! [`std::error::Error`].
//! - Generic [`Diagnostic`] protocol, compatible with `std::error::Error`.
//! Works without the standard library: Just turn off the default `std` feature and you can use `miette` in places like embedded systems or web browsers that don't have the full standard library. You still need `alloc` for memory management.
//! - Unique error codes on every [`Diagnostic`].
//! - Custom links to get more details on error codes.
//! - Super handy derive macro for defining diagnostic metadata.
@ -93,9 +100,23 @@
//! $ cargo add miette --features fancy
//! ```
//!
//! For computers without the standard library (like microcontrollers or web browsers):
//!
//! ```sh
//! $ cargo add miette --no-default-features --features derive
//! ```
//!
//! Available features you can turn on or off:
//! - `std` (on by default): Use the standard library
//! - `derive`: Lets you automatically create error types
//! - `fancy`: Shows pretty error messages with colors
//! - `fancy-no-syscall`: Pretty errors without using system calls
//! - `fancy-no-backtrace`: Pretty errors without showing the call stack
//!
//! ## Example
//!
//! ```rust
//! # extern crate alloc;
//! /*
//! You can derive a `Diagnostic` from any `std::error::Error` type.
//!
@ -193,6 +214,7 @@
//! the trait directly, just like with `std::error::Error`.
//!
//! ```rust
//! # extern crate alloc;
//! // lib/error.rs
//! use miette::{Diagnostic, SourceSpan};
//! use thiserror::Error;
@ -360,6 +382,7 @@
//! attribute:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::Diagnostic;
//! use thiserror::Error;
//!
@ -380,6 +403,7 @@
//! (very high quality and detailed!) documentation on this diagnostic:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::Diagnostic;
//! use thiserror::Error;
//!
@ -410,6 +434,7 @@
//! `derive(Diagnostic)` macro:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::{Diagnostic, SourceSpan};
//! use thiserror::Error;
//!
@ -448,6 +473,7 @@
//! enum variants:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::Diagnostic;
//! use thiserror::Error;
//!
@ -461,6 +487,7 @@
//! your diagnostic:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::Diagnostic;
//! use thiserror::Error;
//!
@ -499,6 +526,7 @@
//! `Diagnostic` type:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::Diagnostic;
//! use thiserror::Error;
//!
@ -517,6 +545,7 @@
//! method for that:
//!
//! ```rust,no_run
//! # extern crate alloc;
//! use miette::{Diagnostic, SourceSpan};
//! use thiserror::Error;
//!
@ -549,6 +578,7 @@
//! emitted at the same time:
//!
//! ```rust,no_run
//! # extern crate alloc;
//! use miette::{Diagnostic, Report, SourceSpan};
//! use thiserror::Error;
//!
@ -610,6 +640,7 @@
//! will likely want to use _both_:
//!
//! ```rust
//! # extern crate alloc;
//! use miette::Diagnostic;
//! use thiserror::Error;
//!
@ -815,6 +846,19 @@
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
//! under the Apache License. Some code is taken from
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
#[cfg(feature = "std")]
extern crate std;
pub use core::error::Error as StdError;
#[doc(hidden)]
pub mod __alloc {
extern crate alloc;
pub use alloc::borrow;
pub use alloc::boxed::Box;
}
#[cfg(feature = "derive")]
pub use miette_derive::*;

View File

@ -1,7 +1,10 @@
use std::{
error::Error,
fmt::{Debug, Display},
};
extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::error::Error;
use core::fmt::{Debug, Display};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -39,7 +42,7 @@ pub struct MietteDiagnostic {
}
impl Display for MietteDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", &self.message)
}
}
@ -183,7 +186,7 @@ impl MietteDiagnostic {
/// 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.labels = Some(Vec::from([label.into()]));
self
}
@ -261,6 +264,7 @@ impl MietteDiagnostic {
#[cfg(feature = "serde")]
#[test]
fn test_serialize_miette_diagnostic() {
use alloc::format;
use serde_json::json;
use crate::diagnostic;
@ -311,6 +315,7 @@ fn test_serialize_miette_diagnostic() {
#[cfg(feature = "serde")]
#[test]
fn test_deserialize_miette_diagnostic() {
use alloc::format;
use serde_json::json;
use crate::diagnostic;

View File

@ -10,8 +10,14 @@ pub struct NamedSource<S: SourceCode + 'static> {
language: Option<String>,
}
impl<S: SourceCode> std::fmt::Debug for NamedSource<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::string::ToString;
impl<S: SourceCode> core::fmt::Debug for NamedSource<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("NamedSource")
.field("name", &self.name)
.field("source", &"<redacted>")

View File

@ -1,10 +1,24 @@
use std::{error::Error, fmt::Display};
// `fancy` feature requires std, so std imports are fine here.
#![allow(
clippy::std_instead_of_core,
clippy::std_instead_of_alloc,
clippy::alloc_instead_of_core
)]
use std::boxed::Box;
use std::{
eprintln,
error::Error,
fmt::Display,
format,
string::{String, ToString},
};
use backtrace::Backtrace;
use crate::{Context, Diagnostic, Result};
/// Tells miette to render panics using its rendering engine.
/// Makes miette show pretty error messages when your program crashes.
pub fn set_panic_hook() {
std::panic::set_hook(Box::new(move |info| {
let mut message = "Something went wrong".to_string();
@ -104,7 +118,7 @@ impl Panic {
#[cfg(test)]
mod tests {
use std::error::Error;
use std::{borrow::ToOwned, error::Error};
use super::*;

View File

@ -3,21 +3,26 @@ This module defines the core of the miette protocol: a series of types and
traits that you can implement to get access to miette's (and related library's)
full reporting and such features.
*/
use std::{
fmt::{self, Display},
fs,
panic::Location,
};
extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt::{self, Display};
use core::ops;
#[cfg(feature = "std")]
use core::panic::Location;
#[cfg(feature = "std")]
use std::fs;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{DiagnosticError, MietteError};
use crate::MietteError;
/// Adds rich metadata to your Error that can be used by
/// [`Report`](crate::Report) to print really nice and human-friendly error
/// messages.
pub trait Diagnostic: std::error::Error {
pub trait Diagnostic: crate::StdError {
/// Unique diagnostic code that can be used 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
@ -72,14 +77,10 @@ pub trait Diagnostic: std::error::Error {
macro_rules! box_error_impls {
($($box_type:ty),*) => {
$(
impl std::error::Error for $box_type {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
impl crate::StdError for $box_type {
fn source(&self) -> Option<&(dyn crate::StdError + 'static)> {
(**self).source()
}
fn cause(&self) -> Option<&dyn std::error::Error> {
self.source()
}
}
)*
}
@ -94,7 +95,7 @@ box_error_impls! {
macro_rules! box_borrow_impls {
($($box_type:ty),*) => {
$(
impl std::borrow::Borrow<dyn Diagnostic> for $box_type {
impl core::borrow::Borrow<dyn Diagnostic> for $box_type {
fn borrow(&self) -> &(dyn Diagnostic + 'static) {
self.as_ref()
}
@ -152,7 +153,7 @@ impl From<String> for Box<dyn Diagnostic + Send + Sync> {
fn from(s: String) -> Self {
struct StringError(String);
impl std::error::Error for StringError {}
impl crate::StdError for StringError {}
impl Diagnostic for StringError {}
impl Display for StringError {
@ -172,8 +173,12 @@ 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 {
#[cfg(feature = "std")]
use crate::DiagnosticError;
#[cfg(feature = "std")]
impl From<Box<dyn core::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
fn from(s: Box<dyn core::error::Error + Send + Sync>) -> Self {
Box::new(DiagnosticError(s))
}
}
@ -369,6 +374,7 @@ impl LabeledSpan {
#[cfg(feature = "serde")]
#[test]
fn test_serialize_labeled_span() {
use alloc::string::ToString;
use serde_json::json;
assert_eq!(
@ -392,6 +398,7 @@ fn test_serialize_labeled_span() {
#[cfg(feature = "serde")]
#[test]
fn test_deserialize_labeled_span() {
use alloc::string::ToString;
use serde_json::json;
let span: LabeledSpan = serde_json::from_value(json!({
@ -594,8 +601,8 @@ impl From<(SourceOffset, usize)> for SourceSpan {
}
}
impl From<std::ops::Range<ByteOffset>> for SourceSpan {
fn from(range: std::ops::Range<ByteOffset>) -> Self {
impl From<ops::Range<ByteOffset>> for SourceSpan {
fn from(range: ops::Range<ByteOffset>) -> Self {
Self {
offset: range.start.into(),
length: range.len(),
@ -603,12 +610,12 @@ impl From<std::ops::Range<ByteOffset>> for SourceSpan {
}
}
impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
impl From<ops::RangeInclusive<ByteOffset>> for SourceSpan {
/// # Panics
///
/// Panics if the total length of the inclusive range would overflow a
/// `usize`. This will only occur with the range `0..=usize::MAX`.
fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
fn from(range: ops::RangeInclusive<ByteOffset>) -> Self {
let (start, end) = range.clone().into_inner();
Self {
offset: start.into(),
@ -715,6 +722,7 @@ impl SourceOffset {
/// you're shipping binaries for your application, you'll want to ignore
/// the Err case or otherwise report it.
#[track_caller]
#[cfg(feature = "std")]
pub fn from_current_location() -> Result<(String, Self), MietteError> {
let loc = Location::caller();
Ok((

View File

@ -1,7 +1,16 @@
/*!
Default trait implementations for [`SourceCode`].
*/
use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
extern crate alloc;
use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Debug;
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
@ -205,7 +214,7 @@ mod tests {
fn basic() -> Result<(), MietteError> {
let src = String::from("foo\n");
let contents = src.read_span(&(0, 4).into(), 0, 0)?;
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!("foo\n", core::str::from_utf8(contents.data()).unwrap());
assert_eq!(0, contents.line());
assert_eq!(0, contents.column());
Ok(())
@ -215,7 +224,7 @@ mod tests {
fn shifted() -> Result<(), MietteError> {
let src = String::from("foobar");
let contents = src.read_span(&(3, 3).into(), 1, 1)?;
assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
assert_eq!("foobar", core::str::from_utf8(contents.data()).unwrap());
assert_eq!(0, contents.line());
assert_eq!(0, contents.column());
Ok(())
@ -225,7 +234,7 @@ mod tests {
fn middle() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz\n");
let contents = src.read_span(&(4, 4).into(), 0, 0)?;
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!("bar\n", core::str::from_utf8(contents.data()).unwrap());
assert_eq!(1, contents.line());
assert_eq!(0, contents.column());
Ok(())
@ -235,7 +244,7 @@ mod tests {
fn middle_of_line() -> Result<(), MietteError> {
let src = String::from("foo\nbarbar\nbaz\n");
let contents = src.read_span(&(7, 4).into(), 0, 0)?;
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!("bar\n", core::str::from_utf8(contents.data()).unwrap());
assert_eq!(1, contents.line());
assert_eq!(3, contents.column());
Ok(())
@ -245,7 +254,7 @@ mod tests {
fn with_crlf() -> Result<(), MietteError> {
let src = String::from("foo\r\nbar\r\nbaz\r\n");
let contents = src.read_span(&(5, 5).into(), 0, 0)?;
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!("bar\r\n", core::str::from_utf8(contents.data()).unwrap());
assert_eq!(1, contents.line());
assert_eq!(0, contents.column());
Ok(())
@ -257,7 +266,7 @@ mod tests {
let contents = src.read_span(&(8, 3).into(), 1, 1)?;
assert_eq!(
"foo\nbar\nbaz\n",
std::str::from_utf8(contents.data()).unwrap()
core::str::from_utf8(contents.data()).unwrap()
);
assert_eq!(1, contents.line());
assert_eq!(0, contents.column());
@ -270,7 +279,7 @@ mod tests {
let contents = src.read_span(&(9, 11).into(), 1, 1)?;
assert_eq!(
"\nfoo\nbar\nbaz\n\n",
std::str::from_utf8(contents.data()).unwrap()
core::str::from_utf8(contents.data()).unwrap()
);
assert_eq!(2, contents.line());
assert_eq!(0, contents.column());
@ -285,7 +294,7 @@ mod tests {
let contents = src.read_span(&(2, 0).into(), 2, 2)?;
assert_eq!(
"one\ntwo\n\n",
std::str::from_utf8(contents.data()).unwrap()
core::str::from_utf8(contents.data()).unwrap()
);
assert_eq!(0, contents.line());
assert_eq!(0, contents.column());

View File

@ -83,7 +83,9 @@ fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
//
// Since environment variables are shared for the entire process, we need
// to ensure that only one test that modifies these env vars runs at a time.
let lock = COLOR_ENV_VARS.lock().unwrap();
let lock = COLOR_ENV_VARS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let guards = (
EnvVarGuard::new("NO_COLOR"),

View File

@ -10,5 +10,5 @@ pub fn bail_fmt() -> Result<()> {
}
pub fn bail_error() -> Result<()> {
bail!(io::Error::new(io::ErrorKind::Other, "oh no!"));
bail!(io::Error::other("oh no!"));
}

View File

@ -1,3 +1,5 @@
#![allow(unused_assignments)] // some fields unused when feature="fancy"
use miette::{Diagnostic, Report, Severity, SourceSpan};
use thiserror::Error;
@ -244,6 +246,7 @@ fn help_field() {
#[diagnostic()]
struct Foo<'a> {
#[help]
#[allow(unused_assignments)]
do_this: Option<&'a str>,
}
@ -293,6 +296,7 @@ fn test_snippet_named_struct() {
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
#[allow(unused_assignments)]
struct Foo<'a> {
#[source_code]
src: &'a str,
@ -401,6 +405,7 @@ const SNIPPET_TEXT: &str = "hello from miette";
help("help"),
severity(Warning)
)]
#[allow(unused_assignments)]
struct ForwardsTo {
#[source_code]
src: String,
@ -501,6 +506,7 @@ fn test_forward_struct_named() {
help("{help}"),
forward(span)
)]
#[allow(unused_assignments)]
struct Struct<'a> {
span: ForwardsTo,
help: &'a str,
@ -534,6 +540,7 @@ fn test_forward_enum_named() {
enum Enum<'a> {
#[error("help: {help_text}")]
#[diagnostic(code(foo::bar::overridden), help("{help_text}"), forward(span))]
#[allow(unused_assignments)]
Variant {
span: ForwardsTo,
help_text: &'a str,
@ -595,6 +602,7 @@ fn test_unit_enum_display() {
fn test_optional_source_code() {
#[derive(Debug, Diagnostic, Error)]
#[error("struct with optional source")]
#[allow(unused_assignments)]
struct Struct {
#[source_code]
src: Option<String>,
@ -607,6 +615,7 @@ fn test_optional_source_code() {
.is_some());
#[derive(Debug, Diagnostic, Error)]
#[allow(unused_assignments)]
enum Enum {
#[error("variant1 with optional source")]
Variant1 {

View File

@ -42,13 +42,13 @@ fn test_boxed_str_stderr() {
#[test]
fn test_boxed_thiserror() {
let error = MyError {
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
source: io::Error::other("oh no!"),
};
let report: Report = miette!(error);
assert_eq!("oh no!", report.source().unwrap().to_string());
let error = MyError {
source: io::Error::new(io::ErrorKind::Other, "oh no!!!!"),
source: io::Error::other("oh no!!!!"),
};
let error: Box<dyn Diagnostic + Send + Sync + 'static> = Box::new(error);
let report = Report::new_boxed(error);
@ -203,7 +203,7 @@ fn test_boxed_custom_diagnostic() {
let related = CustomDiagnostic::new();
let main_diagnostic = CustomDiagnostic::new()
.with_source(io::Error::new(io::ErrorKind::Other, "oh no!"))
.with_source(io::Error::other("oh no!"))
.with_related(related);
let report = Report::new_boxed(Box::new(main_diagnostic));
@ -211,7 +211,7 @@ fn test_boxed_custom_diagnostic() {
let related = CustomDiagnostic::new();
let main_diagnostic = CustomDiagnostic::new()
.with_source(io::Error::new(io::ErrorKind::Other, "oh no!"))
.with_source(io::Error::other("oh no!"))
.with_related(related);
let main_diagnostic = Box::new(main_diagnostic) as Box<dyn Diagnostic + Send + Sync + 'static>;
let report = miette!(main_diagnostic);
@ -228,7 +228,7 @@ fn test_boxed_custom_diagnostic() {
#[test]
fn test_boxed_sources() {
let error = MyError {
source: io::Error::new(io::ErrorKind::Other, "oh no!"),
source: io::Error::other("oh no!"),
};
let error = Box::<dyn Diagnostic + Send + Sync>::from(error);
let error: Report = miette!(error).wrap_err("it failed");

View File

@ -1,4 +1,5 @@
// Testing of the `diagnostic` attr used by derive(Diagnostic)
use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan};
use thiserror::Error;

View File

@ -50,7 +50,7 @@ fn test_fmt_source() {
#[test]
#[ignore = "Again with the io::Error source issue?"]
fn test_io_source() {
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
let io = io::Error::other("oh no!");
let error: Report = miette!(TestError::Io(io));
assert_eq!("oh no!", error.source().unwrap().to_string());
}