Skip to main content

crypt_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Error, Expr, Field, Fields, Meta, parse_macro_input};
4
5fn get_field_attrs<'a>(field: &'a Field) -> (bool, Option<&'a Expr>) {
6    let mut default_value = None;
7    let mut is_optional = false;
8    for attr in &field.attrs {
9        match &attr.meta {
10            Meta::NameValue(nv) => {
11                let Some(attr_ident) = nv.path.get_ident() else {
12                    continue;
13                };
14
15                if attr_ident != "default_value" {
16                    continue;
17                }
18
19                default_value = Some(&nv.value);
20                break;
21            },
22            Meta::Path(path) => {
23                let Some(ident) = path.get_ident() else {
24                    continue;
25                };
26
27                if ident == "optional" {
28                    is_optional = true;
29                    break;
30                }
31            }
32            _ => ()
33        }
34    }
35
36    (is_optional, default_value)
37}
38
39#[proc_macro_derive(Cryptic, attributes(default_value, optional))]
40/// Allows structs and enums to automatically be constructed from a
41/// parsed crypt file.
42///
43/// Properties can additionally be given default values (via `default_value`) or
44/// automatically be set to [None] when not found (via `optional`).
45pub fn cryptic_derived(input: TokenStream) -> TokenStream {
46    let input = parse_macro_input!(input as DeriveInput);
47    let obj_name = input.ident;
48
49    let implementation = match &input.data {
50        Data::Struct(data_struct) => {
51            match &data_struct.fields {
52                Fields::Named(named_fields) => {
53                    let field_construction = named_fields.named.iter().map(|f| {
54                        let (is_optional, default_value) = get_field_attrs(f);
55                        let ident = f.ident.as_ref().unwrap();
56                        match default_value {
57                            Some(def) => {
58                                quote! {
59                                    #ident: mapping
60                                        .remove(stringify!(#ident))
61                                        .map(|i|
62                                            ::crypt_config::Cryptic::cryptic(i))
63                                            .unwrap_or(Ok(match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse the default value for {}", stringify!(#ident)) })
64                                        )?,
65                                }
66                            }
67
68                            None => {
69                                if is_optional {
70                                    quote! {
71                                        #ident: match mapping.remove(stringify!(#ident)) {
72                                            Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
73                                            None => None,
74                                        },
75                                    }
76                                } else {
77                                    quote! {
78                                        #ident: match mapping.remove(stringify!(#ident)) {
79                                            Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
80                                            None => return Err(::crypt_config::CryptError::cannot_find_ident(stringify!(#ident))),
81                                        },
82                                    }
83                                }
84                            }
85                        }
86                    });
87
88                    quote! {
89                        let mut mapping: ::std::collections::HashMap<String, ::crypt_config::TracedObject> = ::crypt_config::Cryptic::cryptic(object)?;
90                        Ok(Self {
91                            #(#field_construction)*
92                        })
93                    }
94                },
95
96                Fields::Unnamed(unnammed_fields) => {
97                    let field_construction = unnammed_fields.unnamed.iter().map(|f| {
98                        let (is_optional, default_value) = get_field_attrs(f);
99                        match default_value {
100                            Some(def) => {
101                                quote! {
102                                    match it.next() {
103                                        Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
104                                        None => match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse default value") }
105                                    },
106                                }
107                            }
108
109                            None => {
110                                if is_optional {
111                                    quote! {
112                                        match it.next() {
113                                            Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
114                                            None => None,
115                                        },
116                                    }
117                                } else {
118                                    quote! {
119                                        match it.next() {
120                                            Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
121                                            None => return Err(::crypt_config::CryptError::not_enough_items())
122                                        },
123                                    }
124                                }
125                            }
126                        }
127                    });
128
129                    quote! {
130                        let items: Vec<::crypt_config::TracedObject> = Cryptic::cryptic(object)?;
131                        let mut it = items.into_iter();
132
133                        Ok(Self(
134                            #(#field_construction)*
135                        ))
136                    }
137                },
138
139                Fields::Unit =>
140                    return TokenStream::from(Error::new(obj_name.span(), "Struct must have at least one attribute").to_compile_error())
141            }
142        },
143
144        Data::Enum(data_enum) => {
145            let variant_bodies = data_enum.variants.iter().map(|var| {
146                let var_ident = &var.ident;
147                match &var.fields {
148                    Fields::Named(named_fields) => {
149                        let field_construction = named_fields.named.iter().map(|f| {
150                            let (is_optional, default_value) = get_field_attrs(f);
151                            let ident = f.ident.as_ref().unwrap();
152                            match default_value {
153                                Some(def) => {
154                                    quote! {
155                                        #ident: mapping
156                                            .remove(stringify!(#ident))
157                                            .map(|i|
158                                                ::crypt_config::Cryptic::cryptic(i))
159                                                .unwrap_or(Ok(match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse the default value for {}", stringify!(#ident)) })
160                                            )?,
161                                    }
162                                }
163
164                                None => {
165                                    if is_optional {
166                                        quote! {
167                                            #ident: match mapping.remove(stringify!(#ident)) {
168                                                Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
169                                                None => None,
170                                            },
171                                        }
172                                    } else {
173                                        quote! {
174                                            #ident: match mapping.remove(stringify!(#ident)) {
175                                                Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
176                                                None => return Err(::crypt_config::CryptError::cannot_find_ident(stringify!(#ident))),
177                                            },
178                                        }
179                                    }
180                                }
181                            }
182                        });
183
184                        quote! {
185                            {
186                                let mut mapping: ::std::collections::HashMap<String, ::crypt_config::TracedObject> = ::crypt_config::Cryptic::cryptic(object)?;
187                                Ok(Self::#var_ident {
188                                    #(#field_construction)*
189                                })
190                            }
191                        }
192                    },
193
194                    Fields::Unnamed(unnammed_fields) => {
195                        let field_construction = unnammed_fields.unnamed.iter().map(|f| {
196                            let (is_optional, default_value) = get_field_attrs(f);
197                            match default_value {
198                                Some(def) => {
199                                    quote! {
200                                        match it.next() {
201                                            Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
202                                            None => match #def.parse() { Ok(v) => v, Err(_) => panic!("Failed to parse default value") }
203                                        },
204                                    }
205                                }
206
207                                None => {
208                                    if is_optional {
209                                        quote! {
210                                            match it.next() {
211                                                Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
212                                                None => None,
213                                            },
214                                        }
215                                    } else {
216                                        quote! {
217                                            match it.next() {
218                                                Some(v) => ::crypt_config::Cryptic::cryptic(v)?,
219                                                None => return Err(::crypt_config::CryptError::not_enough_items())
220                                            },
221                                        }
222                                    }
223                                }
224                            }
225                        });
226
227                        quote! {
228                            {
229                                let items: Vec<::crypt_config::TracedObject> = Cryptic::cryptic(object)?;
230                                let mut it = items.into_iter();
231
232                                Ok(Self::#var_ident(
233                                    #(#field_construction)*
234                                ))
235                            }
236                        }
237                    },
238
239                    Fields::Unit => quote! {
240                        Ok(Self::#var_ident),
241                    }
242                }
243            });
244
245            let variant_patterns = data_enum.variants.iter().map(|var| {
246                let var_ident = &var.ident;
247                quote! {stringify!(#var_ident)}
248            });
249
250            quote! {
251                let obj: ::crypt_config::Tagged<String, ::crypt_config::TracedObject> = ::crypt_config::Cryptic::cryptic(object)?;
252                let object = obj.object;
253                match obj.tag.as_str() {
254                    #(
255                        #variant_patterns => #variant_bodies
256                    )*
257
258                    _ => Err(::crypt_config::CryptError::invalid_enum_variant(obj.tag))
259                }
260            }
261        },
262
263        Data::Union(_) => {
264            return TokenStream::from(Error::new(obj_name.span(), "Only structs and enums can derive Cryptic").to_compile_error())
265        }
266    };
267
268    TokenStream::from(quote! {
269        impl ::crypt_config::Cryptic for #obj_name {
270            fn cryptic(object: ::crypt_config::TracedObject) -> Result<Self, ::crypt_config::CryptError> {
271                #implementation
272            }
273        }
274    })
275}