mirror of
https://github.com/nushell/nushell.git
synced 2025-05-02 17:14:27 +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:
parent
fa4f9b083e
commit
ebe42241fe
@ -1,60 +1,92 @@
|
|||||||
use syn::{spanned::Spanned, Attribute, Fields, LitStr};
|
use syn::{meta::ParseNestedMeta, spanned::Spanned, Attribute, Fields, LitStr};
|
||||||
|
|
||||||
use crate::{case::Case, error::DeriveError, HELPER_ATTRIBUTE};
|
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)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ContainerAttributes {
|
pub struct ContainerAttributes {
|
||||||
pub rename_all: Option<Case>,
|
pub rename_all: Option<Case>,
|
||||||
pub type_name: Option<String>,
|
pub type_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerAttributes {
|
impl ParseAttrs for ContainerAttributes {
|
||||||
pub fn parse_attrs<'a, M>(
|
fn parse_attr<M>(&mut self, attr_meta: ParseNestedMeta<'_>) -> Result<(), DeriveError<M>> {
|
||||||
iter: impl Iterator<Item = &'a Attribute>,
|
let ident = attr_meta.path.require_ident()?;
|
||||||
) -> Result<Self, DeriveError<M>> {
|
|
||||||
let mut container_attrs = ContainerAttributes::default();
|
|
||||||
for attr in filter(iter) {
|
|
||||||
// This is a container to allow returning derive errors inside the parse_nested_meta fn.
|
|
||||||
let mut err = Ok(());
|
|
||||||
|
|
||||||
attr.parse_nested_meta(|meta| {
|
|
||||||
let ident = meta.path.require_ident()?;
|
|
||||||
match ident.to_string().as_str() {
|
match ident.to_string().as_str() {
|
||||||
"rename_all" => {
|
"rename_all" => {
|
||||||
let case: LitStr = meta.value()?.parse()?;
|
let case: LitStr = attr_meta.value()?.parse()?;
|
||||||
let value_span = case.span();
|
let value_span = case.span();
|
||||||
let case = case.value();
|
let case = case.value();
|
||||||
match Case::from_str(&case) {
|
match Case::from_str(&case) {
|
||||||
Some(case) => container_attrs.rename_all = Some(case),
|
Some(case) => self.rename_all = Some(case),
|
||||||
None => {
|
None => {
|
||||||
err = Err(DeriveError::InvalidAttributeValue {
|
return Err(DeriveError::InvalidAttributeValue {
|
||||||
value_span,
|
value_span,
|
||||||
value: Box::new(case),
|
value: Box::new(case),
|
||||||
});
|
});
|
||||||
return Ok(()); // We stored the err in `err`.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"type_name" => {
|
"type_name" => {
|
||||||
let type_name: LitStr = meta.value()?.parse()?;
|
let type_name: LitStr = attr_meta.value()?.parse()?;
|
||||||
let type_name = type_name.value();
|
let type_name = type_name.value();
|
||||||
container_attrs.type_name = Some(type_name);
|
self.type_name = Some(type_name);
|
||||||
}
|
}
|
||||||
ident => {
|
ident => {
|
||||||
err = Err(DeriveError::UnexpectedAttribute {
|
return Err(DeriveError::UnexpectedAttribute {
|
||||||
meta_span: ident.span(),
|
meta_span: ident.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
}
|
||||||
.map_err(DeriveError::Syn)?;
|
}
|
||||||
|
|
||||||
err?; // Shortcircuit here if `err` is holding some error.
|
#[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(container_attrs)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,12 +47,16 @@ impl Case {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Casing {
|
pub trait Casing {
|
||||||
fn to_case(&self, case: Case) -> String;
|
fn to_case(&self, case: impl Into<Option<Case>>) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> Casing for T {
|
impl<T: ToString> Casing for T {
|
||||||
fn to_case(&self, case: Case) -> String {
|
fn to_case(&self, case: impl Into<Option<Case>>) -> String {
|
||||||
let s = self.as_ref();
|
let s = self.to_string();
|
||||||
|
let Some(case) = case.into() else {
|
||||||
|
return s.to_string();
|
||||||
|
};
|
||||||
|
|
||||||
match case {
|
match case {
|
||||||
Case::Pascal => s.to_upper_camel_case(),
|
Case::Pascal => s.to_upper_camel_case(),
|
||||||
Case::Camel => s.to_lower_camel_case(),
|
Case::Camel => s.to_lower_camel_case(),
|
||||||
|
@ -28,6 +28,19 @@ pub enum DeriveError<M> {
|
|||||||
value_span: Span,
|
value_span: Span,
|
||||||
value: Box<dyn Debug>,
|
value: Box<dyn Debug>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Two keys or variants are called the same name breaking bidirectionality.
|
||||||
|
NonUniqueName {
|
||||||
|
name: String,
|
||||||
|
first: Span,
|
||||||
|
second: Span,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> From<syn::parse::Error> for DeriveError<M> {
|
||||||
|
fn from(value: syn::parse::Error) -> Self {
|
||||||
|
Self::Syn(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M> From<DeriveError<M>> for Diagnostic {
|
impl<M> From<DeriveError<M>> for Diagnostic {
|
||||||
@ -77,6 +90,15 @@ impl<M> From<DeriveError<M>> for Diagnostic {
|
|||||||
"check documentation for `{derive_name}` for valid attribute values"
|
"check documentation for `{derive_name}` for valid attribute values"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeriveError::NonUniqueName {
|
||||||
|
name,
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
} => Diagnostic::new(Level::Error, format!("non-unique name {name:?} found"))
|
||||||
|
.span_error(first, "first occurrence found here".to_string())
|
||||||
|
.span_error(second, "second occurrence found here".to_string())
|
||||||
|
.help("use `#[nu_value(rename = \"...\")]` to ensure unique names".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,15 @@ use syn::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
attributes::{self, ContainerAttributes},
|
attributes::{self, ContainerAttributes, MemberAttributes, ParseAttrs},
|
||||||
case::{Case, Casing},
|
case::Case,
|
||||||
|
names::NameResolver,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FromValue;
|
pub struct FromValue;
|
||||||
type DeriveError = super::error::DeriveError<FromValue>;
|
type DeriveError = super::error::DeriveError<FromValue>;
|
||||||
|
type Result<T = TokenStream2> = std::result::Result<T, DeriveError>;
|
||||||
|
|
||||||
/// Inner implementation of the `#[derive(FromValue)]` macro for structs and enums.
|
/// Inner implementation of the `#[derive(FromValue)]` macro for structs and enums.
|
||||||
///
|
///
|
||||||
@ -23,7 +25,7 @@ type DeriveError = super::error::DeriveError<FromValue>;
|
|||||||
/// - For structs: [`derive_struct_from_value`]
|
/// - For structs: [`derive_struct_from_value`]
|
||||||
/// - For enums: [`derive_enum_from_value`]
|
/// - For enums: [`derive_enum_from_value`]
|
||||||
/// - Unions are not supported and will return an error.
|
/// - Unions are not supported and will return an error.
|
||||||
pub fn derive_from_value(input: TokenStream2) -> Result<TokenStream2, DeriveError> {
|
pub fn derive_from_value(input: TokenStream2) -> Result {
|
||||||
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
||||||
match input.data {
|
match input.data {
|
||||||
Data::Struct(data_struct) => Ok(derive_struct_from_value(
|
Data::Struct(data_struct) => Ok(derive_struct_from_value(
|
||||||
@ -52,13 +54,15 @@ fn derive_struct_from_value(
|
|||||||
data: DataStruct,
|
data: DataStruct,
|
||||||
generics: Generics,
|
generics: Generics,
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
) -> Result<TokenStream2, DeriveError> {
|
) -> Result {
|
||||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
attributes::deny_fields(&data.fields)?;
|
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
let from_value_impl = struct_from_value(&data, container_attrs.rename_all);
|
let from_value_impl = struct_from_value(&data, &container_attrs)?;
|
||||||
let expected_type_impl =
|
let expected_type_impl = struct_expected_type(
|
||||||
struct_expected_type(&data.fields, container_attrs.type_name.as_deref());
|
&data.fields,
|
||||||
|
container_attrs.type_name.as_deref(),
|
||||||
|
&container_attrs,
|
||||||
|
)?;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||||
@ -200,28 +204,28 @@ fn derive_struct_from_value(
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn struct_from_value(data: &DataStruct, rename_all: Option<Case>) -> TokenStream2 {
|
fn struct_from_value(data: &DataStruct, container_attrs: &ContainerAttributes) -> Result {
|
||||||
let body = parse_value_via_fields(&data.fields, quote!(Self), rename_all);
|
let body = parse_value_via_fields(&data.fields, quote!(Self), container_attrs)?;
|
||||||
quote! {
|
Ok(quote! {
|
||||||
fn from_value(
|
fn from_value(
|
||||||
v: nu_protocol::Value
|
v: nu_protocol::Value
|
||||||
) -> std::result::Result<Self, nu_protocol::ShellError> {
|
) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||||
#body
|
#body
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `FromValue::expected_type` for structs.
|
/// Implements `FromValue::expected_type` for structs.
|
||||||
///
|
///
|
||||||
/// This function constructs the `expected_type` function for structs.
|
/// This function constructs the `expected_type` function for structs based on the provided fields.
|
||||||
/// The type depends on the `fields`: named fields construct a record type with every key and type
|
/// The type depends on the `fields`:
|
||||||
/// laid out.
|
/// - Named fields construct a record type where each key corresponds to a field name.
|
||||||
/// Unnamed fields construct a custom type with the name in the format like
|
/// The specific keys are resolved by [`NameResolver::resolve_ident`].
|
||||||
/// `list[type0, type1, type2]`.
|
/// - Unnamed fields construct a custom type with the format `list[type0, type1, type2]`.
|
||||||
/// No fields expect the `Type::Nothing`.
|
/// - Unit structs expect `Type::Nothing`.
|
||||||
///
|
///
|
||||||
/// If `#[nu_value(type_name = "...")]` is used, the output type will be `Type::Custom` with that
|
/// If the `#[nu_value(type_name = "...")]` attribute is used, the output type will be
|
||||||
/// passed name.
|
/// `Type::Custom` with the provided name.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -233,6 +237,7 @@ fn struct_from_value(data: &DataStruct, rename_all: Option<Case>) -> TokenStream
|
|||||||
/// struct Pet {
|
/// struct Pet {
|
||||||
/// name: String,
|
/// name: String,
|
||||||
/// age: u8,
|
/// age: u8,
|
||||||
|
/// #[nu_value(rename = "toy")]
|
||||||
/// favorite_toy: Option<String>,
|
/// favorite_toy: Option<String>,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -249,7 +254,7 @@ fn struct_from_value(data: &DataStruct, rename_all: Option<Case>) -> TokenStream
|
|||||||
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||||
/// ),
|
/// ),
|
||||||
/// (
|
/// (
|
||||||
/// std::string::ToString::to_string("favorite_toy"),
|
/// std::string::ToString::to_string("toy"),
|
||||||
/// <Option<String> as nu_protocol::FromValue>::expected_type(),
|
/// <Option<String> as nu_protocol::FromValue>::expected_type(),
|
||||||
/// )
|
/// )
|
||||||
/// ].into_boxed_slice()
|
/// ].into_boxed_slice()
|
||||||
@ -305,7 +310,11 @@ fn struct_from_value(data: &DataStruct, rename_all: Option<Case>) -> TokenStream
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn struct_expected_type(fields: &Fields, attr_type_name: Option<&str>) -> TokenStream2 {
|
fn struct_expected_type(
|
||||||
|
fields: &Fields,
|
||||||
|
attr_type_name: Option<&str>,
|
||||||
|
container_attrs: &ContainerAttributes,
|
||||||
|
) -> Result {
|
||||||
let ty = match (fields, attr_type_name) {
|
let ty = match (fields, attr_type_name) {
|
||||||
(_, Some(type_name)) => {
|
(_, Some(type_name)) => {
|
||||||
quote!(nu_protocol::Type::Custom(
|
quote!(nu_protocol::Type::Custom(
|
||||||
@ -313,20 +322,25 @@ fn struct_expected_type(fields: &Fields, attr_type_name: Option<&str>) -> TokenS
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
(Fields::Named(fields), _) => {
|
(Fields::Named(fields), _) => {
|
||||||
let fields = fields.named.iter().map(|field| {
|
let mut name_resolver = NameResolver::new();
|
||||||
|
let mut fields_ts = Vec::with_capacity(fields.named.len());
|
||||||
|
for field in fields.named.iter() {
|
||||||
|
let member_attrs = MemberAttributes::parse_attrs(&field.attrs)?;
|
||||||
let ident = field.ident.as_ref().expect("named has idents");
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
let ident_s = ident.to_string();
|
let ident_s =
|
||||||
|
name_resolver.resolve_ident(ident, container_attrs, &member_attrs, None)?;
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
quote! {(
|
fields_ts.push(quote! {(
|
||||||
std::string::ToString::to_string(#ident_s),
|
std::string::ToString::to_string(#ident_s),
|
||||||
<#ty as nu_protocol::FromValue>::expected_type(),
|
<#ty as nu_protocol::FromValue>::expected_type(),
|
||||||
)}
|
)});
|
||||||
});
|
}
|
||||||
quote!(nu_protocol::Type::Record(
|
quote!(nu_protocol::Type::Record(
|
||||||
std::vec![#(#fields),*].into_boxed_slice()
|
std::vec![#(#fields_ts),*].into_boxed_slice()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
(Fields::Unnamed(fields), _) => {
|
(f @ Fields::Unnamed(fields), _) => {
|
||||||
|
attributes::deny_fields(f)?;
|
||||||
let mut iter = fields.unnamed.iter();
|
let mut iter = fields.unnamed.iter();
|
||||||
let fields = fields.unnamed.iter().map(|field| {
|
let fields = fields.unnamed.iter().map(|field| {
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
@ -352,11 +366,11 @@ fn struct_expected_type(fields: &Fields, attr_type_name: Option<&str>) -> TokenS
|
|||||||
(Fields::Unit, _) => quote!(nu_protocol::Type::Nothing),
|
(Fields::Unit, _) => quote!(nu_protocol::Type::Nothing),
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
Ok(quote! {
|
||||||
fn expected_type() -> nu_protocol::Type {
|
fn expected_type() -> nu_protocol::Type {
|
||||||
#ty
|
#ty
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements the `#[derive(FromValue)]` macro for enums.
|
/// Implements the `#[derive(FromValue)]` macro for enums.
|
||||||
@ -372,7 +386,7 @@ fn derive_enum_from_value(
|
|||||||
data: DataEnum,
|
data: DataEnum,
|
||||||
generics: Generics,
|
generics: Generics,
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
) -> Result<TokenStream2, DeriveError> {
|
) -> Result {
|
||||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
let from_value_impl = enum_from_value(&data, &attrs)?;
|
let from_value_impl = enum_from_value(&data, &attrs)?;
|
||||||
@ -393,9 +407,9 @@ fn derive_enum_from_value(
|
|||||||
/// should be represented via a `Value`.
|
/// should be represented via a `Value`.
|
||||||
/// This function checks that every field is a unit variant and constructs a match statement over
|
/// This function checks that every field is a unit variant and constructs a match statement over
|
||||||
/// all possible variants.
|
/// all possible variants.
|
||||||
/// The input value is expected to be a `Value::String` containing the name of the variant formatted
|
/// The input value is expected to be a `Value::String` containing the name of the variant.
|
||||||
/// as defined by the `#[nu_value(rename_all = "...")]` attribute.
|
/// That string is defined by the [`NameResolver::resolve_ident`] method with the `default` value
|
||||||
/// If no attribute is given, [`snake_case`](heck::ToSnakeCase) is expected.
|
/// being [`Case::Snake`].
|
||||||
///
|
///
|
||||||
/// If no matching variant is found, `ShellError::CantConvert` is returned.
|
/// If no matching variant is found, `ShellError::CantConvert` is returned.
|
||||||
///
|
///
|
||||||
@ -405,6 +419,7 @@ fn derive_enum_from_value(
|
|||||||
/// enum Weather {
|
/// enum Weather {
|
||||||
/// Sunny,
|
/// Sunny,
|
||||||
/// Cloudy,
|
/// Cloudy,
|
||||||
|
/// #[nu_value(rename = "rain")]
|
||||||
/// Raining
|
/// Raining
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -417,7 +432,7 @@ fn derive_enum_from_value(
|
|||||||
/// match s.as_str() {
|
/// match s.as_str() {
|
||||||
/// "sunny" => std::result::Ok(Self::Sunny),
|
/// "sunny" => std::result::Ok(Self::Sunny),
|
||||||
/// "cloudy" => std::result::Ok(Self::Cloudy),
|
/// "cloudy" => std::result::Ok(Self::Cloudy),
|
||||||
/// "raining" => std::result::Ok(Self::Raining),
|
/// "rain" => std::result::Ok(Self::Raining),
|
||||||
/// _ => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
/// _ => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||||
/// to_type: std::string::ToString::to_string(
|
/// to_type: std::string::ToString::to_string(
|
||||||
/// &<Self as nu_protocol::FromValue>::expected_type()
|
/// &<Self as nu_protocol::FromValue>::expected_type()
|
||||||
@ -429,17 +444,17 @@ fn derive_enum_from_value(
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result<TokenStream2, DeriveError> {
|
fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result {
|
||||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
|
let mut name_resolver = NameResolver::new();
|
||||||
let arms: Vec<TokenStream2> = data
|
let arms: Vec<TokenStream2> = data
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
attributes::deny(&variant.attrs)?;
|
let member_attrs = MemberAttributes::parse_attrs(&variant.attrs)?;
|
||||||
let ident = &variant.ident;
|
let ident = &variant.ident;
|
||||||
let ident_s = format!("{ident}")
|
let ident_s =
|
||||||
.as_str()
|
name_resolver.resolve_ident(ident, &container_attrs, &member_attrs, Case::Snake)?;
|
||||||
.to_case(container_attrs.rename_all.unwrap_or(Case::Snake));
|
|
||||||
match &variant.fields {
|
match &variant.fields {
|
||||||
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
||||||
fields_span: fields.span(),
|
fields_span: fields.span(),
|
||||||
@ -450,7 +465,7 @@ fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result<TokenStream2,
|
|||||||
Fields::Unit => Ok(quote!(#ident_s => std::result::Result::Ok(Self::#ident))),
|
Fields::Unit => Ok(quote!(#ident_s => std::result::Result::Ok(Self::#ident))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
fn from_value(
|
fn from_value(
|
||||||
@ -512,48 +527,50 @@ fn enum_expected_type(attr_type_name: Option<&str>) -> Option<TokenStream2> {
|
|||||||
|
|
||||||
/// Parses a `Value` into self.
|
/// Parses a `Value` into self.
|
||||||
///
|
///
|
||||||
/// This function handles the actual parsing of a `Value` into self.
|
/// This function handles parsing a `Value` into the corresponding struct or enum variant (`self`).
|
||||||
/// It takes three parameters: `fields`, `self_ident` and `rename_all`.
|
/// It takes three parameters: `fields`, `self_ident`, and `rename_all`.
|
||||||
/// The `fields` parameter determines the expected type of `Value`: named fields expect a
|
|
||||||
/// `Value::Record`, unnamed fields expect a `Value::List`, and a unit expects `Value::Nothing`.
|
|
||||||
///
|
///
|
||||||
/// For named fields, the `fields` parameter indicates which field in the record corresponds to
|
/// - The `fields` parameter specifies the expected structure of the `Value`:
|
||||||
/// which struct field.
|
/// - Named fields expect a `Value::Record`.
|
||||||
/// For both named and unnamed fields, it also helps cast the type into a `FromValue` type.
|
/// - Unnamed fields expect a `Value::List`.
|
||||||
/// This approach maintains
|
/// - A unit struct expects `Value::Nothing`.
|
||||||
/// [hygiene](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene).
|
|
||||||
///
|
///
|
||||||
/// The `self_ident` parameter is used to describe the identifier of the returned value.
|
/// For named fields, each field in the record is matched to a struct field.
|
||||||
/// For structs, `Self` is usually sufficient, but for enums, `Self::Variant` may be needed in the
|
/// The name matching uses the identifiers resolved by
|
||||||
/// future.
|
/// [`NameResolver`](NameResolver::resolve_ident) with `default` being `None`.
|
||||||
///
|
///
|
||||||
/// The `rename_all` parameter is provided through `#[nu_value(rename_all = "...")]` and describes
|
/// The `self_ident` parameter is used to specify the identifier for the returned value.
|
||||||
/// how, if passed, the field keys in the `Value` should be named.
|
/// For most structs, `Self` is sufficient, but `Self::Variant` may be needed for enum variants.
|
||||||
/// If this is `None`, we keep the names as they are in the struct.
|
///
|
||||||
|
/// The `container_attrs` parameters, provided through `#[nu_value]` on the container, defines
|
||||||
|
/// global rules for the `FromValue` implementation.
|
||||||
|
/// This is used for the [`NameResolver`] to resolve the correct ident in the `Value`.
|
||||||
|
///
|
||||||
|
/// This function is more complex than the equivalent for `IntoValue` due to additional error
|
||||||
|
/// handling:
|
||||||
|
/// - If a named field is missing in the `Value`, `ShellError::CantFindColumn` is returned.
|
||||||
|
/// - For unit structs, if the value is not `Value::Nothing`, `ShellError::CantConvert` is returned.
|
||||||
///
|
///
|
||||||
/// This function is more complex than the equivalent for `IntoValue` due to error handling
|
|
||||||
/// requirements.
|
|
||||||
/// For missing fields, `ShellError::CantFindColumn` is used, and for unit structs,
|
|
||||||
/// `ShellError::CantConvert` is used.
|
|
||||||
/// The implementation avoids local variables for fields to prevent accidental shadowing, ensuring
|
/// The implementation avoids local variables for fields to prevent accidental shadowing, ensuring
|
||||||
/// that poorly named fields don't cause issues.
|
/// that fields with similar names do not cause unexpected behavior.
|
||||||
/// While this style is not typically recommended in handwritten Rust, it is acceptable for code
|
/// This approach is not typically recommended in handwritten Rust, but it is acceptable for code
|
||||||
/// generation.
|
/// generation.
|
||||||
fn parse_value_via_fields(
|
fn parse_value_via_fields(
|
||||||
fields: &Fields,
|
fields: &Fields,
|
||||||
self_ident: impl ToTokens,
|
self_ident: impl ToTokens,
|
||||||
rename_all: Option<Case>,
|
container_attrs: &ContainerAttributes,
|
||||||
) -> TokenStream2 {
|
) -> Result {
|
||||||
match fields {
|
match fields {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let fields = fields.named.iter().map(|field| {
|
let mut name_resolver = NameResolver::new();
|
||||||
|
let mut fields_ts: Vec<TokenStream2> = Vec::with_capacity(fields.named.len());
|
||||||
|
for field in fields.named.iter() {
|
||||||
|
let member_attrs = MemberAttributes::parse_attrs(&field.attrs)?;
|
||||||
let ident = field.ident.as_ref().expect("named has idents");
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
let mut ident_s = ident.to_string();
|
let ident_s =
|
||||||
if let Some(rename_all) = rename_all {
|
name_resolver.resolve_ident(ident, container_attrs, &member_attrs, None)?;
|
||||||
ident_s = ident_s.to_case(rename_all);
|
|
||||||
}
|
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
match type_is_option(ty) {
|
fields_ts.push(match type_is_option(ty) {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
#ident: record
|
#ident: record
|
||||||
.remove(#ident_s)
|
.remove(#ident_s)
|
||||||
@ -573,15 +590,16 @@ fn parse_value_via_fields(
|
|||||||
})?,
|
})?,
|
||||||
)?
|
)?
|
||||||
},
|
},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
quote! {
|
}
|
||||||
|
Ok(quote! {
|
||||||
let span = v.span();
|
let span = v.span();
|
||||||
let mut record = v.into_record()?;
|
let mut record = v.into_record()?;
|
||||||
std::result::Result::Ok(#self_ident {#(#fields),*})
|
std::result::Result::Ok(#self_ident {#(#fields_ts),*})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
f @ Fields::Unnamed(fields) => {
|
||||||
Fields::Unnamed(fields) => {
|
attributes::deny_fields(f)?;
|
||||||
let fields = fields.unnamed.iter().enumerate().map(|(i, field)| {
|
let fields = fields.unnamed.iter().enumerate().map(|(i, field)| {
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
quote! {{
|
quote! {{
|
||||||
@ -596,14 +614,14 @@ fn parse_value_via_fields(
|
|||||||
)?
|
)?
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
quote! {
|
Ok(quote! {
|
||||||
let span = v.span();
|
let span = v.span();
|
||||||
let list = v.into_list()?;
|
let list = v.into_list()?;
|
||||||
let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list);
|
let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list);
|
||||||
std::result::Result::Ok(#self_ident(#(#fields),*))
|
std::result::Result::Ok(#self_ident(#(#fields),*))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
Fields::Unit => Ok(quote! {
|
||||||
Fields::Unit => quote! {
|
|
||||||
match v {
|
match v {
|
||||||
nu_protocol::Value::Nothing {..} => Ok(#self_ident),
|
nu_protocol::Value::Nothing {..} => Ok(#self_ident),
|
||||||
v => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
v => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||||
@ -613,7 +631,7 @@ fn parse_value_via_fields(
|
|||||||
help: std::option::Option::None
|
help: std::option::Option::None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,13 +6,15 @@ use syn::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
attributes::{self, ContainerAttributes},
|
attributes::{self, ContainerAttributes, MemberAttributes, ParseAttrs},
|
||||||
case::{Case, Casing},
|
case::Case,
|
||||||
|
names::NameResolver,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IntoValue;
|
pub struct IntoValue;
|
||||||
type DeriveError = super::error::DeriveError<IntoValue>;
|
type DeriveError = super::error::DeriveError<IntoValue>;
|
||||||
|
type Result<T = TokenStream2> = std::result::Result<T, DeriveError>;
|
||||||
|
|
||||||
/// Inner implementation of the `#[derive(IntoValue)]` macro for structs and enums.
|
/// Inner implementation of the `#[derive(IntoValue)]` macro for structs and enums.
|
||||||
///
|
///
|
||||||
@ -23,7 +25,7 @@ type DeriveError = super::error::DeriveError<IntoValue>;
|
|||||||
/// - For structs: [`struct_into_value`]
|
/// - For structs: [`struct_into_value`]
|
||||||
/// - For enums: [`enum_into_value`]
|
/// - For enums: [`enum_into_value`]
|
||||||
/// - Unions are not supported and will return an error.
|
/// - Unions are not supported and will return an error.
|
||||||
pub fn derive_into_value(input: TokenStream2) -> Result<TokenStream2, DeriveError> {
|
pub fn derive_into_value(input: TokenStream2) -> Result {
|
||||||
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
||||||
match input.data {
|
match input.data {
|
||||||
Data::Struct(data_struct) => Ok(struct_into_value(
|
Data::Struct(data_struct) => Ok(struct_into_value(
|
||||||
@ -48,6 +50,7 @@ pub fn derive_into_value(input: TokenStream2) -> Result<TokenStream2, DeriveErro
|
|||||||
/// `IntoValue`.
|
/// `IntoValue`.
|
||||||
/// For structs with named fields, the derived implementation creates a `Value::Record` using the
|
/// For structs with named fields, the derived implementation creates a `Value::Record` using the
|
||||||
/// struct fields as keys.
|
/// struct fields as keys.
|
||||||
|
/// The specific keys are resolved by [`NameResolver`](NameResolver::resolve_ident).
|
||||||
/// Each field value is converted using the `IntoValue::into_value` method.
|
/// Each field value is converted using the `IntoValue::into_value` method.
|
||||||
/// For structs with unnamed fields, this generates a `Value::List` with each field in the list.
|
/// For structs with unnamed fields, this generates a `Value::List` with each field in the list.
|
||||||
/// For unit structs, this generates `Value::Nothing`, because there is no data.
|
/// For unit structs, this generates `Value::Nothing`, because there is no data.
|
||||||
@ -111,9 +114,8 @@ fn struct_into_value(
|
|||||||
data: DataStruct,
|
data: DataStruct,
|
||||||
generics: Generics,
|
generics: Generics,
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
) -> Result<TokenStream2, DeriveError> {
|
) -> Result {
|
||||||
let rename_all = ContainerAttributes::parse_attrs(attrs.iter())?.rename_all;
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
attributes::deny_fields(&data.fields)?;
|
|
||||||
let record = match &data.fields {
|
let record = match &data.fields {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let accessor = fields
|
let accessor = fields
|
||||||
@ -121,7 +123,7 @@ fn struct_into_value(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|field| field.ident.as_ref().expect("named has idents"))
|
.map(|field| field.ident.as_ref().expect("named has idents"))
|
||||||
.map(|ident| quote!(self.#ident));
|
.map(|ident| quote!(self.#ident));
|
||||||
fields_return_value(&data.fields, accessor, rename_all)
|
fields_return_value(&data.fields, accessor, &container_attrs)?
|
||||||
}
|
}
|
||||||
Fields::Unnamed(fields) => {
|
Fields::Unnamed(fields) => {
|
||||||
let accessor = fields
|
let accessor = fields
|
||||||
@ -130,7 +132,7 @@ fn struct_into_value(
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(n, _)| Index::from(n))
|
.map(|(n, _)| Index::from(n))
|
||||||
.map(|index| quote!(self.#index));
|
.map(|index| quote!(self.#index));
|
||||||
fields_return_value(&data.fields, accessor, rename_all)
|
fields_return_value(&data.fields, accessor, &container_attrs)?
|
||||||
}
|
}
|
||||||
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
||||||
};
|
};
|
||||||
@ -150,10 +152,13 @@ fn struct_into_value(
|
|||||||
/// This function implements the derive macro `IntoValue` for enums.
|
/// This function implements the derive macro `IntoValue` for enums.
|
||||||
/// Currently, only unit enum variants are supported as it is not clear how other types of enums
|
/// Currently, only unit enum variants are supported as it is not clear how other types of enums
|
||||||
/// should be represented in a `Value`.
|
/// should be represented in a `Value`.
|
||||||
/// For simple enums, we represent the enum as a `Value::String`. For other types of variants, we return an error.
|
/// For simple enums, we represent the enum as a `Value::String`.
|
||||||
/// The variant name will be case-converted as described by the `#[nu_value(rename_all = "...")]` helper attribute.
|
/// For other types of variants, we return an error.
|
||||||
/// If no attribute is used, the default is `case_convert::Case::Snake`.
|
///
|
||||||
/// The implementation matches over all variants, uses the appropriate variant name, and constructs a `Value::String`.
|
/// The variant name used in the `Value::String` is resolved by the
|
||||||
|
/// [`NameResolver`](NameResolver::resolve_ident) with the `default` being [`Case::Snake`].
|
||||||
|
/// The implementation matches over all variants, uses the appropriate variant name, and constructs
|
||||||
|
/// a `Value::String`.
|
||||||
///
|
///
|
||||||
/// This is how such a derived implementation looks:
|
/// This is how such a derived implementation looks:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -161,6 +166,7 @@ fn struct_into_value(
|
|||||||
/// enum Weather {
|
/// enum Weather {
|
||||||
/// Sunny,
|
/// Sunny,
|
||||||
/// Cloudy,
|
/// Cloudy,
|
||||||
|
/// #[nu_value(rename = "rain")]
|
||||||
/// Raining
|
/// Raining
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -169,7 +175,7 @@ fn struct_into_value(
|
|||||||
/// match self {
|
/// match self {
|
||||||
/// Self::Sunny => nu_protocol::Value::string("sunny", span),
|
/// Self::Sunny => nu_protocol::Value::string("sunny", span),
|
||||||
/// Self::Cloudy => nu_protocol::Value::string("cloudy", span),
|
/// Self::Cloudy => nu_protocol::Value::string("cloudy", span),
|
||||||
/// Self::Raining => nu_protocol::Value::string("raining", span),
|
/// Self::Raining => nu_protocol::Value::string("rain", span),
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
@ -179,17 +185,21 @@ fn enum_into_value(
|
|||||||
data: DataEnum,
|
data: DataEnum,
|
||||||
generics: Generics,
|
generics: Generics,
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
) -> Result<TokenStream2, DeriveError> {
|
) -> Result {
|
||||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
|
let mut name_resolver = NameResolver::new();
|
||||||
let arms: Vec<TokenStream2> = data
|
let arms: Vec<TokenStream2> = data
|
||||||
.variants
|
.variants
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|variant| {
|
.map(|variant| {
|
||||||
attributes::deny(&variant.attrs)?;
|
let member_attrs = MemberAttributes::parse_attrs(variant.attrs.iter())?;
|
||||||
let ident = variant.ident;
|
let ident = variant.ident;
|
||||||
let ident_s = format!("{ident}")
|
let ident_s = name_resolver.resolve_ident(
|
||||||
.as_str()
|
&ident,
|
||||||
.to_case(container_attrs.rename_all.unwrap_or(Case::Snake));
|
&container_attrs,
|
||||||
|
&member_attrs,
|
||||||
|
Case::Snake,
|
||||||
|
)?;
|
||||||
match &variant.fields {
|
match &variant.fields {
|
||||||
// In the future we can implement more complex enums here.
|
// In the future we can implement more complex enums here.
|
||||||
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
||||||
@ -203,7 +213,7 @@ fn enum_into_value(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
@ -219,57 +229,63 @@ fn enum_into_value(
|
|||||||
|
|
||||||
/// Constructs the final `Value` that the macro generates.
|
/// Constructs the final `Value` that the macro generates.
|
||||||
///
|
///
|
||||||
/// This function handles the construction of the final `Value` that the macro generates.
|
/// This function handles the construction of the final `Value` that the macro generates, primarily
|
||||||
/// It is currently only used for structs but may be used for enums in the future.
|
/// for structs.
|
||||||
/// The function takes three parameters: the `fields`, which allow iterating over each field of a
|
/// It takes three parameters: `fields`, which allows iterating over each field of a data type,
|
||||||
/// data type, the `accessor` and `rename_all`.
|
/// `accessor`, which generalizes data access, and `container_attrs`, which is used for the
|
||||||
/// The fields determine whether we need to generate a `Value::Record`, `Value::List`, or
|
/// [`NameResolver`].
|
||||||
/// `Value::Nothing`.
|
|
||||||
/// For named fields, they are also directly used to generate the record key.
|
|
||||||
/// If `#[nu_value(rename_all = "...")]` is used and then passed in here via `rename_all`, the
|
|
||||||
/// named fields will be converted to the given case and then uses as the record key.
|
|
||||||
///
|
///
|
||||||
/// The `accessor` parameter generalizes how the data is accessed.
|
/// - **Field Keys**:
|
||||||
/// For named fields, this is usually the name of the fields preceded by `self` in a struct, and
|
/// The field key is field name of the input struct and resolved the
|
||||||
/// maybe something else for enums.
|
/// [`NameResolver`](NameResolver::resolve_ident).
|
||||||
/// For unnamed fields, this should be an iterator similar to the one with named fields, but
|
///
|
||||||
/// accessing tuple fields, so we get `self.n`.
|
/// - **Fields Type**:
|
||||||
/// For unit structs, this parameter is ignored.
|
/// - Determines whether to generate a `Value::Record`, `Value::List`, or `Value::Nothing` based
|
||||||
/// By using the accessor like this, we can have the same code for structs and enums with data
|
/// on the nature of the fields.
|
||||||
|
/// - Named fields are directly used to generate the record key, as described above.
|
||||||
|
///
|
||||||
|
/// - **Accessor**:
|
||||||
|
/// - Generalizes how data is accessed for different data types.
|
||||||
|
/// - For named fields in structs, this is typically `self.field_name`.
|
||||||
|
/// - For unnamed fields (e.g., tuple structs), it should be an iterator similar to named fields
|
||||||
|
/// but accessing fields like `self.0`.
|
||||||
|
/// - For unit structs, this parameter is ignored.
|
||||||
|
///
|
||||||
|
/// This design allows the same function to potentially handle both structs and enums with data
|
||||||
/// variants in the future.
|
/// variants in the future.
|
||||||
fn fields_return_value(
|
fn fields_return_value(
|
||||||
fields: &Fields,
|
fields: &Fields,
|
||||||
accessor: impl Iterator<Item = impl ToTokens>,
|
accessor: impl Iterator<Item = impl ToTokens>,
|
||||||
rename_all: Option<Case>,
|
container_attrs: &ContainerAttributes,
|
||||||
) -> TokenStream2 {
|
) -> Result {
|
||||||
match fields {
|
match fields {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let items: Vec<TokenStream2> = fields
|
let mut name_resolver = NameResolver::new();
|
||||||
.named
|
let mut items: Vec<TokenStream2> = Vec::with_capacity(fields.named.len());
|
||||||
.iter()
|
for (field, accessor) in fields.named.iter().zip(accessor) {
|
||||||
.zip(accessor)
|
let member_attrs = MemberAttributes::parse_attrs(field.attrs.iter())?;
|
||||||
.map(|(field, accessor)| {
|
|
||||||
let ident = field.ident.as_ref().expect("named has idents");
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
let mut field = ident.to_string();
|
let field =
|
||||||
if let Some(rename_all) = rename_all {
|
name_resolver.resolve_ident(ident, container_attrs, &member_attrs, None)?;
|
||||||
field = field.to_case(rename_all);
|
items.push(quote!(#field => nu_protocol::IntoValue::into_value(#accessor, span)));
|
||||||
}
|
}
|
||||||
quote!(#field => nu_protocol::IntoValue::into_value(#accessor, span))
|
Ok(quote! {
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
quote! {
|
|
||||||
nu_protocol::Value::record(nu_protocol::record! {
|
nu_protocol::Value::record(nu_protocol::record! {
|
||||||
#(#items),*
|
#(#items),*
|
||||||
}, span)
|
}, span)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
f @ Fields::Unnamed(fields) => {
|
||||||
Fields::Unnamed(fields) => {
|
attributes::deny_fields(f)?;
|
||||||
let items =
|
let items =
|
||||||
fields.unnamed.iter().zip(accessor).map(
|
fields.unnamed.iter().zip(accessor).map(
|
||||||
|(_, accessor)| quote!(nu_protocol::IntoValue::into_value(#accessor, span)),
|
|(_, accessor)| quote!(nu_protocol::IntoValue::into_value(#accessor, span)),
|
||||||
);
|
);
|
||||||
quote!(nu_protocol::Value::list(std::vec![#(#items),*], span))
|
Ok(quote!(nu_protocol::Value::list(
|
||||||
|
std::vec![#(#items),*],
|
||||||
|
span
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
Fields::Unit => Ok(quote!(nu_protocol::Value::nothing(span))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ mod case;
|
|||||||
mod error;
|
mod error;
|
||||||
mod from;
|
mod from;
|
||||||
mod into;
|
mod into;
|
||||||
|
mod names;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
61
crates/nu-derive-value/src/names.rs
Normal file
61
crates/nu-derive-value/src/names.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -86,24 +86,81 @@ fn unexpected_attribute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deny_attribute_on_fields() {
|
fn unexpected_attribute_on_struct_field() {
|
||||||
let input = quote! {
|
let input = quote! {
|
||||||
struct SomeStruct {
|
struct SimpleStruct {
|
||||||
#[nu_value]
|
#[nu_value(what)]
|
||||||
field: ()
|
field_a: i32,
|
||||||
|
field_b: String,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_res = derive_from_value(input.clone());
|
let from_res = derive_from_value(input.clone());
|
||||||
assert!(
|
assert!(
|
||||||
matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
matches!(from_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||||
|
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||||
|
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unexpected_attribute_on_enum_variant() {
|
||||||
|
let input = quote! {
|
||||||
|
enum SimpleEnum {
|
||||||
|
#[nu_value(what)]
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||||
|
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||||
|
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_attribute_position_in_tuple_struct() {
|
||||||
|
let input = quote! {
|
||||||
|
struct SimpleTupleStruct(
|
||||||
|
#[nu_value(what)]
|
||||||
|
i32,
|
||||||
|
String,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
from_res,
|
||||||
|
Err(DeriveError::InvalidAttributePosition { attribute_span: _ })
|
||||||
|
),
|
||||||
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||||
from_res
|
from_res
|
||||||
);
|
);
|
||||||
|
|
||||||
let into_res = derive_into_value(input);
|
let into_res = derive_into_value(input);
|
||||||
assert!(
|
assert!(
|
||||||
matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
matches!(
|
||||||
|
into_res,
|
||||||
|
Err(DeriveError::InvalidAttributePosition { attribute_span: _ })
|
||||||
|
),
|
||||||
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||||
into_res
|
into_res
|
||||||
);
|
);
|
||||||
@ -133,3 +190,53 @@ fn invalid_attribute_value() {
|
|||||||
into_res
|
into_res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_unique_struct_keys() {
|
||||||
|
let input = quote! {
|
||||||
|
struct DuplicateStruct {
|
||||||
|
#[nu_value(rename = "field")]
|
||||||
|
some_field: (),
|
||||||
|
field: (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::NonUniqueName { .. })),
|
||||||
|
"expected `DeriveError::NonUniqueName`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::NonUniqueName { .. })),
|
||||||
|
"expected `DeriveError::NonUniqueName`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_unique_enum_variants() {
|
||||||
|
let input = quote! {
|
||||||
|
enum DuplicateEnum {
|
||||||
|
#[nu_value(rename = "variant")]
|
||||||
|
SomeVariant,
|
||||||
|
Variant
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::NonUniqueName { .. })),
|
||||||
|
"expected `DeriveError::NonUniqueName`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::NonUniqueName { .. })),
|
||||||
|
"expected `DeriveError::NonUniqueName`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -20,13 +20,17 @@ use std::{
|
|||||||
///
|
///
|
||||||
/// When derived on structs with named fields, it expects a [`Value::Record`] where each field of
|
/// 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.
|
/// 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
|
/// Supported case conversions include those provided by [`heck`], such as
|
||||||
/// "snake_case", "kebab-case", "PascalCase", and others.
|
/// "snake_case", "kebab-case", "PascalCase", and others.
|
||||||
/// Additionally, all values accepted by
|
/// Additionally, all values accepted by
|
||||||
/// [`#[serde(rename_all = "...")]`](https://serde.rs/container-attrs.html#rename_all) are valid here.
|
/// [`#[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
|
/// For structs with unnamed fields, it expects a [`Value::List`], and the fields are populated in
|
||||||
/// the order they appear in the list.
|
/// the order they appear in the list.
|
||||||
@ -35,13 +39,18 @@ use std::{
|
|||||||
///
|
///
|
||||||
/// Only enums with no fields may derive this trait.
|
/// Only enums with no fields may derive this trait.
|
||||||
/// The expected value representation will be the name of the variant as a [`Value::String`].
|
/// 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
|
/// 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.
|
/// 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.
|
/// This can be useful in situations where the default type name is not desired.
|
||||||
///
|
///
|
||||||
|
/// # Enum Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
||||||
/// #
|
/// #
|
||||||
@ -52,11 +61,17 @@ use std::{
|
|||||||
/// enum Bird {
|
/// enum Bird {
|
||||||
/// MountainEagle,
|
/// MountainEagle,
|
||||||
/// ForestOwl,
|
/// ForestOwl,
|
||||||
|
/// #[nu_value(rename = "RIVER-QUACK")]
|
||||||
/// RiverDuck,
|
/// RiverDuck,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// 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
|
/// Bird::RiverDuck
|
||||||
/// );
|
/// );
|
||||||
///
|
///
|
||||||
@ -64,14 +79,21 @@ use std::{
|
|||||||
/// &Bird::expected_type().to_string(),
|
/// &Bird::expected_type().to_string(),
|
||||||
/// "birb"
|
/// "birb"
|
||||||
/// );
|
/// );
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
///
|
/// # Struct Example
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{FromValue, Value, ShellError, record, Span};
|
||||||
|
/// #
|
||||||
|
/// # let span = Span::unknown();
|
||||||
|
/// #
|
||||||
/// #[derive(FromValue, PartialEq, Eq, Debug)]
|
/// #[derive(FromValue, PartialEq, Eq, Debug)]
|
||||||
/// #[nu_value(rename_all = "kebab-case")]
|
/// #[nu_value(rename_all = "kebab-case")]
|
||||||
/// struct Person {
|
/// struct Person {
|
||||||
/// first_name: String,
|
/// first_name: String,
|
||||||
/// last_name: String,
|
/// last_name: String,
|
||||||
/// age: u32,
|
/// #[nu_value(rename = "age")]
|
||||||
|
/// age_years: u32,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let value = Value::record(record! {
|
/// let value = Value::record(record! {
|
||||||
@ -85,7 +107,7 @@ use std::{
|
|||||||
/// Person {
|
/// Person {
|
||||||
/// first_name: "John".into(),
|
/// first_name: "John".into(),
|
||||||
/// last_name: "Doe".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]`.
|
/// This trait can be used with `#[derive]`.
|
||||||
/// When derived on structs with named fields, the resulting value representation will use
|
/// 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.
|
/// [`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.
|
/// By default, field names will be used as-is unless specified otherwise:
|
||||||
/// All case options from [`heck`] are supported, as well as the values allowed by
|
/// - If `#[nu_value(rename = "...")]` is applied to a specific field, that name is used.
|
||||||
/// [`#[serde(rename_all)]`](https://serde.rs/container-attrs.html#rename_all).
|
/// - 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
|
/// For structs with unnamed fields, the value representation will be [`Value::List`], with all
|
||||||
/// fields inserted into a list.
|
/// fields inserted into a list.
|
||||||
/// Unit structs will be represented as [`Value::Nothing`] since they contain no data.
|
/// Unit structs will be represented as [`Value::Nothing`] since they contain no data.
|
||||||
///
|
///
|
||||||
/// Only enums with no fields may derive this trait.
|
/// For enums, the resulting value representation depends on the variant name:
|
||||||
/// The resulting value representation will be the name of the variant as a [`Value::String`].
|
/// - If `#[nu_value(rename = "...")]` is applied to a specific variant, that name is used.
|
||||||
/// By default, variant names will be converted to ["snake_case"](heck::ToSnakeCase).
|
/// - If `#[nu_value(rename_all = "...")]` is applied to the enum, variant names will be
|
||||||
/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum.
|
/// 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};
|
/// # use nu_protocol::{IntoValue, Value, Span, record};
|
||||||
/// #
|
/// #
|
||||||
@ -34,28 +44,41 @@ use std::{borrow::Borrow, collections::HashMap};
|
|||||||
/// enum Bird {
|
/// enum Bird {
|
||||||
/// MountainEagle,
|
/// MountainEagle,
|
||||||
/// ForestOwl,
|
/// ForestOwl,
|
||||||
|
/// #[nu_value(rename = "RIVER-QUACK")]
|
||||||
/// RiverDuck,
|
/// RiverDuck,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// Bird::RiverDuck.into_value(span),
|
/// Bird::ForestOwl.into_value(span),
|
||||||
/// Value::test_string("RIVER-DUCK")
|
/// 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)]
|
/// #[derive(IntoValue)]
|
||||||
/// #[nu_value(rename_all = "kebab-case")]
|
/// #[nu_value(rename_all = "kebab-case")]
|
||||||
/// struct Person {
|
/// struct Person {
|
||||||
/// first_name: String,
|
/// first_name: String,
|
||||||
/// last_name: String,
|
/// last_name: String,
|
||||||
/// age: u32,
|
/// #[nu_value(rename = "age")]
|
||||||
|
/// age_years: u32,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// Person {
|
/// Person {
|
||||||
/// first_name: "John".into(),
|
/// first_name: "John".into(),
|
||||||
/// last_name: "Doe".into(),
|
/// last_name: "Doe".into(),
|
||||||
/// age: 42,
|
/// age_years: 42,
|
||||||
/// }.into_value(span),
|
/// }.into_value(span),
|
||||||
/// Value::record(record! {
|
/// Value::record(record! {
|
||||||
/// "first-name" => Value::string("John", span),
|
/// "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");
|
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);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user