mirror of https://github.com/zkat/miette.git
feat(help): update macro to allow optional help text
Fixes: https://github.com/zkat/miette/issues/148
This commit is contained in:
parent
5e54b29acf
commit
cb10790d8e
|
|
@ -9,3 +9,9 @@ workspace=false
|
|||
install_crate="cargo-release"
|
||||
command = "cargo"
|
||||
args = ["release", "--workspace", "${@}"]
|
||||
|
||||
[tasks.readme]
|
||||
workspace=false
|
||||
install_crate="cargo-readme"
|
||||
command = "cargo"
|
||||
args = ["readme"]
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -372,6 +372,34 @@ pub struct MyErrorType {
|
|||
}
|
||||
```
|
||||
|
||||
#### ... help text
|
||||
|
||||
`miette` provides two facilities for supplying help text for your errors:
|
||||
|
||||
The first is the `#[help()]` format attribute that applies to structs or enum variants:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(help("try doing this instead"))]
|
||||
struct Foo;
|
||||
```
|
||||
|
||||
The other is by programmatically supplying the help text as a field to your
|
||||
diagnostic:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic()]
|
||||
struct Foo {
|
||||
#[help]
|
||||
advice: Option<String>
|
||||
}
|
||||
|
||||
let err = Foo { advice: Some("try doing this instead".to_string()) };
|
||||
```
|
||||
|
||||
#### ... multiple related errors
|
||||
|
||||
`miette` supports collecting multiple errors into a single diagnostic, and
|
||||
|
|
|
|||
|
|
@ -73,9 +73,10 @@ impl DiagnosticConcreteArgs {
|
|||
let labels = Labels::from_fields(fields)?;
|
||||
let source_code = SourceCode::from_fields(fields)?;
|
||||
let related = Related::from_fields(fields)?;
|
||||
let help = Help::from_fields(fields)?;
|
||||
Ok(DiagnosticConcreteArgs {
|
||||
code: None,
|
||||
help: None,
|
||||
help,
|
||||
related,
|
||||
severity: None,
|
||||
labels,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Fields, Token,
|
||||
};
|
||||
|
||||
|
|
@ -15,8 +16,9 @@ use crate::{
|
|||
forward::WhichFn,
|
||||
};
|
||||
|
||||
pub struct Help {
|
||||
pub display: Display,
|
||||
pub enum Help {
|
||||
Display(Display),
|
||||
Field(syn::Member),
|
||||
}
|
||||
|
||||
impl Parse for Help {
|
||||
|
|
@ -38,16 +40,14 @@ impl Parse for Help {
|
|||
args,
|
||||
has_bonus_display: false,
|
||||
};
|
||||
Ok(Help { display })
|
||||
Ok(Help::Display(display))
|
||||
} else {
|
||||
input.parse::<Token![=]>()?;
|
||||
Ok(Help {
|
||||
display: Display {
|
||||
fmt: input.parse()?,
|
||||
args: TokenStream::new(),
|
||||
has_bonus_display: false,
|
||||
},
|
||||
})
|
||||
Ok(Help::Display(Display {
|
||||
fmt: input.parse()?,
|
||||
args: TokenStream::new(),
|
||||
has_bonus_display: false,
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Err(syn::Error::new(ident.span(), "not a help"))
|
||||
|
|
@ -56,30 +56,83 @@ impl Parse for Help {
|
|||
}
|
||||
|
||||
impl Help {
|
||||
pub(crate) fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||
match fields {
|
||||
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||
syn::Fields::Unnamed(unnamed) => {
|
||||
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||
}
|
||||
syn::Fields::Unit => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
for attr in &field.attrs {
|
||||
if attr.path.is_ident("help") {
|
||||
let help = if let Some(ident) = field.ident.clone() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index {
|
||||
index: i as u32,
|
||||
span: field.span(),
|
||||
})
|
||||
};
|
||||
return Ok(Some(Help::Field(help)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||
gen_all_variants_with(
|
||||
variants,
|
||||
WhichFn::Help,
|
||||
|ident, fields, DiagnosticConcreteArgs { help, .. }| {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let display = &help.as_ref()?.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))),
|
||||
})
|
||||
match &help.as_ref()? {
|
||||
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))),
|
||||
})
|
||||
}
|
||||
Help::Field(member) => {
|
||||
let help = match &member {
|
||||
syn::Member::Named(ident) => ident.clone(),
|
||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||
format_ident!("_{}", index)
|
||||
}
|
||||
};
|
||||
Some(quote! {
|
||||
Self::#ident #display_pat => #help.as_ref().map(|h| -> std::boxed::Box<dyn std::fmt::Display + 'a> { std::boxed::Box::new(format!("{}", h)) }),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let (fmt, args) = self.display.expand_shorthand_cloned(&display_members);
|
||||
Some(quote! {
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
let Self #display_pat = self;
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
match self {
|
||||
Help::Display(display) => {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
Some(quote! {
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
let Self #display_pat = self;
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
Help::Field(member) => Some(quote! {
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
let Self #display_pat = self;
|
||||
self.#member.as_ref().map(|h| -> std::boxed::Box<dyn std::fmt::Display + 'a> { std::boxed::Box::new(format!("{}", h)) })
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ mod source_code;
|
|||
mod url;
|
||||
mod utils;
|
||||
|
||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic, source_code, label, related))]
|
||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic, source_code, label, related, help))]
|
||||
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let cmd = match Diagnostic::from_derive_input(input) {
|
||||
|
|
|
|||
36
src/lib.rs
36
src/lib.rs
|
|
@ -371,6 +371,42 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! #### ... help text
|
||||
//! `miette` provides two facilities for supplying help text for your errors:
|
||||
//
|
||||
//! The first is the `#[help()]` format attribute that applies to structs or
|
||||
//! enum variants:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use miette::Diagnostic;
|
||||
//! use thiserror::Error;
|
||||
//!
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("welp")]
|
||||
//! #[diagnostic(help("try doing this instead"))]
|
||||
//! struct Foo;
|
||||
//! ```
|
||||
//!
|
||||
//! The other is by programmatically supplying the help text as a field to your
|
||||
//! diagnostic:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use miette::Diagnostic;
|
||||
//! use thiserror::Error;
|
||||
//!
|
||||
//! #[derive(Debug, Diagnostic, Error)]
|
||||
//! #[error("welp")]
|
||||
//! #[diagnostic()]
|
||||
//! struct Foo {
|
||||
//! #[help]
|
||||
//! advice: Option<String>,
|
||||
//! }
|
||||
//!
|
||||
//! let err = Foo {
|
||||
//! advice: Some("try doing this instead".to_string()),
|
||||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! ### ... multiple related errors
|
||||
//!
|
||||
//! `miette` supports collecting multiple errors into a single diagnostic, and
|
||||
|
|
|
|||
|
|
@ -238,6 +238,62 @@ fn fmt_help() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_field() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic()]
|
||||
struct Foo {
|
||||
#[help]
|
||||
do_this: Option<String>,
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Foo {
|
||||
do_this: Some("x".into())
|
||||
}
|
||||
.help()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic()]
|
||||
enum Bar {
|
||||
A(#[help] Option<String>),
|
||||
B {
|
||||
#[help]
|
||||
do_this: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Bar::A(Some("x".into())).help().unwrap().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Bar::B {
|
||||
do_this: Some("x".into())
|
||||
}
|
||||
.help()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
);
|
||||
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic()]
|
||||
struct Baz(#[help] Option<String>);
|
||||
|
||||
assert_eq!(
|
||||
"x".to_string(),
|
||||
Baz(Some("x".into())).help().unwrap().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_named_struct() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue