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
  • Loading branch information
katyo committed Feb 25, 2024
1 parent 9a0050e commit 69ca851
Show file tree
Hide file tree
Showing 3 changed files with 105 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 @@ -1535,10 +1535,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 @@ -1549,6 +1549,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
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 };
}
98 changes: 84 additions & 14 deletions zvariant_derive/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
Lifetime, LifetimeDef,
Lifetime, LifetimeDef, Variant,
};
use zvariant_utils::case;

use crate::utils::*;

Expand Down Expand Up @@ -236,26 +237,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 @@ -269,34 +297,76 @@ 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(
#(
if v == #name::#variant_names as #repr {
#name::#variant_names
} else
)* {
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(
#(
if v == #name::#variant_names as #repr {
#name::#variant_names
} else
)* {
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_case(&ident)),
None => Ok(ident),
Some(other) => Err(Error::new(
v.span(),
format!("invalid `rename_all` attribute value {other}"),
)),
}
}
}

0 comments on commit 69ca851

Please sign in to comment.