montycat_serialization_derive/
lib.rs

1// Importing the necessary crates for procedural macros
2extern crate proc_macro;
3use proc_macro::TokenStream; // TokenStream is used for passing input/output to/from the macro
4use quote::quote; // Quote is used to generate Rust code as tokens
5use syn::{parse_macro_input, DeriveInput, Ident, Fields, Data}; // Syn crate for parsing Rust syntax
6
7/// This procedural macro derives a trait called `BinaryConvert` for the struct
8/// it's applied to. The `BinaryConvert` trait provides methods to convert a
9/// struct to and from its byte representation, useful for serializing and
10/// deserializing to and from binary formats.
11///
12/// ## Overview:
13/// The trait defines two methods:
14/// - `convert_to_bytes(&self) -> Vec<u8>`: Converts the struct into a byte vector.
15/// - `convert_from_bytes(bytes: &[u8]) -> Self`: Converts a byte slice back into the struct.
16///
17/// The implementation uses `rmp_serde` (a MessagePack serializer/deserializer) for binary
18/// serialization and deserialization of the struct. The struct must also implement `Serialize`
19/// and `Deserialize` to work with `rmp_serde` correctly.
20#[proc_macro_derive(BinaryConvert)]
21pub fn binary_convert_derive(input: TokenStream) -> TokenStream {
22    // Parse the input tokens into a DeriveInput, which represents the structure
23    // of the item (e.g., struct, enum) to which the macro is being applied.
24    let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
25    // Extract the identifier (name) of the struct or enum from the parsed input.
26    let name: Ident = ast.ident;
27
28    // Generate the implementation of the `BinaryConvert` trait for the struct
29    let gen = quote! {
30        // Define the BinaryConvert trait
31        pub trait BinaryConvert: Serialize + for<'de> serde::Deserialize<'de> + Default + Sized {
32            // Method to convert the struct into a byte vector
33            fn convert_to_bytes(&self) -> Vec<u8>;
34            // Method to convert a byte slice back into the struct
35            fn convert_from_bytes(bytes: &[u8]) -> Self;
36            // Method to convert a byte slice back into the struct
37            fn convert_from_bytes_populate_option(bytes: &[u8]) -> Option<Self>;
38            // Method to convert a byte slice back into the struct
39            fn convert_to_bytes_populate_option(&self) -> Option<Vec<u8>>;
40        }
41
42        // Implement the BinaryConvert trait for the specific struct (identified by `#name`)
43        impl BinaryConvert for #name
44        where
45            #name: Serialize + for<'de> serde::Deserialize<'de> + Default, // The struct must implement `Serialize`, `Deserialize`, and `Default`
46        {
47            // Implementation of the `convert_to_bytes` method
48            fn convert_to_bytes(&self) -> Vec<u8> {
49                // Attempt to serialize the struct into a MessagePack byte vector.
50                if let Ok(vector) = rmp_serde::to_vec(self) {
51                    vector // Return the byte vector if serialization was successful
52                } else {
53                    Vec::new() // Return an empty vector if serialization failed
54                }
55            }
56
57            // Implementation of the `convert_from_bytes` method
58            fn convert_from_bytes(bytes: &[u8]) -> Self {
59                // Attempt to deserialize the byte slice into the struct.
60                if let Ok(structure) = rmp_serde::from_slice(bytes) {
61                    structure // Return the deserialized struct if successful
62                } else {
63                    Self::default() // Return the default value of the struct if deserialization failed
64                }
65            }
66
67            fn convert_from_bytes_populate_option(bytes: &[u8]) -> Option<Self> {
68                // Attempt to deserialize the byte slice into the struct.
69                rmp_serde::from_slice(bytes).ok()
70            }
71
72            fn convert_to_bytes_populate_option(&self) -> Option<Vec<u8>> {
73                // Attempt to serialize the struct into a MessagePack byte vector.
74                rmp_serde::to_vec(self).ok()
75            }
76
77        }
78    };
79
80    // Return the generated code as a TokenStream, which will be inserted into the user's code.
81    gen.into()
82}
83
84/// This procedural macro derives a trait called `RuntimeSchema` for the struct
85/// it's applied to. The `RuntimeSchema` trait provides methods to introspect
86/// the struct's fields at runtime, specifically to identify fields of certain
87/// types (like `Pointer` and `Timestamp`), retrieve all field names and types,
88/// and generate schema parameters.
89///
90//// ## Overview:
91/// The trait defines three methods:
92/// - `pointer_and_timestamp_fields(&self) -> Vec<(&'static str, &'static str)>`:
93///   Returns a vector of field names and their types for fields that are either
94///   `Pointer` or `Timestamp`.
95/// - `field_names_and_types(&self) -> Vec<(&'static str, &'static str)>`:
96///   Returns a vector of all field names and their types in the struct.
97/// - `schema_params() -> (HashMap<&'static str, &'static str>, &'static str)`:
98///   Returns a tuple containing a HashMap of field names to their types
99///   and the name of the struct.
100///
101//// The implementation uses Rust's type identification to check field types
102/// and generates the necessary code to fulfill the trait's requirements.
103///
104/// # Note:
105/// The struct must have named fields for this macro to work correctly.
106///
107/// # Dependencies:
108/// This macro assumes the existence of `Pointer` and `Timestamp` types in scope.
109///
110/// # Example Usage:
111///
112/// ```rust
113/// #[derive(RuntimeSchema)]
114/// struct MyStruct {
115///    id: Pointer,
116///   timestamp: Timestamp,
117///   name: String,
118/// }
119/// ```
120///
121/// The above will generate an implementation of `RuntimeSchema` for `MyStruct`.
122///
123/// # Generated Methods:
124/// - `pointer_and_timestamp_fields`: Will return `[("id", "Pointer"), ("timestamp", "Timestamp")]`
125/// - `field_names_and_types`: Will return `[("id", "Pointer"), ("timestamp", "Timestamp"), ("name", "String")]`
126/// - `schema_params`: Will return a HashMap with all field names and types, along with the struct name "MyStruct".
127///
128/// ```rust
129/// let (schema_map, struct_name) = MyStruct::schema_params();
130/// ```
131///
132/// The above will give you a HashMap of field names to types and the struct name.
133///
134#[proc_macro_derive(RuntimeSchema)]
135pub fn montycat_schema_derive(input: TokenStream) -> TokenStream {
136    let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
137    let name = &ast.ident;
138
139    let mut matches = Vec::new();
140    let mut field_info = Vec::new();
141    let mut schema_inserts = Vec::new();
142
143    if let Data::Struct(data_struct) = &ast.data {
144        if let Fields::Named(fields_named) = &data_struct.fields {
145            for field in fields_named.named.iter() {
146                let ident = &field.ident;
147                let ty = &field.ty;
148
149                matches.push(quote! {
150                    if std::any::TypeId::of::<#ty>() == std::any::TypeId::of::<Pointer>() {
151                        result.push((stringify!(#ident), "Pointer"));
152                    }
153                    if std::any::TypeId::of::<#ty>() == std::any::TypeId::of::<Timestamp>() {
154                        result.push((stringify!(#ident), "Timestamp"));
155                    }
156                });
157
158                field_info.push(quote! {
159                    (stringify!(#ident), stringify!(#ty))
160                });
161
162                schema_inserts.push(quote! {
163                    map.insert(stringify!(#ident), stringify!(#ty));
164                });
165
166            }
167        }
168    }
169
170    let gen = quote! {
171        impl RuntimeSchema for #name {
172            fn pointer_and_timestamp_fields(&self) -> Vec<(&'static str, &'static str)> {
173                let mut result = Vec::new();
174                #(#matches)*
175                result
176            }
177
178            fn field_names_and_types(&self) -> Vec<(&'static str, &'static str)> {
179                vec![#(#field_info),*]
180            }
181
182            fn schema_params() -> (HashMap<&'static str, &'static str>, &'static str) {
183                let mut map = std::collections::HashMap::new();
184                #(#schema_inserts)*
185                (map, stringify!(#name))
186            }
187
188        }
189    };
190
191    gen.into()
192}