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; 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
30fn 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)))?; ignite_rs::protocol::write_i32(&mut schema, ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN + fields.len() as i32)?; 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; }
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)?; ignite_rs::protocol::write_u16(writer, ignite_rs::protocol::FLAG_USER_TYPE|ignite_rs::protocol::FLAG_HAS_SCHEMA)?; ignite_rs::protocol::write_i32(writer, #type_id)?; let mut fields: Vec<u8> = Vec::new();
61 let mut schema: Vec<u8> = Vec::new();
62
63 #( #fields_schema)*
65
66 ignite_rs::protocol::write_i32(writer, ignite_rs::utils::bytes_to_java_hashcode(fields.as_slice()))?; ignite_rs::protocol::write_i32(writer, ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN + fields.len() as i32 + schema.len() as i32)?; ignite_rs::protocol::write_i32(writer, #schema_id)?; ignite_rs::protocol::write_i32(writer, ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN + fields.len() as i32)?; writer.write_all(&fields)?; writer.write_all(&schema)?; Ok(())
74 }
75
76 fn size(&self) -> usize {
77 let mut size = ignite_rs::protocol::COMPLEX_OBJ_HEADER_LEN as usize;
78 #( #fields_schema_size)*
80 size
81 }
82 }
83 }
84}
85
86fn 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(); }
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)?; let flags = ignite_rs::protocol::read_u16(reader)?; 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)?; 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)?; ignite_rs::protocol::read_i32(reader)?; ignite_rs::protocol::read_i32(reader)?; ignite_rs::protocol::read_i32(reader)?; #( #fields_read)*
138
139 for _ in 0..#fields_count {
141 ignite_rs::protocol::read_i64(reader)?; }
143
144 Some(
145 #type_name{
146 #(#field_pairs)*
147 }
148 )
149 }
150 };
151 Ok(value)
152 }
153 }
154 }
155}
156
157fn get_schema_id(fields: &FieldsNamed) -> i32 {
159 fields
160 .named
161 .iter()
162 .map(|field| field.ident.as_ref().unwrap()) .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
179fn get_type_id(ident: &Ident) -> i32 {
181 string_to_java_hashcode(&ident.to_string())
182}
183
184const FNV1_OFFSET_BASIS: i32 = 0x811C_9DC5_u32 as i32;
186const FNV1_PRIME: i32 = 0x0100_0193;
188
189fn 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}