serde_crypt_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{parse::Parse, parse_macro_input, DeriveInput, Ident, Lit, Meta, MetaList, Token, Type};
4
5struct SerdeCryptAttrStruct {
6    ident: Ident,
7    _punct: Token![=],
8    literal: Lit,
9}
10
11impl Parse for SerdeCryptAttrStruct {
12    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
13        Ok(SerdeCryptAttrStruct {
14            ident: input.parse()?,
15            _punct: input.parse()?,
16            literal: input.parse()?,
17        })
18    }
19}
20
21struct SerdeCryptTypes {
22    e: Type,
23    _punct: Token![,],
24    d: Type,
25}
26
27impl Parse for SerdeCryptTypes {
28    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
29        Ok(SerdeCryptTypes {
30            e: input.parse()?,
31            _punct: input.parse()?,
32            d: input.parse()?,
33        })
34    }
35}
36
37#[proc_macro_attribute]
38pub fn serde_crypt_gen(_meta: TokenStream, input: TokenStream) -> TokenStream {
39    let ast = parse_macro_input!(input as DeriveInput);
40    let vis = ast.vis;
41    let attrs = ast.attrs.iter().map(|e| quote! { #e }).reduce(|a, e| {
42        quote! {
43            #a
44            #e
45        }
46    });
47    let encrypted_ident = format_ident!("{}Encrypted", ast.ident);
48    let decrypted_ident = format_ident!("{}Decrypted", ast.ident);
49    let encrypted_fields = match &ast.data {
50        syn::Data::Struct(ref data_struct) => data_struct
51            .fields
52            .iter()
53            .map(|field| {
54                let ident = &field.ident;
55                let field_vis = &field.vis;
56                let mut replace = false;
57                let mut custom_types = false;
58                let mut ty = field.ty.clone();
59                let field_attrs = &field
60                    .attrs
61                    .iter()
62                    .filter(|attr| {
63                        if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta {
64                            let serde_tag: Result<SerdeCryptAttrStruct, _> =
65                                syn::parse2(tokens.clone());
66                            let crypt_types: Result<SerdeCryptTypes, _> =
67                                syn::parse2(tokens.clone());
68                            if serde_tag.is_ok() {
69                                let tokens = serde_tag.unwrap();
70                                let ident = tokens.ident.to_string();
71                                let lit: String = match tokens.literal {
72                                    Lit::Str(val) => val.value(),
73                                    _ => return true,
74                                };
75
76                                if path.is_ident("serde") && ident == "with" && lit == "serde_crypt"
77                                {
78                                    replace = true;
79                                    return false;
80                                }
81                            }
82                            if crypt_types.is_ok() {
83                                let tokens = crypt_types.unwrap();
84                                let enc = tokens.e;
85                                if path.is_ident("serde_crypt_types") {
86                                    custom_types = true;
87                                    replace = true;
88                                    ty = enc;
89                                    return false;
90                                }
91                            }
92                        }
93                        true
94                    })
95                    .map(|e| quote! {#e})
96                    .reduce(|a, e| {
97                        quote! {
98                            #a
99                            #e
100                        }
101                    });
102                if replace {
103                    if custom_types {
104                        quote! {
105                            #field_attrs
106                            #field_vis #ident: #ty
107                        }
108                    } else {
109                        quote! {
110                            #field_attrs
111                            #field_vis #ident: String
112                        }
113                    }
114                } else {
115                    quote! { #field }
116                }
117            })
118            .reduce(|a, e| {
119                return quote! {
120                    #a,
121                    #e
122                };
123            }),
124        _ => panic!("#[serde_crypt_gen] may only be used on structs"),
125    };
126    let decrypted_fields = match &ast.data {
127        syn::Data::Struct(ref data_struct) => data_struct
128            .fields
129            .iter()
130            .map(|field| {
131                let ident = &field.ident;
132                let field_vis = &field.vis;
133                let mut custom_types = false;
134                let mut ty = field.ty.clone();
135                let field_attrs = &field
136                    .attrs
137                    .iter()
138                    .filter(|attr| {
139                        if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta {
140                            let crypt_types: Result<SerdeCryptTypes, _> =
141                                syn::parse2(tokens.clone());
142                            if crypt_types.is_ok() {
143                                let tokens = crypt_types.unwrap();
144                                let dec = tokens.d;
145                                if path.is_ident("serde_crypt_types") {
146                                    custom_types = true;
147                                    ty = dec;
148                                    return false;
149                                }
150                            }
151                        }
152                        true
153                    })
154                    .map(|e| quote! {#e})
155                    .reduce(|a, e| {
156                        quote! {
157                            #a
158                            #e
159                        }
160                    });
161                if custom_types {
162                    quote! {
163                        #field_attrs
164                        #[serde(with = "serde_crypt")]
165                        #field_vis #ident: #ty
166                    }
167                } else {
168                    quote! { #field }
169                }
170            })
171            .map(|e| quote! {#e})
172            .reduce(|a, e| {
173                quote! {
174                    #a,
175                    #e
176                }
177            }),
178        _ => panic!("#[serde_crypt_gen] may only be used on structs"),
179    };
180
181    let types = quote! {
182        #attrs
183        #vis struct #encrypted_ident {
184            #encrypted_fields
185        }
186
187        #attrs
188        #vis struct #decrypted_ident {
189            #decrypted_fields
190        }
191    };
192    types.into()
193}