logid_derive/
lib.rs

1use logid_core::log_id::LogLevel;
2use proc_macro::TokenStream;
3use quote::{quote, quote_spanned};
4use syn::{parse_macro_input, DeriveInput};
5
6#[proc_macro_derive(ErrLogId)]
7pub fn derive_err_log_id(input: TokenStream) -> TokenStream {
8    derive_log_id(input, LogLevel::Error)
9}
10
11#[proc_macro_derive(WarnLogId)]
12pub fn derive_warn_log_id(input: TokenStream) -> TokenStream {
13    derive_log_id(input, LogLevel::Warn)
14}
15
16#[proc_macro_derive(InfoLogId)]
17pub fn derive_info_log_id(input: TokenStream) -> TokenStream {
18    derive_log_id(input, LogLevel::Info)
19}
20
21#[proc_macro_derive(DbgLogId)]
22pub fn derive_dbg_log_id(input: TokenStream) -> TokenStream {
23    derive_log_id(input, LogLevel::Debug)
24}
25
26#[proc_macro_derive(TraceLogId)]
27pub fn derive_trace_log_id(input: TokenStream) -> TokenStream {
28    derive_log_id(input, LogLevel::Trace)
29}
30
31fn derive_log_id(input: TokenStream, log_level: LogLevel) -> TokenStream {
32    let input = parse_macro_input!(input as DeriveInput);
33    let ident_name = input.ident;
34
35    let log_token = log_level_as_tokenstream(log_level);
36
37    match input.data {
38        syn::Data::Enum(enum_data) => {
39            let span = ident_name.span();
40
41            let mut field_identifiers = Vec::new();
42
43            for variant in enum_data.variants {
44                let field_name = variant.ident;
45                let full_field_name = match variant.fields {
46                    syn::Fields::Named(_) => {
47                        quote_spanned! {span=>
48                            #ident_name::#field_name{..}
49                        }
50                    }
51                    syn::Fields::Unnamed(_) => {
52                        quote_spanned! {span=>
53                            #ident_name::#field_name(_)
54                        }
55                    }
56                    _ => quote_spanned! {span=>
57                        #ident_name::#field_name
58                    },
59                };
60                let full_field_name_str =
61                    syn::LitStr::new(&full_field_name.to_string().replace(' ', ""), span);
62                field_identifiers.push(quote_spanned! {span=>
63                    #full_field_name => #full_field_name_str,
64                });
65            }
66
67            let from_enum = quote_spanned! {span=>
68                impl From<#ident_name> for logid::log_id::LogId {
69                    fn from(value: #ident_name) -> Self {
70                        let field_name = match value {
71                            #(#field_identifiers)*
72                        };
73
74                        logid::log_id::LogId::new(
75                            module_path!(),
76                            field_name,
77                            #log_token,
78                        )
79                    }
80                }
81            };
82
83            TokenStream::from(from_enum)
84        }
85        syn::Data::Struct(struct_data) => {
86            let span = struct_data.struct_token.span;
87            from_struct_or_union(ident_name, log_token, span)
88        }
89        syn::Data::Union(union_data) => {
90            let span = union_data.union_token.span;
91            from_struct_or_union(ident_name, log_token, span)
92        }
93    }
94}
95
96fn from_struct_or_union(
97    ident_name: proc_macro2::Ident,
98    log_token: proc_macro2::TokenStream,
99    span: proc_macro2::Span,
100) -> TokenStream {
101    let ident_name_str = syn::LitStr::new(&ident_name.to_string(), span);
102
103    let from = quote_spanned! {span=>
104        impl From<#ident_name> for logid::log_id::LogId {
105            fn from(value: #ident_name) -> Self {
106                logid::log_id::LogId::new(
107                    module_path!(),
108                    #ident_name_str,
109                    #log_token,
110                )
111            }
112        }
113    };
114
115    TokenStream::from(from)
116}
117
118#[proc_macro_derive(FromLogId)]
119pub fn derive_from_log_id(input: TokenStream) -> TokenStream {
120    let input = parse_macro_input!(input as DeriveInput);
121    let enum_name = input.ident;
122
123    match input.data {
124        syn::Data::Enum(enum_data) => {
125            let span = enum_name.span();
126
127            let mut from_fields = Vec::new();
128
129            for variant in enum_data.variants {
130                let field_name = variant.ident;
131                let full_field_name = quote_spanned! {span=>
132                    #enum_name::#field_name
133                };
134                let full_field_name_str =
135                    syn::LitStr::new(&full_field_name.to_string().replace(' ', ""), span);
136                from_fields.push(quote_spanned! {span=>
137                    v if v == #full_field_name_str => #full_field_name,
138                });
139            }
140
141            let from_log_id = quote_spanned! {span=>
142                impl From<logid::log_id::LogId> for #enum_name {
143                    fn from(value: logid::log_id::LogId) -> Self {
144                        if value.get_module_path() != module_path!() {
145
146                            return Self::default();
147                        }
148
149                        match value.get_identifier() {
150                            #(#from_fields)*
151                            _ => Self::default(),
152                        }
153                    }
154                }
155
156                impl From<logid::logging::intermediary_event::IntermediaryLogEvent> for #enum_name {
157                    fn from(value: logid::logging::intermediary_event::IntermediaryLogEvent) -> Self {
158                        value.finalize().into_event_id().into()
159                    }
160                }
161            };
162
163            TokenStream::from(from_log_id)
164        }
165        _ => panic!("Derive `FromLogId` is only implemented for enumerations where no item has fields (e.g. SomeEnum::Item(Field) is **not** allowed)."),
166    }
167}
168
169fn log_level_as_tokenstream(level: LogLevel) -> proc_macro2::TokenStream {
170    match level {
171        LogLevel::Error => quote! { logid::log_id::LogLevel::Error },
172        LogLevel::Warn => quote! { logid::log_id::LogLevel::Warn },
173        LogLevel::Info => quote! { logid::log_id::LogLevel::Info },
174        LogLevel::Debug => quote! { logid::log_id::LogLevel::Debug },
175        LogLevel::Trace => quote! { logid::log_id::LogLevel::Trace },
176    }
177}