mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 14:36:08 +02:00
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:
@ -20,13 +20,17 @@ use std::{
|
||||
///
|
||||
/// When derived on structs with named fields, it expects a [`Value::Record`] where each field of
|
||||
/// the struct maps to a corresponding field in the record.
|
||||
/// You can customize the case conversion of these field names by using
|
||||
/// `#[nu_value(rename_all = "...")]` on the struct.
|
||||
///
|
||||
/// - If `#[nu_value(rename = "...")]` is applied to a field, that name will be used as the key in
|
||||
/// the record.
|
||||
/// - If `#[nu_value(rename_all = "...")]` is applied on the container (struct) the key of the
|
||||
/// field will be case-converted accordingly.
|
||||
/// - If neither attribute is applied, the field name is used as is.
|
||||
///
|
||||
/// Supported case conversions include those provided by [`heck`], such as
|
||||
/// "snake_case", "kebab-case", "PascalCase", and others.
|
||||
/// Additionally, all values accepted by
|
||||
/// [`#[serde(rename_all = "...")]`](https://serde.rs/container-attrs.html#rename_all) are valid here.
|
||||
/// If not set, the field names will match the original Rust field names as-is.
|
||||
///
|
||||
/// For structs with unnamed fields, it expects a [`Value::List`], and the fields are populated in
|
||||
/// the order they appear in the list.
|
||||
@ -35,13 +39,18 @@ use std::{
|
||||
///
|
||||
/// Only enums with no fields may derive this trait.
|
||||
/// The expected value representation will be the name of the variant as a [`Value::String`].
|
||||
/// By default, variant names will be expected in ["snake_case"](heck::ToSnakeCase).
|
||||
/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum.
|
||||
///
|
||||
/// - If `#[nu_value(rename = "...")]` is applied to a variant, that name will be used.
|
||||
/// - If `#[nu_value(rename_all = "...")]` is applied on the enum container, the name of variant
|
||||
/// will be case-converted accordingly.
|
||||
/// - If neither attribute is applied, the variant name will default to
|
||||
/// ["snake_case"](heck::ToSnakeCase).
|
||||
///
|
||||
/// Additionally, you can use `#[nu_value(type_name = "...")]` in the derive macro to set a custom type name
|
||||
/// for `FromValue::expected_type`. This will result in a `Type::Custom` with the specified type name.
|
||||
/// This can be useful in situations where the default type name is not desired.
|
||||
///
|
||||
/// # Enum Example
|
||||
/// ```
|
||||
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
||||
/// #
|
||||
@ -52,11 +61,17 @@ use std::{
|
||||
/// enum Bird {
|
||||
/// MountainEagle,
|
||||
/// ForestOwl,
|
||||
/// #[nu_value(rename = "RIVER-QUACK")]
|
||||
/// RiverDuck,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Bird::from_value(Value::test_string("RIVER-DUCK")).unwrap(),
|
||||
/// Bird::from_value(Value::string("FOREST-OWL", span)).unwrap(),
|
||||
/// Bird::ForestOwl
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Bird::from_value(Value::string("RIVER-QUACK", span)).unwrap(),
|
||||
/// Bird::RiverDuck
|
||||
/// );
|
||||
///
|
||||
@ -64,14 +79,21 @@ use std::{
|
||||
/// &Bird::expected_type().to_string(),
|
||||
/// "birb"
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Struct Example
|
||||
/// ```
|
||||
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
||||
/// #
|
||||
/// # let span = Span::unknown();
|
||||
/// #
|
||||
/// #[derive(FromValue, PartialEq, Eq, Debug)]
|
||||
/// #[nu_value(rename_all = "kebab-case")]
|
||||
/// struct Person {
|
||||
/// first_name: String,
|
||||
/// last_name: String,
|
||||
/// age: u32,
|
||||
/// #[nu_value(rename = "age")]
|
||||
/// age_years: u32,
|
||||
/// }
|
||||
///
|
||||
/// let value = Value::record(record! {
|
||||
@ -85,7 +107,7 @@ use std::{
|
||||
/// Person {
|
||||
/// first_name: "John".into(),
|
||||
/// last_name: "Doe".into(),
|
||||
/// age: 42,
|
||||
/// age_years: 42,
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
|
@ -10,20 +10,30 @@ use std::{borrow::Borrow, collections::HashMap};
|
||||
/// This trait can be used with `#[derive]`.
|
||||
/// When derived on structs with named fields, the resulting value representation will use
|
||||
/// [`Value::Record`], where each field of the record corresponds to a field of the struct.
|
||||
/// By default, field names will be kept as-is, but you can customize the case conversion
|
||||
/// for all fields in a struct by using `#[nu_value(rename_all = "kebab-case")]` on the struct.
|
||||
/// All case options from [`heck`] are supported, as well as the values allowed by
|
||||
/// [`#[serde(rename_all)]`](https://serde.rs/container-attrs.html#rename_all).
|
||||
///
|
||||
/// By default, field names will be used as-is unless specified otherwise:
|
||||
/// - If `#[nu_value(rename = "...")]` is applied to a specific field, that name is used.
|
||||
/// - If `#[nu_value(rename_all = "...")]` is applied to the struct, field names will be
|
||||
/// case-converted accordingly.
|
||||
/// - If neither attribute is used, the original field name will be retained.
|
||||
///
|
||||
/// For structs with unnamed fields, the value representation will be [`Value::List`], with all
|
||||
/// fields inserted into a list.
|
||||
/// Unit structs will be represented as [`Value::Nothing`] since they contain no data.
|
||||
///
|
||||
/// Only enums with no fields may derive this trait.
|
||||
/// The resulting value representation will be the name of the variant as a [`Value::String`].
|
||||
/// By default, variant names will be converted to ["snake_case"](heck::ToSnakeCase).
|
||||
/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum.
|
||||
/// For enums, the resulting value representation depends on the variant name:
|
||||
/// - If `#[nu_value(rename = "...")]` is applied to a specific variant, that name is used.
|
||||
/// - If `#[nu_value(rename_all = "...")]` is applied to the enum, variant names will be
|
||||
/// case-converted accordingly.
|
||||
/// - If neither attribute is used, variant names will default to snake_case.
|
||||
///
|
||||
/// Only enums with no fields may derive this trait.
|
||||
/// The resulting value will be the name of the variant as a [`Value::String`].
|
||||
///
|
||||
/// All case options from [`heck`] are supported, as well as the values allowed by
|
||||
/// [`#[serde(rename_all)]`](https://serde.rs/container-attrs.html#rename_all).
|
||||
///
|
||||
/// # Enum Example
|
||||
/// ```
|
||||
/// # use nu_protocol::{IntoValue, Value, Span, record};
|
||||
/// #
|
||||
@ -34,28 +44,41 @@ use std::{borrow::Borrow, collections::HashMap};
|
||||
/// enum Bird {
|
||||
/// MountainEagle,
|
||||
/// ForestOwl,
|
||||
/// #[nu_value(rename = "RIVER-QUACK")]
|
||||
/// RiverDuck,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Bird::RiverDuck.into_value(span),
|
||||
/// Value::test_string("RIVER-DUCK")
|
||||
/// Bird::ForestOwl.into_value(span),
|
||||
/// Value::string("FOREST-OWL", span)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Bird::RiverDuck.into_value(span),
|
||||
/// Value::string("RIVER-QUACK", span)
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// # Struct Example
|
||||
/// ```
|
||||
/// # use nu_protocol::{IntoValue, Value, Span, record};
|
||||
/// #
|
||||
/// # let span = Span::unknown();
|
||||
/// #
|
||||
/// #[derive(IntoValue)]
|
||||
/// #[nu_value(rename_all = "kebab-case")]
|
||||
/// struct Person {
|
||||
/// first_name: String,
|
||||
/// last_name: String,
|
||||
/// age: u32,
|
||||
/// #[nu_value(rename = "age")]
|
||||
/// age_years: u32,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Person {
|
||||
/// first_name: "John".into(),
|
||||
/// last_name: "Doe".into(),
|
||||
/// age: 42,
|
||||
/// age_years: 42,
|
||||
/// }.into_value(span),
|
||||
/// Value::record(record! {
|
||||
/// "first-name" => Value::string("John", span),
|
||||
|
@ -586,3 +586,86 @@ fn enum_type_name_attr() {
|
||||
|
||||
assert_eq!(TypeNameEnum::expected_type().to_string().as_str(), "enum");
|
||||
}
|
||||
|
||||
#[derive(IntoValue, FromValue, Default, Debug, PartialEq)]
|
||||
struct RenamedFieldStruct {
|
||||
#[nu_value(rename = "renamed")]
|
||||
field: (),
|
||||
}
|
||||
|
||||
impl RenamedFieldStruct {
|
||||
fn value() -> Value {
|
||||
Value::test_record(record! {
|
||||
"renamed" => Value::test_nothing(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_field_struct_into_value() {
|
||||
let expected = RenamedFieldStruct::value();
|
||||
let actual = RenamedFieldStruct::default().into_test_value();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_field_struct_from_value() {
|
||||
let expected = RenamedFieldStruct::default();
|
||||
let actual = RenamedFieldStruct::from_value(RenamedFieldStruct::value()).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_field_struct_roundtrip() {
|
||||
let expected = RenamedFieldStruct::default();
|
||||
let actual =
|
||||
RenamedFieldStruct::from_value(RenamedFieldStruct::default().into_test_value()).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected = RenamedFieldStruct::value();
|
||||
let actual = RenamedFieldStruct::from_value(RenamedFieldStruct::value())
|
||||
.unwrap()
|
||||
.into_test_value();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[derive(IntoValue, FromValue, Default, Debug, PartialEq)]
|
||||
enum RenamedVariantEnum {
|
||||
#[default]
|
||||
#[nu_value(rename = "renamed")]
|
||||
Variant,
|
||||
}
|
||||
|
||||
impl RenamedVariantEnum {
|
||||
fn value() -> Value {
|
||||
Value::test_string("renamed")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_variant_enum_into_value() {
|
||||
let expected = RenamedVariantEnum::value();
|
||||
let actual = RenamedVariantEnum::default().into_test_value();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_variant_enum_from_value() {
|
||||
let expected = RenamedVariantEnum::default();
|
||||
let actual = RenamedVariantEnum::from_value(RenamedVariantEnum::value()).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_variant_enum_roundtrip() {
|
||||
let expected = RenamedVariantEnum::default();
|
||||
let actual =
|
||||
RenamedVariantEnum::from_value(RenamedVariantEnum::default().into_test_value()).unwrap();
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
let expected = RenamedVariantEnum::value();
|
||||
let actual = RenamedVariantEnum::from_value(RenamedVariantEnum::value())
|
||||
.unwrap()
|
||||
.into_test_value();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
Reference in New Issue
Block a user