ruma_macros/serde/
enum_from_string.rs
1use proc_macro2::{Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::{Fields, FieldsNamed, FieldsUnnamed, ItemEnum};
4
5use super::{
6 attr::EnumAttrs,
7 util::{get_enum_attributes, get_rename_rule},
8};
9
10pub fn expand_enum_from_string(input: &ItemEnum) -> syn::Result<TokenStream> {
11 let enum_name = &input.ident;
12 let rename_rule = get_rename_rule(input)?;
13 let mut fallback = None;
14 let branches: Vec<_> = input
15 .variants
16 .iter()
17 .map(|v| {
18 let variant_name = &v.ident;
19 let EnumAttrs { rename, aliases } = get_enum_attributes(v)?;
20 let variant_str = match (rename, &v.fields) {
21 (None, Fields::Unit) => Some(
22 rename_rule.apply_to_variant(&variant_name.to_string()).into_token_stream(),
23 ),
24 (Some(rename), Fields::Unit) => Some(rename.into_token_stream()),
25 (None, Fields::Named(FieldsNamed { named: fields, .. }))
26 | (None, Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. })) => {
27 if fields.len() != 1 {
28 return Err(syn::Error::new_spanned(
29 v,
30 "multiple data fields are not supported",
31 ));
32 }
33
34 if fallback.is_some() {
35 return Err(syn::Error::new_spanned(
36 v,
37 "multiple data-carrying variants are not supported",
38 ));
39 }
40
41 let member = match &fields[0].ident {
42 Some(name) => name.into_token_stream(),
43 None => quote! { 0 },
44 };
45
46 let ty = &fields[0].ty;
47 fallback = Some(quote! {
48 _ => #enum_name::#variant_name {
49 #member: #ty(s.into()),
50 }
51 });
52
53 None
54 }
55 (Some(_), _) => {
56 return Err(syn::Error::new_spanned(
57 v,
58 "ruma_enum(rename) is only allowed on unit variants",
59 ));
60 }
61 };
62
63 Ok(variant_str.map(|s| {
64 quote! {
65 #( #aliases => #enum_name :: #variant_name, )*
66 #s => #enum_name :: #variant_name
67 }
68 }))
69 })
70 .collect::<syn::Result<_>>()?;
71
72 let branches = branches.iter().flatten();
74
75 if fallback.is_none() {
76 return Err(syn::Error::new(Span::call_site(), "required fallback variant not found"));
77 }
78
79 Ok(quote! {
80 #[automatically_derived]
81 #[allow(deprecated)]
82 impl<T> ::std::convert::From<T> for #enum_name
83 where
84 T: ::std::convert::AsRef<::std::primitive::str>
85 + ::std::convert::Into<::std::boxed::Box<::std::primitive::str>>
86 {
87 fn from(s: T) -> Self {
88 match s.as_ref() {
89 #( #branches, )*
90 #fallback
91 }
92 }
93 }
94 })
95}