Skip to content

Commit

Permalink
✨ zd,zv: Value: Add support string representation for enums
Browse files Browse the repository at this point in the history
Co-authored-by: Zeeshan Ali Khan <[email protected]>
  • Loading branch information
katyo and zeenix committed Feb 14, 2025
1 parent 4cdcabe commit f693be5
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 17 deletions.
20 changes: 17 additions & 3 deletions zvariant/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1447,10 +1447,10 @@ mod tests {
let decoded: NoReprEnum = encoded.deserialize().unwrap().0;
assert_eq!(decoded, NoReprEnum::Variant2);

#[derive(Deserialize, Serialize, Type, Debug, PartialEq)]
#[zvariant(signature = "s")]
#[derive(Deserialize, Serialize, Type, Value, OwnedValue, Debug, PartialEq)]
#[zvariant(signature = "s", rename_all = "snake_case")]
enum StrEnum {
Variant1,
VariantOne,
Variant2,
Variant3,
}
Expand All @@ -1461,6 +1461,20 @@ mod tests {
let decoded: StrEnum = encoded.deserialize().unwrap().0;
assert_eq!(decoded, StrEnum::Variant2);

assert_eq!(
StrEnum::try_from(Value::Str("variant_one".into())),
Ok(StrEnum::VariantOne)
);
assert_eq!(
StrEnum::try_from(Value::Str("variant2".into())),
Ok(StrEnum::Variant2)
);
assert_eq!(
StrEnum::try_from(Value::Str("variant4".into())),
Err(Error::IncorrectType)
);
assert_eq!(StrEnum::try_from(Value::U32(0)), Err(Error::IncorrectType));

#[derive(Deserialize, Serialize, Type)]
enum NewType {
Variant1(f64),
Expand Down
24 changes: 22 additions & 2 deletions zvariant_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,26 @@ pub fn deserialize_dict_macro_derive(input: TokenStream) -> TokenStream {
/// assert_eq!(e, Enum::Variant2);
/// ```
///
/// String-encoded enums are also supported:
///
/// ```
/// # use zvariant::{OwnedValue, Value};
/// #
/// #[derive(Debug, PartialEq, Value, OwnedValue)]
/// #[zvariant(signature = "s")]
/// enum StrEnum {
/// Variant1,
/// Variant2,
/// }
///
/// let value = Value::from(StrEnum::Variant1);
/// let e = StrEnum::try_from(value).unwrap();
/// assert_eq!(e, StrEnum::Variant1);
/// let value = OwnedValue::try_from(StrEnum::Variant2).unwrap();
/// let e = StrEnum::try_from(value).unwrap();
/// assert_eq!(e, StrEnum::Variant2);
/// ```
///
/// # Dictionary encoding
///
/// For treating your type as a dictionary, you can use the `signature = "dict"` attribute. See
Expand All @@ -412,7 +432,7 @@ pub fn deserialize_dict_macro_derive(input: TokenStream) -> TokenStream {
///
/// [`Value`]: https://docs.rs/zvariant/latest/zvariant/enum.Value.html
/// [`Type`]: derive.Type.html#custom-types
#[proc_macro_derive(Value)]
#[proc_macro_derive(Value, attributes(zbus, zvariant))]
pub fn value_macro_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
value::expand_derive(ast, value::ValueType::Value)
Expand All @@ -427,7 +447,7 @@ pub fn value_macro_derive(input: TokenStream) -> TokenStream {
/// See [`Value`] documentation for examples.
///
/// [`OwnedValue`]: https://docs.rs/zvariant/latest/zvariant/struct.OwnedValue.html
#[proc_macro_derive(OwnedValue)]
#[proc_macro_derive(OwnedValue, attributes(zbus, zvariant))]
pub fn owned_value_macro_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
value::expand_derive(ast, value::ValueType::OwnedValue)
Expand Down
4 changes: 4 additions & 0 deletions zvariant_derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ def_attrs! {
pub StructAttributes("struct") { signature str, rename_all str, deny_unknown_fields none };
/// Attributes defined on fields.
pub FieldAttributes("field") { rename str };
/// Attributes defined on enumerations.
pub EnumAttributes("enum") { signature str, rename_all str };
/// Attributes defined on variants.
pub VariantAttributes("variant") { rename str };
}
93 changes: 81 additions & 12 deletions zvariant_derive/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
Lifetime, LifetimeParam,
Lifetime, LifetimeParam, Variant,
};
use zvariant_utils::macros;
use zvariant_utils::{case, macros};

use crate::utils::*;

Expand Down Expand Up @@ -275,26 +275,53 @@ fn impl_enum(
Some(repr_attr) => repr_attr.parse_args()?,
None => quote! { u32 },
};
let enum_attrs = EnumAttributes::parse(&attrs)?;
let str_enum = enum_attrs
.signature
.map(|sig| sig == "s")
.unwrap_or_default();

let mut variant_names = vec![];
let mut str_values = vec![];
for variant in &data.variants {
let variant_attrs = VariantAttributes::parse(&variant.attrs)?;
// Ensure all variants of the enum are unit type
match variant.fields {
Fields::Unit => {
variant_names.push(&variant.ident);
if str_enum {
let str_value = enum_name_for_variant(
variant,
variant_attrs.rename,
enum_attrs.rename_all.as_ref().map(AsRef::as_ref),
)?;
str_values.push(str_value);
}
}
_ => return Err(Error::new(variant.span(), "must be a unit variant")),
}
}

let into_val = if str_enum {
quote! {
match e {
#(
#name::#variant_names => #str_values,
)*
}
}
} else {
quote! { e as #repr }
};

let (value_type, into_value) = match value_type {
ValueType::Value => (
quote! { #zv::Value<'_> },
quote! {
impl ::std::convert::From<#name> for #zv::Value<'_> {
#[inline]
fn from(e: #name) -> Self {
<#zv::Value as ::std::convert::From<_>>::from(e as #repr).into()
<#zv::Value as ::std::convert::From<_>>::from(#into_val)
}
}
},
Expand All @@ -308,31 +335,73 @@ fn impl_enum(
#[inline]
fn try_from(e: #name) -> #zv::Result<Self> {
<#zv::OwnedValue as ::std::convert::TryFrom<_>>::try_from(
<#zv::Value as ::std::convert::From<_>>::from(e as #repr)
<#zv::Value as ::std::convert::From<_>>::from(#into_val)
)
}
}
},
),
};

let from_val = if str_enum {
quote! {
let v: #zv::Str = ::std::convert::TryInto::try_into(value)?;

::std::result::Result::Ok(match v.as_str() {
#(
#str_values => #name::#variant_names,
)*
_ => return ::std::result::Result::Err(#zv::Error::IncorrectType),
})
}
} else {
quote! {
let v: #repr = ::std::convert::TryInto::try_into(value)?;

::std::result::Result::Ok(match v {
#(
x if x == #name::#variant_names as #repr => #name::#variant_names
),*,
_ => return ::std::result::Result::Err(#zv::Error::IncorrectType),
})
}
};

Ok(quote! {
impl ::std::convert::TryFrom<#value_type> for #name {
type Error = #zv::Error;

#[inline]
fn try_from(value: #value_type) -> #zv::Result<Self> {
let v: #repr = ::std::convert::TryInto::try_into(value)?;

::std::result::Result::Ok(match v {
#(
x if x == #name::#variant_names as #repr => #name::#variant_names
),*,
_ => return ::std::result::Result::Err(#zv::Error::IncorrectType),
})
#from_val
}
}

#into_value
})
}

fn enum_name_for_variant(
v: &Variant,
rename_attr: Option<String>,
rename_all_attr: Option<&str>,
) -> Result<String, Error> {
if let Some(name) = rename_attr {
Ok(name)
} else {
let ident = v.ident.to_string();

match rename_all_attr {
Some("lowercase") => Ok(ident.to_ascii_lowercase()),
Some("UPPERCASE") => Ok(ident.to_ascii_uppercase()),
Some("PascalCase") => Ok(case::pascal_or_camel_case(&ident, true)),
Some("camelCase") => Ok(case::pascal_or_camel_case(&ident, false)),
Some("snake_case") => Ok(case::snake_or_kebab_case(&ident, true)),
None => Ok(ident),
Some(other) => Err(Error::new(
v.span(),
format!("invalid `rename_all` attribute value {other}"),
)),
}
}
}

0 comments on commit f693be5

Please sign in to comment.