rust_decouple_derive/
lib.rs

1use quote::quote;
2use syn::{parse_macro_input, DeriveInput, GenericArgument, Ident, PathArguments, Type};
3
4#[proc_macro_derive(Decouple)]
5pub fn derive_env_var_parser(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
6    let input = parse_macro_input!(input as DeriveInput);
7    let struct_name = &input.ident;
8
9    let expanded = match input.data {
10        syn::Data::Struct(ref data_struct) => {
11            let fields: Vec<_> = data_struct
12                .fields
13                .iter()
14                .filter_map(|f| {
15                    let ident = f.clone().ident;
16                    let ty = f.ty.clone();
17                    let ty_is_vec = f.ty.clone();
18                    if ident.as_ref().is_some() {
19                        Some((ident.unwrap(), ty, is_vec(ty_is_vec)))
20                    } else {
21                        None
22                    }
23                })
24                .collect();
25            let non_vec_fields: Vec<_> = fields.iter().filter(|(_, _, v)| !v).collect();
26            let non_vec_fields = gen_fields(non_vec_fields);
27            let vec_fields: Vec<_> = fields.iter().filter(|(_, _, v)| *v).collect();
28            let vec_fields = gen_fields(vec_fields);
29
30            quote! {
31                impl Decouple for #struct_name {
32                    type Error = rust_decouple::core::FromEnvironmentError;
33                    fn parse() -> Result<Self, Self::Error> where Self: Sized {
34                        Ok(Self {
35                            #non_vec_fields
36                            #vec_fields
37                        })
38                    }
39                }
40            }
41        }
42        _ => panic!("#[derive(Decouple)] is only defined for structs"),
43    };
44
45    expanded.into()
46}
47
48/// this function must be improved
49fn is_vec(v: Type) -> bool {
50    let v = quote!(#v).to_string();
51    v.starts_with("Vec")
52}
53
54fn gen_fields(fields: Vec<&(Ident, Type, bool)>) -> proc_macro2::TokenStream {
55    if fields.len() == 0 {
56        return quote! {};
57    }
58
59    let field_names: Vec<_> = fields.iter().map(|(f, _, _)| f).collect();
60    let field_names_uppercase: Vec<_> = fields
61        .iter()
62        .map(|(f, _, _)| f.to_string().to_uppercase())
63        .collect();
64    let field_types: Vec<_> = fields.iter().map(|(_, t, _)| t).collect();
65    let is_vec = fields.iter().next().unwrap().2;
66
67    if is_vec {
68        let extracted_field_types: Vec<_> = field_types
69            .iter()
70            .map(|ty| extract_inner_type_from_vec(ty).unwrap())
71            .collect();
72
73        quote! {
74            #(#field_names: (rust_decouple::core::VecEnvironment::from::<#extracted_field_types>(#field_names_uppercase, None))?,)*
75        }
76    } else {
77        quote! {
78            #(#field_names: (rust_decouple::core::Environment::from::<#field_types>(#field_names_uppercase, None))?,)*
79        }
80    }
81}
82
83// This function checks if a type is a Vec<T> and returns T if it is.
84fn extract_inner_type_from_vec(ty: &Type) -> Option<&Type> {
85    // Check if the type is a Path (which represents types like Vec, Option, etc.)
86    if let Type::Path(type_path) = ty {
87        // Check if the path segments are not empty and match "Vec"
88        if let Some(path_segment) = type_path.path.segments.last() {
89            if path_segment.ident == "Vec" {
90                // Now we check the generic arguments (which would hold the inner type T)
91                if let PathArguments::AngleBracketed(angle_bracketed_args) = &path_segment.arguments
92                {
93                    // Check if there is exactly one generic argument (Vec<T> has one)
94                    if let Some(GenericArgument::Type(inner_type)) =
95                        angle_bracketed_args.args.first()
96                    {
97                        return Some(inner_type); // Return the inner type T
98                    }
99                }
100            }
101        }
102    }
103    None
104}