Skip to content

Commit

Permalink
Merge pull request #21 from j4r0u53k/tryfromrpcvalue-externally-tagge…
Browse files Browse the repository at this point in the history
…s-enums

TryFromRpcValue: Implement externally tagged enums
  • Loading branch information
fvacek authored Sep 1, 2024
2 parents 8ad6964 + 5d01c6b commit 340cd06
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ workspace = { members = ["libshvproto-macros"] }

[package]
name = "shvproto"
version = "3.0.14"
version = "3.0.15"
edition = "2021"

[dependencies]
Expand Down
52 changes: 36 additions & 16 deletions libshvproto-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,13 @@ fn field_to_initializers(
(struct_initializer, rpcvalue_insert)
}

#[derive(Default)]
struct StructAttributes {
tag: Option<String>,
}

fn parse_struct_attributes(attrs: &Vec<syn::Attribute>) -> syn::Result<StructAttributes> {
let mut tag: Option<String> = None;
let mut res = StructAttributes::default();
for attr in attrs {
if attr.path().is_ident("rpcvalue") {
let nested = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
Expand All @@ -108,14 +109,14 @@ fn parse_struct_attributes(attrs: &Vec<syn::Attribute>) -> syn::Result<StructAtt
let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(tag_lit), .. }) = &name_value.value else {
return Err(syn::Error::new_spanned(meta, "tag value is not a string literal"));
};
tag = Some(tag_lit.value());
res.tag = Some(tag_lit.value());
}
_ => return Err(syn::Error::new_spanned(meta, "unrecognized attributes")),
}
}
}
}
Ok(StructAttributes { tag })
Ok(res)
}

#[proc_macro_derive(TryFromRpcValue, attributes(field_name,rpcvalue))]
Expand Down Expand Up @@ -222,8 +223,7 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream {
let mut allowed_types = vec![];
let mut custom_type_matchers = vec![];
let mut match_arms_tags = vec![];
// TODO: support for external tags
let tag_key = parse_struct_attributes(&input.attrs).unwrap().tag.unwrap_or("type".to_string());
let struct_attributes = parse_struct_attributes(&input.attrs).unwrap();
let mut map_has_been_matched_as_map: Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> = None;
let mut map_has_been_matched_as_struct: Option<Vec<(proc_macro2::TokenStream, proc_macro2::TokenStream)>> = None;
for variant in variants {
Expand Down Expand Up @@ -320,10 +320,6 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream {
let mut field_idents = vec![];
let variant_ident_name = variant_ident.to_string().to_case(Case::Camel);

rpcvalue_inserts.extend(quote! {
map.insert(#tag_key.into(), #variant_ident_name.into());
});

for field in &variant_fields.named {
let field_ident = field.ident.as_ref().expect("Missing field identifier");
let (struct_initializer, rpcvalue_insert) = field_to_initializers(
Expand All @@ -338,6 +334,20 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream {
field_idents.push(field_ident);
}

if let Some(tag_key) = &struct_attributes.tag {
rpcvalue_inserts.extend(quote! {
map.insert(#tag_key.into(), #variant_ident_name.into());
});
} else {
rpcvalue_inserts = quote! {
map.insert(#variant_ident_name.into(), {
let mut map = shvproto::rpcvalue::Map::new();
#rpcvalue_inserts
map.into()
});
};
}

match_arms_ser.extend(quote!{
#struct_identifier::#variant_ident{ #(#field_idents),* } => {
let mut map = shvproto::rpcvalue::Map::new();
Expand All @@ -360,18 +370,28 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream {
}

if !match_arms_tags.is_empty() {
if let Some(tag_key) = &struct_attributes.tag {
custom_type_matchers.push(quote! {
let tag: String = x.get(#tag_key)
.ok_or_else(|| "Missing `".to_string() + #tag_key + "` key")
.and_then(|val| val
.try_into()
.map_err(|e: String| "Cannot parse `".to_string() + #tag_key + "` field: " + &e)
)
.map_err(|e| "Cannot get tag: ".to_string() + &e)?;
});
} else {
custom_type_matchers.push(quote! {
let (tag, val) = x.iter().nth(0).ok_or_else(|| "Cannot get tag from an empty Map")?;
let x: shvproto::rpcvalue::Map = val.try_into()?;
});
}

custom_type_matchers.push(quote! {
let get_key = |key_name| x
.get(key_name)
.ok_or_else(|| "Missing `".to_string() + key_name + "` key");

let tag: String = get_key(#tag_key)
.and_then(|val| val
.try_into()
.map_err(|e: String| "Cannot parse `".to_string() + #tag_key + "` field: " + &e)
)
.map_err(|e| "Cannot get tag: ".to_string() + &e)?;

match tag.as_str() {
#(#match_arms_tags)*
_ => Err("Couldn't deserialize into `".to_owned() + stringify!(#struct_identifier) + "` enum from a Map. Unknown tag `" + &tag + "`"),
Expand Down
18 changes: 15 additions & 3 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ mod test {
let _v: EnumWithNamedFields = rv.try_into().unwrap();
}

#[test]
fn enum_with_named_fields_tryinto() {
let rv: shvproto::RpcValue = shvproto::make_map!{
"linux" => shvproto::make_map!{
"user" => "alice",
"shell" => "bash",
"uptimeDays" => 10,
},
}.into();
let _v: EnumWithNamedFields = rv.try_into().unwrap();
}

#[derive(Clone,Debug,PartialEq,TryFromRpcValue)]
#[rpcvalue(tag = "os")]
pub enum EnumWithNamedFieldsCustomTag {
Expand All @@ -302,9 +314,9 @@ mod test {

#[test]
fn enum_with_named_fields_custom_tag() {
test_case(EnumWithNamedFields::Linux { user: "alice".to_string(), shell: "bash".to_string(), uptime_days: 888 });
test_case(EnumWithNamedFields::MacOsX { shell: "zsh".to_string(), user: "bob".to_string(), uptime_days: 666, });
test_case(EnumWithNamedFields::Windows { user: Some("boomer".to_string()), number_of_failures: 12 << 33 });
test_case(EnumWithNamedFieldsCustomTag::Linux { user: "alice".to_string(), shell: "bash".to_string(), uptime_days: 888 });
test_case(EnumWithNamedFieldsCustomTag::MacOsX { shell: "zsh".to_string(), user: "bob".to_string(), uptime_days: 666, });
test_case(EnumWithNamedFieldsCustomTag::Windows { user: Some("boomer".to_string()), number_of_failures: 12 << 33 });
}

#[test]
Expand Down

0 comments on commit 340cd06

Please sign in to comment.