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    // Remove `None` from the iterator to avoid emitting consecutive commas in repetition
73    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}