Skip to main content

ignite_rs_derive/
lib.rs

1use proc_macro2::{Ident, TokenStream};
2use quote::*;
3use syn::spanned::Spanned;
4use syn::{Data, DeriveInput, Fields, FieldsNamed};
5
6#[proc_macro_derive(IgniteObj)]
7pub fn derive_ignite_obj(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
8    let input = syn::parse_macro_input!(item as DeriveInput);
9
10    let type_name = &input.ident; // name of the struct
11    let output = match input.data {
12        Data::Struct(ref st) => match st.fields {
13            Fields::Named(ref fields) => {
14                let write_tokens = impl_write_type(type_name, fields);
15                let read_tokens = impl_read_type(type_name, fields);
16
17                quote! {
18                    #write_tokens
19                    #read_tokens
20                }
21            }
22            _ => quote_spanned! { st.fields.span() => compile_error!("Named struct expected!");},
23        },
24        _ => quote_spanned! { input.span() => compile_error!("Named struct expected!");},
25    };
26
27    proc_macro::TokenStream::from(output)
28}
29
30/// Implements ignite_rs::WritableType trait
31fn impl_write_type(type_name: &Ident, fields: &FieldsNamed) -> TokenStream {
32    let type_id: i32 = get_type_id(type_name);
33    let schema_id = get_schema_id(fields);
34
35    let fields_schema = fields.named.iter().map(|f| {
36        let field_name = &f.ident;
37        quote_spanned! { field_name.span() =>
38            ignite_rs::protocol::write_i32(&mut schema, ignite_rs::utils::string_to_java_hashcode(stringify!(#field_name)))?; // field id
39            ignite_rs::protocol::write_i32(&mut schema, ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN + fields.len() as i32)?; // field offset
40            self.#field_name.write(&mut fields)?;
41        }
42    });
43
44    let fields_schema_size = fields.named.iter().map(|f| {
45        let field_name = &f.ident;
46        quote_spanned! { field_name.span() =>
47            size += self.#field_name.size() + 4 + 4; // field's size, field id, fields offset
48        }
49    });
50
51    quote! {
52        impl ignite_rs::WritableType for #type_name {
53            fn write(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
54                ignite_rs::protocol::write_u8(writer, ignite_rs::protocol::TypeCode::ComplexObj as u8)?;
55                ignite_rs::protocol::write_u8(writer,1)?; //version. always 1
56                ignite_rs::protocol::write_u16(writer, ignite_rs::protocol::FLAG_USER_TYPE|ignite_rs::protocol::FLAG_HAS_SCHEMA)?; //flags
57                ignite_rs::protocol::write_i32(writer, #type_id)?; //type_id
58
59                //prepare buffers
60                let mut fields: Vec<u8> = Vec::new();
61                let mut schema: Vec<u8> = Vec::new();
62
63                //write fields
64                #( #fields_schema)*
65
66                ignite_rs::protocol::write_i32(writer, ignite_rs::utils::bytes_to_java_hashcode(fields.as_slice()))?; //hash_code. used for keys
67                ignite_rs::protocol::write_i32(writer, ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN + fields.len() as i32 + schema.len() as i32)?; //length. including header
68                ignite_rs::protocol::write_i32(writer, #schema_id)?; //schema_id
69                ignite_rs::protocol::write_i32(writer, ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN + fields.len() as i32)?; //schema offset
70                writer.write_all(&fields)?; //object fields
71                writer.write_all(&schema)?; //schema
72                // no raw_data_offset written
73                Ok(())
74            }
75
76            fn size(&self) -> usize {
77                let mut size = ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN as usize;
78                //write fields and schema sized
79                #( #fields_schema_size)*
80                size
81            }
82        }
83    }
84}
85
86/// Implements ReadableType trait
87fn impl_read_type(type_name: &Ident, fields: &FieldsNamed) -> TokenStream {
88    let exp_type_id: i32 = get_type_id(type_name);
89    let fields_count = fields.named.len();
90
91    let fields_read = fields.named.iter().map(|f| {
92        let field_name = &f.ident;
93        let ty = &f.ty;
94        let formatted_name = format_ident!("_{}", field_name.as_ref().unwrap().to_string());
95        quote_spanned! { field_name.span() =>
96            let #formatted_name = <#ty>::read(reader)?.unwrap(); // get option value
97        }
98    });
99
100    let field_pairs = fields.named.iter().map(|f| {
101        let field_name = f.ident.as_ref().unwrap();
102        let formatted_name = format_ident!("_{}", field_name);
103        quote! (#field_name: #formatted_name,)
104    });
105
106    quote! {
107            impl ignite_rs::ReadableType for #type_name {
108            fn read_unwrapped(type_code: ignite_rs::protocol::TypeCode, reader: &mut impl std::io::Read) -> ignite_rs::error::IgniteResult<Option<Self>> {
109                let value: Option<Self> = match type_code {
110                    ignite_rs::protocol::TypeCode::Null => None,
111                    _ => {
112                        ignite_rs::protocol::read_u8(reader)?; // read version. skip
113
114                        let flags = ignite_rs::protocol::read_u16(reader)?; // read and parse flags
115                        if (flags & ignite_rs::protocol::FLAG_HAS_SCHEMA) == 0 {
116                            return Err(ignite_rs::error::IgniteError::from("Serialized object schema expected!"));
117                        }
118                        if (flags & ignite_rs::protocol::FLAG_COMPACT_FOOTER) != 0 {
119                            return Err(ignite_rs::error::IgniteError::from("Compact footer is not supported!"));
120                        }
121                        if (flags & ignite_rs::protocol::FLAG_OFFSET_ONE_BYTE) != 0 || (flags & ignite_rs::protocol::FLAG_OFFSET_TWO_BYTES) != 0 {
122                            return Err(ignite_rs::error::IgniteError::from("Schema offset=4 is expected!"));
123                        }
124
125                        let type_id = ignite_rs::protocol::read_i32(reader)?; // read and check type_id
126                        if type_id != #exp_type_id {
127                            return Err(ignite_rs::error::IgniteError::from(
128                                format!("Unknown type id! {} expected!", #exp_type_id).as_str(),
129                            ));
130                        }
131
132                        ignite_rs::protocol::read_i32(reader)?; // read hashcode
133                        ignite_rs::protocol::read_i32(reader)?; // read length (with header)
134                        ignite_rs::protocol::read_i32(reader)?; // read schema id
135                        ignite_rs::protocol::read_i32(reader)?; // read schema offset
136
137                        #( #fields_read)*
138
139                        // read schema
140                        for _ in 0..#fields_count {
141                            ignite_rs::protocol::read_i64(reader)?; // read one field (id and offset)
142                        }
143
144                        Some(
145                            #type_name{
146                                #(#field_pairs)*
147                            }
148                        )
149                    }
150                };
151                Ok(value)
152            }
153        }
154    }
155}
156
157/// Schema ID based on field hashcodes
158fn get_schema_id(fields: &FieldsNamed) -> i32 {
159    fields
160        .named
161        .iter()
162        .map(|field| field.ident.as_ref().unwrap()) // can unwrap because fields are named
163        .map(|ident| ident.to_string())
164        .map(|name| string_to_java_hashcode(&name))
165        .fold(FNV1_OFFSET_BASIS, |acc, hash| {
166            let mut res = acc;
167            res ^= hash & 0xFF;
168            res = res.overflowing_mul(FNV1_PRIME).0;
169            res ^= (hash >> 8) & 0xFF;
170            res = res.overflowing_mul(FNV1_PRIME).0;
171            res ^= (hash >> 16) & 0xFF;
172            res = res.overflowing_mul(FNV1_PRIME).0;
173            res ^= (hash >> 24) & 0xFF;
174            res = res.overflowing_mul(FNV1_PRIME).0;
175            res
176        })
177}
178
179/// Java-like hashcode of type's name
180fn get_type_id(ident: &Ident) -> i32 {
181    string_to_java_hashcode(&ident.to_string())
182}
183
184/// FNV1 hash offset basis
185const FNV1_OFFSET_BASIS: i32 = 0x811C_9DC5_u32 as i32;
186/// FNV1 hash prime
187const FNV1_PRIME: i32 = 0x0100_0193;
188
189/// Converts string into Java-like hash code
190fn string_to_java_hashcode(value: &str) -> i32 {
191    let mut hash: i32 = 0;
192    for char in value.chars().into_iter() {
193        hash = 31i32.overflowing_mul(hash).0 + char as i32;
194    }
195    hash
196}