tpfs_log_event_procmacro/
lib.rs

1#![forbid(unsafe_code)]
2#![recursion_limit = "128"]
3
4extern crate proc_macro;
5extern crate serde_derive;
6
7use quote::ToTokens;
8
9use proc_macro::TokenStream;
10use quote::quote;
11use syn::{Data, DataEnum, DeriveInput, Fields};
12
13/// This macro is used to break up an enum into structs of the same shape, auto-derive an implementation
14///   of the `name` and `value` functions in the [tpfs_logger_port::LogKVPair] trait, and create a
15///   mod that replaces the enum but with the same name--to be more reader-friendly.
16#[proc_macro_attribute]
17pub fn logging_event(_args: TokenStream, input: TokenStream) -> TokenStream {
18    let ast: DeriveInput = syn::parse(input).unwrap();
19
20    // Error out if we're not annotating an enum
21    let data: DataEnum = match ast.data {
22        Data::Enum(d) => d,
23        _ => panic!("LoggingEvent can only be derived for enums"),
24    };
25
26    let name = ast.ident;
27
28    let variant_tokens_streams = data.variants.iter().map(|v| {
29        let var_id = &v.ident;
30        let stringified = var_id.to_string();
31
32        let fields = v.fields.clone().into_token_stream();
33        // Convert the enum variant into a struct with the same shape.
34        let struct_tokens = match &v.fields {
35            Fields::Unnamed(fields) => {
36                let fields = fields.unnamed.iter().map(|f| {
37                    let f_ty = &f.ty;
38                    quote! {
39                         pub #f_ty
40                    }
41                });
42                quote! {
43                    #[derive(::serde_derive::Serialize, Clone, Debug)]
44                    pub struct #var_id(#(#fields),*);
45                }
46            }
47            Fields::Named(fields) => {
48                let fields = fields.named.iter().map(|f| {
49                    let f_id = f.ident.as_ref().unwrap();
50                    let f_ty = &f.ty;
51                    quote! {
52                         pub #f_id: #f_ty
53                    }
54                });
55                quote! {
56                    #[derive(::serde_derive::Serialize, Clone, Debug)]
57                    pub struct #var_id {
58                        #(#fields),*
59                    }
60                }
61            }
62            _ => {
63                quote! {
64                    #[derive(::serde_derive::Serialize, Clone, Debug)]
65                    pub struct #var_id #fields;
66                }
67            }
68        };
69        // Implement the LogKVPairName and LogKVPairValue for the new struct.
70        let impl_tokens = quote! {
71            impl ::tpfs_logger_port::LogKVPairName for #var_id {
72                fn name(&self) -> &'static str {
73                    #stringified
74                }
75            }
76
77            impl ::tpfs_logger_port::LogKVPairValue for #var_id {
78                type T = Self;
79
80                fn value(&self) -> Self::T {
81                    self.clone() // TODO: We can probably get rid of this clone()
82                }
83            }
84        };
85        quote! {
86            #struct_tokens
87            #impl_tokens
88        }
89    });
90
91    // Gather all the token streams into new module with a name matching the original enum
92    let gen = quote! {
93        #[allow(non_snake_case)]
94        pub mod #name {
95            use super::*;
96            #(#variant_tokens_streams)*
97        }
98    };
99    gen.into()
100}