from_str_sequential_derive/
lib.rs

1use proc_macro::{self, TokenStream};
2use quote::quote;
3use syn::{Data, DataEnum, DeriveInput, Fields, Ident, Type};
4
5#[proc_macro_derive(FromStrSequential)]
6pub fn from_str_sequential_derive(input: TokenStream) -> TokenStream {
7    // Construct a representation of Rust code as a syntax tree
8    // that we can manipulate
9    let DeriveInput { ident, data, .. } = syn::parse(input).unwrap();
10
11    // Build the trait implementation
12    impl_from_str_sequential(ident, data)
13}
14
15fn impl_from_str_sequential(ident: Ident, data: Data) -> TokenStream {
16    let Data::Enum(data_enum) = data else {panic!("Only enums are supported")};
17    let fields = fields_ident(data_enum);
18    let ([first_field], other_fields) = fields.split_at(1) else {panic!("Enum need to have at least one variant")};
19    let sequenced_fom_str: proc_macro2::TokenStream = format!(
20        "{}{}",
21        first_field.to_token_stream(),
22        other_fields
23            .iter()
24            .map(|f| format!(".or_else(|_| {})", f.to_token_stream()))
25            .collect::<String>()
26    )
27    .parse()
28    .unwrap();
29    let gen = quote! {
30        impl FromStrSequential for #ident {
31            type Err = String;
32            fn from_str_sequential(__str: &str) -> Result<Self, Self::Err> {
33                #sequenced_fom_str
34            }
35        }
36    };
37    gen.into()
38}
39
40// local enum to gather only the info we care about the variants
41enum CrateVariant {
42    Unit(Ident),
43    Unnamed { ident: Ident, ty: Type },
44}
45
46impl CrateVariant {
47    fn to_token_stream(&self) -> TokenStream {
48        match self {
49            Self::Unit(ident) => {
50                quote! {
51                    if __str.to_ascii_lowercase() == stringify!(#ident).to_ascii_lowercase() {
52                        Ok(Self::#ident)
53                    } else {
54                        Err("String not matching variant name (case-insensitive)".to_string())
55                    }
56                }
57                .into()
58            }
59            Self::Unnamed { ident, ty } => quote! {
60                <#ty as ::std::str::FromStr>::from_str(__str).map(Self::#ident).map_err(|e| e.to_string())
61            }
62            .into(),
63        }
64    }
65}
66
67fn fields_ident(data_enum: DataEnum) -> Vec<CrateVariant> {
68    let mut fields_ident = Vec::new();
69    for variant in data_enum.variants {
70        match variant.fields {
71            Fields::Unnamed(ref unnamed_fields) if unnamed_fields.unnamed.len() == 1 => {
72                fields_ident.push(CrateVariant::Unnamed {
73                    ident: variant.ident,
74                    ty: unnamed_fields
75                        .unnamed
76                        .first()
77                        .expect("Unnamed fields should have at least one member")
78                        .ty
79                        .clone(),
80                })
81            }
82            Fields::Unit => fields_ident.push(CrateVariant::Unit(variant.ident)),
83            _ => panic!(""),
84        }
85    }
86    fields_ident
87}