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
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Meta, MetaList};

#[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 {
                            if path.is_ident("serde") {
                                // TODO: verify `tokens` to contain `with = "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 sealed_type = quote! {
        #attrs
        #vis struct #encrypted_ident {
            #encrypted_fields
        }

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