nushell/crates/nu-derive-value/src/attributes.rs
Piepmatz ebe42241fe
Add #[nu_value(rename = "...")] as helper attribute on members for derive macros (#13761)
# Description

This PR allows the helper attribute `nu_value(rename = "...")` to be
used on struct fields and enum variants. This allows renaming keys and
variants just like [`#[serde(rename =
"name")]`](https://serde.rs/field-attrs.html#rename). This has no
singular variants for `IntoValue` or `FromValue`, both need to use the
same (but I think this shouldn't be an issue for now).

# User-Facing Changes

Users of the derive macros `IntoValue` and `FromValue` may now use
`#[nu_value(rename = "...")]` to rename single fields, but no already
existing code will break.
2024-09-04 11:27:21 +02:00

130 lines
4.1 KiB
Rust

use syn::{meta::ParseNestedMeta, spanned::Spanned, Attribute, Fields, LitStr};
use crate::{case::Case, error::DeriveError, HELPER_ATTRIBUTE};
pub trait ParseAttrs: Default {
fn parse_attrs<'a, M>(
iter: impl IntoIterator<Item = &'a Attribute>,
) -> Result<Self, DeriveError<M>> {
let mut attrs = Self::default();
for attr in filter(iter.into_iter()) {
// This is a container to allow returning derive errors inside the parse_nested_meta fn.
let mut err = Ok(());
let _ = attr.parse_nested_meta(|meta| {
attrs.parse_attr(meta).or_else(|e| {
err = Err(e);
Ok(()) // parse_nested_meta requires another error type, so we escape it here
})
});
err?; // Shortcircuit here if `err` is holding some error.
}
Ok(attrs)
}
fn parse_attr<M>(&mut self, attr_meta: ParseNestedMeta<'_>) -> Result<(), DeriveError<M>>;
}
#[derive(Debug, Default)]
pub struct ContainerAttributes {
pub rename_all: Option<Case>,
pub type_name: Option<String>,
}
impl ParseAttrs for ContainerAttributes {
fn parse_attr<M>(&mut self, attr_meta: ParseNestedMeta<'_>) -> Result<(), DeriveError<M>> {
let ident = attr_meta.path.require_ident()?;
match ident.to_string().as_str() {
"rename_all" => {
let case: LitStr = attr_meta.value()?.parse()?;
let value_span = case.span();
let case = case.value();
match Case::from_str(&case) {
Some(case) => self.rename_all = Some(case),
None => {
return Err(DeriveError::InvalidAttributeValue {
value_span,
value: Box::new(case),
});
}
}
}
"type_name" => {
let type_name: LitStr = attr_meta.value()?.parse()?;
let type_name = type_name.value();
self.type_name = Some(type_name);
}
ident => {
return Err(DeriveError::UnexpectedAttribute {
meta_span: ident.span(),
});
}
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct MemberAttributes {
pub rename: Option<String>,
}
impl ParseAttrs for MemberAttributes {
fn parse_attr<M>(&mut self, attr_meta: ParseNestedMeta<'_>) -> Result<(), DeriveError<M>> {
let ident = attr_meta.path.require_ident()?;
match ident.to_string().as_str() {
"rename" => {
let rename: LitStr = attr_meta.value()?.parse()?;
let rename = rename.value();
self.rename = Some(rename);
}
ident => {
return Err(DeriveError::UnexpectedAttribute {
meta_span: ident.span(),
});
}
}
Ok(())
}
}
pub fn filter<'a>(
iter: impl Iterator<Item = &'a Attribute>,
) -> impl Iterator<Item = &'a Attribute> {
iter.filter(|attr| attr.path().is_ident(HELPER_ATTRIBUTE))
}
// The deny functions are built to easily deny the use of the helper attribute if used incorrectly.
// As the usage of it gets more complex, these functions might be discarded or replaced.
/// Deny any attribute that uses the helper attribute.
pub fn deny<M>(attrs: &[Attribute]) -> Result<(), DeriveError<M>> {
match filter(attrs.iter()).next() {
Some(attr) => Err(DeriveError::InvalidAttributePosition {
attribute_span: attr.span(),
}),
None => Ok(()),
}
}
/// Deny any attributes that uses the helper attribute on any field.
pub fn deny_fields<M>(fields: &Fields) -> Result<(), DeriveError<M>> {
match fields {
Fields::Named(fields) => {
for field in fields.named.iter() {
deny(&field.attrs)?;
}
}
Fields::Unnamed(fields) => {
for field in fields.unnamed.iter() {
deny(&field.attrs)?;
}
}
Fields::Unit => (),
}
Ok(())
}