zephyr_macros/
lib.rs

1
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{self, ext, parse_macro_input, DeriveInput, Expr, ExprLit, FieldsNamed, Ident, Lit, LitStr, Type};
5
6// todo: clean code
7
8#[proc_macro_derive(DatabaseInteract, attributes(with_name, external))]
9pub fn database_interact_derive(input: TokenStream) -> TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11    let struct_name = &input.ident;
12    
13    let with_name_attr = input.attrs.iter().find_map(|attr| {
14        if attr.path().is_ident("with_name") {
15            let value: Expr = attr.parse_args().unwrap();
16            if let Expr::Lit(ExprLit { lit, .. }) = value {
17                if let Lit::Str(value) = lit {
18                    return Some(value.value())
19                } else {
20                    panic!("Invalid lit type")
21                }
22            } else {
23                panic!("Invalid type")
24            }
25        } else {
26            panic!("No table name provided")
27        } 
28    }).expect("No with_name attribute");
29
30    let external = input.attrs.iter().find_map(|attr| {
31        if attr.path().is_ident("external") {
32            let value: Expr = attr.parse_args().unwrap();
33            if let Expr::Lit(ExprLit { lit, .. }) = value {
34                if let Lit::Str(value) = lit {
35                    return Some(value.value().parse::<i64>().expect("Cannot parse external to i64"))
36                } else {
37                    panic!("Invalid lit type")
38                }
39            } else {
40                panic!("Invalid type")
41            }
42        } else {
43            return None
44        } 
45    });
46
47    let (is_external, external) = {
48        if let Some(external) = external {
49            (true, external)
50        } else {
51            (false, 0)
52        }
53    };
54
55    let idents: Vec<(Ident, usize, Ident)> = match input.data {
56        syn::Data::Struct(s) => match s.fields {
57            syn::Fields::Named(FieldsNamed { named, .. }) => {
58                named.iter().enumerate().map(|(idx, field)| {
59                    let Type::Path(path) = &field.ty else {
60                        panic!("unsupported field type")
61                    };
62
63                    (field.ident.clone().unwrap(), idx, path.path.segments[0].ident.clone())
64
65                }).collect()
66            }
67            _ => panic!("Unnamed structs are not supported.")
68        },
69
70        _ => panic!("Unsupported type.")
71    };
72    let field_literals: Vec<Lit> = idents
73        .iter()
74        .map(|ident| {
75            let field_str = LitStr::new(&ident.0.to_string(), ident.0.span());
76            Lit::Str(field_str)
77        })
78        .collect();
79
80    macro_rules! check_type {
81        ($t:expr, $($expected:literal),*) => {
82            matches!($t, $($expected)|*)
83        };
84    }
85    
86    let construction_code = idents.iter().map(|(ident, _, field_type)| {
87        if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
88            quote! {
89                #ident: #ident.try_into().unwrap(),
90            }
91        } else {
92            quote! {
93                #ident,
94            }
95        }
96    });
97
98    let deser_code = idents.iter().map(|(ident, index, field_type)| {
99        let field_string = field_type.to_string();
100        let field_str = field_string.as_str();
101        if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
102            quote! {
103                let bytes = row.row.get(#index).unwrap();
104                let #ident = bincode::deserialize::<ZephyrVal>(&bytes.0).unwrap();
105            
106            }
107        } else if check_type!(field_str, "ScVal", "Hash") {
108            quote! {
109                let bytes = row.row.get(#index).unwrap();
110                let #ident = ReadXdr::from_xdr(&bytes.0, Limits::none()).unwrap();
111            
112            }
113        } else {
114            quote! {
115                let bytes = row.row.get(#index).unwrap();
116                let #ident = bincode::deserialize(&bytes.0).unwrap();
117                
118            }
119        }
120    });
121
122    let serialize_type = idents.iter().map(|(ident, _, field_type)| {
123        if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
124            quote! {
125                bincode::serialize(&TryInto::<ZephyrVal>::try_into(self.#ident.clone()).unwrap()).unwrap().as_slice()
126            }
127        } else if check_type!(field_type.to_string().as_str(), "ScVal", "Hash") {
128            quote! {
129                self.#ident.clone().to_xdr(Limits::none()).unwrap().as_slice()
130            }
131        }  else {
132            quote! {
133                bincode::serialize(&self.#ident).unwrap().as_slice()
134            }
135        }
136    });
137
138    let serialize_type_update = idents.iter().map(|(ident, _, field_type)| {
139        if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
140            quote! {
141                bincode::serialize(&TryInto::<ZephyrVal>::try_into(self.#ident.clone()).unwrap()).unwrap().as_slice()
142            }
143        } else if check_type!(field_type.to_string().as_str(), "ScVal", "Hash") {
144            quote! {
145                self.#ident.clone().to_xdr(Limits::none()).unwrap().as_slice()
146            }
147        } else {
148            quote! {
149                bincode::serialize(&self.#ident).unwrap().as_slice()
150            }
151        }
152    });
153
154    // Actual trait implementation generation
155    let expanded = quote! {
156        impl DatabaseInteract for #struct_name {
157            fn read_to_rows(env: &EnvClient, conditions: Option<&[Condition]>) -> Vec<Self> where Self: Sized {
158                let external = if #is_external {
159                    Some(#external)
160                } else {
161                    None
162                };
163
164                env.log().debug("calling the zephyr host db read", None);
165                let rows = env.db_read(&#with_name_attr, &[#(#field_literals),*], external, conditions);
166                if rows.is_err() {
167                    env.log().debug(format!("dbread failed {:?}", rows.as_ref().err()), None);
168                }
169                let rows = rows.unwrap();
170                let mut result = Vec::new();
171                
172                for row in rows.rows {
173                    #(#deser_code)*
174                    result.push(Self {
175                        #(#construction_code)*
176                    });
177                }
178
179
180                result
181            }
182
183            fn put(&self, env: &EnvClient) {
184                env.db_write(&#with_name_attr, &[#(#field_literals),*], &[#(#serialize_type),*]).unwrap();
185            }
186
187            fn update(&self, env: &EnvClient, conditions: &[Condition]) {
188                env.db_update(&#with_name_attr, &[#(#field_literals),*], &[#(#serialize_type_update),*], conditions).unwrap();
189            }
190        }
191    };
192
193
194    TokenStream::from(expanded)
195}
196