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.
This commit is contained in:
Piepmatz
2024-09-04 11:27:21 +02:00
committed by GitHub
parent fa4f9b083e
commit ebe42241fe
11 changed files with 599 additions and 210 deletions

View File

@ -0,0 +1,61 @@
use proc_macro2::Span;
use std::collections::HashMap;
use syn::Ident;
use crate::attributes::{ContainerAttributes, MemberAttributes};
use crate::case::{Case, Casing};
use crate::error::DeriveError;
#[derive(Debug, Default)]
pub struct NameResolver {
seen_names: HashMap<String, Span>,
}
impl NameResolver {
pub fn new() -> Self {
Self::default()
}
/// Resolves an identifier using attributes and ensures its uniqueness.
///
/// The identifier is transformed according to these rules:
/// - If [`MemberAttributes::rename`] is set, this explicitly renamed value is used.
/// The value is defined by the helper attribute `#[nu_value(rename = "...")]` on a member.
/// - If the above is not set but [`ContainerAttributes::rename_all`] is, the identifier
/// undergoes case conversion as specified by the helper attribute
/// `#[nu_value(rename_all = "...")]` on the container (struct or enum).
/// - If neither renaming attribute is set, the function applies the case conversion provided
/// by the `default` parameter.
/// If `default` is `None`, the identifier remains unchanged.
///
/// This function checks the transformed identifier against previously seen identifiers to
/// ensure it is unique.
/// If a duplicate identifier is detected, it returns [`DeriveError::NonUniqueName`].
pub fn resolve_ident<M>(
&mut self,
ident: &'_ Ident,
container_attrs: &'_ ContainerAttributes,
member_attrs: &'_ MemberAttributes,
default: impl Into<Option<Case>>,
) -> Result<String, DeriveError<M>> {
let span = ident.span();
let rename_all = container_attrs.rename_all;
let rename = member_attrs.rename.as_ref();
let ident = match (rename, rename_all) {
(Some(rename), _) => rename.to_string(),
(None, Some(case)) => ident.to_case(case),
(None, None) => ident.to_case(default),
};
if let Some(seen) = self.seen_names.get(&ident) {
return Err(DeriveError::NonUniqueName {
name: ident.to_string(),
first: *seen,
second: span,
});
}
self.seen_names.insert(ident.clone(), span);
Ok(ident)
}
}