1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Parse, parse_macro_input, DeriveInput, Ident, Lit, Meta, MetaList, Token};

struct SerdeCryptAttrStruct {
    ident: Ident,
    _punct: Token![=],
    literal: Lit,
}

impl Parse for SerdeCryptAttrStruct {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(SerdeCryptAttrStruct {
            ident: input.parse()?,
            _punct: input.parse()?,
            literal: input.parse()?,
        })
    }
}

#[proc_macro_attribute]
pub fn serde_crypt_gen(_meta: TokenStream, input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let vis = ast.vis;
    let attrs = ast.attrs.iter().map(|e| quote! { #e }).reduce(|a, e| {
        quote! {
            #a
            #e
        }
    });
    let encrypted_ident = format_ident!("{}Encrypted", ast.ident);
    let decrypted_ident = format_ident!("{}Decrypted", ast.ident);
    let encrypted_fields = match &ast.data {
        syn::Data::Struct(ref data_struct) => data_struct
            .fields
            .iter()
            .map(|field| {
                let ident = &field.ident;
                let field_vis = &field.vis;
                let mut replace = false;
                let field_attrs = &field
                    .attrs
                    .iter()
                    .filter(|attr| {
                        if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta {
                            let tokens: Result<SerdeCryptAttrStruct, _> =
                                syn::parse2(tokens.clone());
                            if tokens.is_err() {
                                return true;
                            }
                            let tokens = tokens.unwrap();
                            let ident = tokens.ident.to_string();
                            let lit: String = match tokens.literal {
                                Lit::Str(val) => val.value(),
                                _ => return true,
                            };

                            if path.is_ident("serde") && ident == "with" && lit == "serde_crypt" {
                                replace = true;
                                return false;
                            }
                        }
                        true
                    })
                    .map(|e| quote! {#e})
                    .reduce(|a, e| {
                        quote! {
                            #a
                            #e
                        }
                    });
                if replace {
                    quote! {
                        #field_attrs
                        #field_vis #ident: String
                    }
                } else {
                    quote! { #field }
                }
            })
            .reduce(|a, e| {
                return quote! {
                    #a,
                    #e
                };
            }),
        _ => panic!("#[serde_crypt] may only be used on structs"),
    };
    let decrypted_fields = match &ast.data {
        syn::Data::Struct(ref data_struct) => data_struct
            .fields
            .iter()
            .map(|e| quote! {#e})
            .reduce(|a, e| {
                quote! {
                    #a,
                    #e
                }
            }),
        _ => panic!("#[serde_crypt] may only be used on structs"),
    };

    let types = quote! {
        #attrs
        #vis struct #encrypted_ident {
            #encrypted_fields
        }

        #attrs
        #vis struct #decrypted_ident {
            #decrypted_fields
        }
    };
    types.into()
}