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
|
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>
|
fn from_value(value: &Lit) -> Result<Self>
|
||||||
{
|
{
|
||||||
match value {
|
match value {
|
||||||
@@ -36,22 +42,59 @@ impl FromMeta for StrictBool
|
|||||||
_ => Err(Error::unexpected_lit_type(value)),
|
_ => 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)
|
/// - `descriptor = "..."` - prefix shown before all variant messages (default: enum name)
|
||||||
/// - `exit = true|false` - whether to call process::exit (default: false)
|
/// - `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)]
|
#[derive(Debug, Default, FromMeta)]
|
||||||
struct EnumAttrs {
|
struct EnumAttrs {
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
exit: Option<StrictBool>,
|
exit: Option<StrictBool>,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
colorizer: Option<Path>,
|
colorizer: Option<StrictPath>,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
descriptor: Option<String>,
|
descriptor: Option<StrictString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// - `message = "..."` - error message (default: variant name as title case)
|
/// - `message = "..."` - error message (default: variant name as title case)
|
||||||
@@ -62,9 +105,9 @@ struct VariantAttrs {
|
|||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
exit: Option<StrictBool>,
|
exit: Option<StrictBool>,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
message: Option<String>,
|
message: Option<StrictString>,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
colorizer: Option<Path>,
|
colorizer: Option<StrictPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------- //
|
// ---------------------------------------------------------------------------------------------- //
|
||||||
@@ -106,7 +149,9 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
|||||||
|
|
||||||
let enum_ident = &shape.ident;
|
let enum_ident = &shape.ident;
|
||||||
let descriptor = enum_attrs.descriptor
|
let descriptor = enum_attrs.descriptor
|
||||||
|
.map(|s| s.0)
|
||||||
.unwrap_or_else(|| pascal_to_title(&enum_ident.to_string()));
|
.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;
|
let global_colorizer = enum_attrs.colorizer;
|
||||||
|
|
||||||
@@ -128,7 +173,8 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
|||||||
let v_attrs: VariantAttrs = match parse_pmp_attr(&v.attrs)
|
let v_attrs: VariantAttrs = match parse_pmp_attr(&v.attrs)
|
||||||
{
|
{
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => {
|
Err(e) =>
|
||||||
|
{
|
||||||
match &mut errors {
|
match &mut errors {
|
||||||
None => errors = Some(e),
|
None => errors = Some(e),
|
||||||
Some(existing) => existing.combine(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);
|
let should_exit = v_attrs.exit.map(|b| b.0).unwrap_or(global_exit);
|
||||||
|
|
||||||
// ┌─ Output format ───────────────────────────────┐
|
// ┌─ Output format ───────────────────────────────┐
|
||||||
@@ -148,7 +194,7 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
|||||||
let header = format!("{}: {} (\"{}\")", descriptor, title, message);
|
let header = format!("{}: {} (\"{}\")", descriptor, title, message);
|
||||||
|
|
||||||
let formatted_header = match v_attrs.colorizer.as_ref().or(global_colorizer.as_ref()) {
|
let formatted_header = match v_attrs.colorizer.as_ref().or(global_colorizer.as_ref()) {
|
||||||
Some(path) => quote! { #path(#header) },
|
Some(StrictPath(path)) => quote! { #path(#header) },
|
||||||
None => quote! { #header.to_string() },
|
None => quote! { #header.to_string() },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,14 +204,21 @@ fn expand(shape: EmitterInput, raw_input: &DeriveInput) -> syn::Result<TokenStre
|
|||||||
// source location instead of pointing at generated code.
|
// source location instead of pointing at generated code.
|
||||||
let arm = match v.fields.style
|
let arm = match v.fields.style
|
||||||
{
|
{
|
||||||
ast::Style::Tuple => quote_spanned! { var_ident.span() =>
|
ast::Style::Tuple =>
|
||||||
#enum_ident::#var_ident(trace) => {
|
{
|
||||||
|
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);
|
println!("{}\n\n{}", #formatted_header, trace);
|
||||||
#exit_tokens
|
#exit_tokens
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => quote_spanned! { var_ident.span() =>
|
_ => quote_spanned! { var_ident.span() =>
|
||||||
#enum_ident::#var_ident => {
|
#enum_ident::#var_ident =>
|
||||||
|
{
|
||||||
println!("{}", #formatted_header);
|
println!("{}", #formatted_header);
|
||||||
#exit_tokens
|
#exit_tokens
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user