rapira_derive/
lib.rs

1extern crate proc_macro2;
2extern crate quote;
3extern crate syn;
4
5mod attributes;
6mod enum_with_primitive;
7mod enums;
8mod field_attrs;
9mod shared;
10mod simple_enum;
11mod structs;
12
13use enum_with_primitive::enum_with_primitive_serializer;
14use enums::enum_serializer;
15use proc_macro2::TokenStream;
16use quote::quote;
17use simple_enum::simple_enum_serializer;
18use structs::struct_serializer;
19use syn::{Data, DeriveInput, Fields, Ident, parse_macro_input};
20
21/// available attributes:
22/// - `#[primitive(PrimitiveName)]` - set primitive enum for complex enum
23/// - `#[idx = 1]`
24/// - `#[rapira(static_size = expr)]`
25/// - `#[rapira(min_size = expr)]`
26/// - `#[rapira(with = path)]`
27/// - `#[rapira(skip)]`
28/// - `#[rapira(debug)]`
29#[proc_macro_derive(Rapira, attributes(rapira, idx, primitive))]
30pub fn serializer_trait(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
31    let ast = parse_macro_input!(stream as DeriveInput);
32    let name = &ast.ident;
33    let data = &ast.data;
34    let is_debug = attributes::debug_attr(&ast.attrs);
35
36    match data {
37        Data::Struct(data_struct) => struct_serializer(data_struct, name, ast.generics, is_debug),
38        Data::Enum(data_enum) => {
39            let is_simple_enum = data_enum.variants.iter().all(|item| item.fields.is_empty());
40            if is_simple_enum {
41                simple_enum_serializer(name)
42            } else {
43                let primitive_name = attributes::get_primitive_name(&ast.attrs);
44
45                match primitive_name {
46                    Some(primitive_name) => {
47                        enum_with_primitive_serializer(data_enum, name, primitive_name)
48                    }
49                    None => {
50                        let enum_static_size = attributes::enum_static_size(&ast.attrs);
51                        let min_size = attributes::min_size(&ast.attrs);
52                        enum_serializer(
53                            data_enum,
54                            name,
55                            enum_static_size,
56                            min_size,
57                            ast.generics,
58                            is_debug,
59                        )
60                    }
61                }
62            }
63        }
64        Data::Union(_) => {
65            panic!(
66                "unions not supported, but Rust enums is implemented Rapira trait (use Enums instead)"
67            );
68        }
69    }
70}
71
72/// #[primitive(PrimitiveName)]
73#[proc_macro_derive(PrimitiveFromEnum, attributes(primitive))]
74pub fn derive_primitive_from_enum(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
75    let ast = parse_macro_input!(stream as DeriveInput);
76
77    let name = &ast.ident;
78    let data = &ast.data;
79
80    match data {
81        Data::Enum(data_enum) => {
82            let is_simple_enum = data_enum.variants.iter().all(|item| item.fields.is_empty());
83
84            if is_simple_enum {
85                panic!("PrimitiveFromEnum only for non simple enum allow");
86            } else {
87                let primitive_name = ast
88                    .attrs
89                    .iter()
90                    .find_map(|attr| {
91                        if !attr.path().is_ident("primitive") {
92                            return None;
93                        }
94
95                        let ident: Ident = attr.parse_args().unwrap();
96
97                        Some(ident)
98                    })
99                    .expect("complex enums must include primitive type name");
100
101                let len = data_enum.variants.len();
102
103                let mut get_primitive_enum: Vec<TokenStream> = Vec::with_capacity(len);
104
105                for variant in &data_enum.variants {
106                    let variant_name = &variant.ident;
107
108                    match &variant.fields {
109                        Fields::Unit => {
110                            get_primitive_enum.push(quote! {
111                                #name::#variant_name => #primitive_name::#variant_name,
112                            });
113                        }
114                        Fields::Unnamed(fields) => {
115                            let len = fields.unnamed.len();
116                            if len == 1 {
117                                get_primitive_enum.push(quote! {
118                                    #name::#variant_name(_) => #primitive_name::#variant_name,
119                                });
120                            } else {
121                                let underscores = vec![quote! { ,_ }; len - 1];
122                                get_primitive_enum.push(quote! {
123                                    #name::#variant_name(_ #(#underscores)*) => #primitive_name::#variant_name,
124                                });
125                            }
126                        }
127                        Fields::Named(fields) => {
128                            let fields = &fields
129                                .named
130                                .iter()
131                                .map(|f| {
132                                    let ident = f.ident.as_ref().unwrap();
133                                    quote! { #ident: _, }
134                                })
135                                .collect::<Vec<_>>();
136                            get_primitive_enum.push(quote! {
137                                #name::#variant_name{ #(#fields)* } => #primitive_name::#variant_name,
138                            });
139                        }
140                    };
141                }
142
143                let res = quote! {
144                    impl From<&#name> for #primitive_name {
145                        #[inline]
146                        fn from(value: &#name) -> Self {
147                            match value {
148                                #(#get_primitive_enum)*
149                            }
150                        }
151                    }
152                };
153
154                proc_macro::TokenStream::from(res)
155            }
156        }
157        _ => {
158            panic!("PrimitiveFromEnum only for enum allow");
159        }
160    }
161}
162
163#[proc_macro_derive(FromU8, attributes(primitive))]
164pub fn derive_from_u8(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
165    let ast = parse_macro_input!(stream as DeriveInput);
166
167    let name = &ast.ident;
168    let data = &ast.data;
169
170    match data {
171        Data::Enum(data_enum) => {
172            let is_simple_enum = data_enum.variants.iter().all(|item| item.fields.is_empty());
173            if is_simple_enum {
174                let mut variants: Vec<TokenStream> = Vec::with_capacity(data_enum.variants.len());
175                let mut try_variants: Vec<TokenStream> =
176                    Vec::with_capacity(data_enum.variants.len());
177
178                for variant in &data_enum.variants {
179                    let ident = &variant.ident;
180                    let var = quote! {
181                        u if #name::#ident == u => #name::#ident,
182                    };
183                    variants.push(var);
184                    try_variants.push(quote! {
185                        u if #name::#ident == u => Ok(#name::#ident),
186                    });
187                }
188
189                let r#gen = quote! {
190                    impl PartialEq<u8> for #name {
191                        fn eq(&self, other: &u8) -> bool {
192                            *self as u8 == *other
193                        }
194                    }
195                    impl From<#name> for u8 {
196                        fn from(e: #name) -> u8 {
197                            e as u8
198                        }
199                    }
200                    impl rapira::FromU8 for #name {
201                        /// # Panics
202                        ///
203                        /// Panics if `u` is not equal to any variant
204                        #[inline]
205                        fn from_u8(u: u8) -> Self {
206                            match u {
207                                #(#variants)*
208                                _ => panic!("FromU8 undefined value: {}", u),
209                            }
210                        }
211                    }
212                    impl core::convert::TryFrom<u8> for #name {
213                        type Error = rapira::EnumFromU8Error;
214                        fn try_from(value: u8) -> Result<Self, Self::Error> {
215                            match value {
216                                #(#try_variants)*
217                                _ => Err(rapira::EnumFromU8Error),
218                            }
219                        }
220                    }
221                };
222                proc_macro::TokenStream::from(r#gen)
223            } else {
224                panic!("FromU8 only for simple enum allow (without nested data)");
225            }
226        }
227        _ => {
228            panic!("FromU8 only for enum allow");
229        }
230    }
231}