Refactor attribute parsing in pmp-emitter-core: enforce strict types, add StrictBool, StrictString, and StrictPath wrappers, and improve error reporting
This commit is contained in:
@@ -29,6 +29,12 @@ struct StrictBool(bool);
|
||||
|
||||
impl FromMeta for StrictBool
|
||||
{
|
||||
fn from_word() -> Result<Self> {
|
||||
Err(Error::custom(
|
||||
"expected `= true/false`, provide a boolean value"
|
||||
))
|
||||
}
|
||||
|
||||
fn from_value(value: &Lit) -> Result<Self>
|
||||
{
|
||||
match value {
|
||||
@@ -36,22 +42,59 @@ impl FromMeta for StrictBool
|
||||
_ => Err(Error::unexpected_lit_type(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intentionally NOT implementing from_word() - bare `exit` is rejected
|
||||
/// A string that must be written as `= "..."`
|
||||
#[derive(Debug, Clone)]
|
||||
struct StrictString(String);
|
||||
|
||||
impl FromMeta for StrictString
|
||||
{
|
||||
fn from_word() -> Result<Self> {
|
||||
Err(Error::custom(
|
||||
"expected `= \"...\"`, provide a string value"
|
||||
))
|
||||
}
|
||||
|
||||
fn from_value(value: &Lit) -> Result<Self> {
|
||||
String::from_value(value).map(StrictString)
|
||||
}
|
||||
}
|
||||
|
||||
/// A path that must be written as `= my_fn`
|
||||
#[derive(Debug, Clone)]
|
||||
struct StrictPath(Path);
|
||||
|
||||
impl FromMeta for StrictPath
|
||||
{
|
||||
fn from_word() -> Result<Self> {
|
||||
Err(Error::custom(
|
||||
"expected `= my_fn`, provide a function path"
|
||||
))
|
||||
}
|
||||
|
||||
fn from_value(value: &Lit) -> Result<Self> {
|
||||
Path::from_value(value).map(StrictPath)
|
||||
}
|
||||
|
||||
fn from_expr(expr: &syn::Expr) -> Result<Self> {
|
||||
Path::from_expr(expr).map(StrictPath)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------- //
|
||||
|
||||
/// - `descriptor = "..."` - prefix shown before all variant messages (default: enum name)
|
||||
/// - `exit = true|false` - whether to call process::exit (default: false)
|
||||
/// - `colorizer = fn` - fn(String) -> String applied to all variants unless overridden
|
||||
#[derive(Debug, Default, FromMeta)]
|
||||
struct EnumAttrs {
|
||||
#[darling(default)]
|
||||
exit: Option<StrictBool>,
|
||||
#[darling(default)]
|
||||
colorizer: Option<Path>,
|
||||
colorizer: Option<StrictPath>,
|
||||
#[darling(default)]
|
||||
descriptor: Option<String>,
|
||||
descriptor: Option<StrictString>,
|
||||
}
|
||||
|
||||
/// - `message = "..."` - error message (default: variant name as title case)
|
||||
@@ -62,9 +105,9 @@ struct VariantAttrs {
|
||||
#[darling(default)]
|
||||
exit: Option<StrictBool>,
|
||||
#[darling(default)]
|
||||
message: Option<String>,
|
||||
message: Option<StrictString>,
|
||||
#[darling(default)]
|
||||
colorizer: Option<Path>,
|
||||
colorizer: Option<StrictPath>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------- //
|
||||
@@ -106,8 +149,10 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
||||
|
||||
let enum_ident = &shape.ident;
|
||||
let descriptor = enum_attrs.descriptor
|
||||
.map(|s| s.0)
|
||||
.unwrap_or_else(|| pascal_to_title(&enum_ident.to_string()));
|
||||
let global_exit = enum_attrs.exit.map(|b| b.0).unwrap_or(false);
|
||||
|
||||
let global_exit = enum_attrs.exit.map(|b| b.0).unwrap_or(false);
|
||||
let global_colorizer = enum_attrs.colorizer;
|
||||
|
||||
// Reject non-enums with a proper span pointing at the type name.
|
||||
@@ -117,7 +162,7 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
||||
|
||||
// Accumulate all per-variant attribute errors before failing, so the user
|
||||
// sees every problem in one compilation instead of one-at-a-time.
|
||||
let mut errors: Option<syn::Error> = None;
|
||||
let mut errors: Option<syn::Error> = None;
|
||||
let mut match_arms: Vec<TokenStream2> = Vec::with_capacity(variants.len());
|
||||
|
||||
for v in &variants
|
||||
@@ -128,7 +173,8 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
||||
let v_attrs: VariantAttrs = match parse_pmp_attr(&v.attrs)
|
||||
{
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
Err(e) =>
|
||||
{
|
||||
match &mut errors {
|
||||
None => errors = Some(e),
|
||||
Some(existing) => existing.combine(e),
|
||||
@@ -137,7 +183,7 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
||||
}
|
||||
};
|
||||
|
||||
let message = v_attrs.message.unwrap_or_else(|| title.clone());
|
||||
let message = v_attrs.message.map(|s| s.0).unwrap_or_else(|| title.clone());
|
||||
let should_exit = v_attrs.exit.map(|b| b.0).unwrap_or(global_exit);
|
||||
|
||||
// ┌─ Output format ───────────────────────────────┐
|
||||
@@ -148,8 +194,8 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
||||
let header = format!("{}: {} (\"{}\")", descriptor, title, message);
|
||||
|
||||
let formatted_header = match v_attrs.colorizer.as_ref().or(global_colorizer.as_ref()) {
|
||||
Some(path) => quote! { #path(#header) },
|
||||
None => quote! { #header.to_string() },
|
||||
Some(StrictPath(path)) => quote! { #path(#header) },
|
||||
None => quote! { #header.to_string() },
|
||||
};
|
||||
|
||||
let exit_tokens = if should_exit { quote! { std::process::exit(1); } } else { quote! {} };
|
||||
@@ -158,14 +204,21 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
||||
// source location instead of pointing at generated code.
|
||||
let arm = match v.fields.style
|
||||
{
|
||||
ast::Style::Tuple => quote_spanned! { var_ident.span() =>
|
||||
#enum_ident::#var_ident(trace) => {
|
||||
println!("{}\n\n{}", #formatted_header, trace);
|
||||
#exit_tokens
|
||||
ast::Style::Tuple =>
|
||||
{
|
||||
let pattern = if v.fields.len() == 1 { quote! { (trace) } } else { quote! { (trace, ..) } };
|
||||
|
||||
quote_spanned! { var_ident.span() =>
|
||||
#enum_ident::#var_ident #pattern =>
|
||||
{
|
||||
println!("{}\n\n{}", #formatted_header, trace);
|
||||
#exit_tokens
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => quote_spanned! { var_ident.span() =>
|
||||
#enum_ident::#var_ident => {
|
||||
#enum_ident::#var_ident =>
|
||||
{
|
||||
println!("{}", #formatted_header);
|
||||
#exit_tokens
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user