reth_codecs_derive/compact/
generator.rsuse super::*;
use convert_case::{Case, Casing};
use syn::{Attribute, LitStr};
pub fn generate_from_to(
ident: &Ident,
attrs: &[Attribute],
has_lifetime: bool,
fields: &FieldList,
is_zstd: bool,
) -> TokenStream2 {
let flags = format_ident!("{ident}Flags");
let to_compact = generate_to_compact(fields, ident, is_zstd);
let from_compact = generate_from_compact(fields, ident, is_zstd);
let snake_case_ident = ident.to_string().to_case(Case::Snake);
let fuzz = format_ident!("fuzz_test_{snake_case_ident}");
let test = format_ident!("fuzz_{snake_case_ident}");
let reth_codecs = parse_reth_codecs_path(attrs).unwrap();
let lifetime = if has_lifetime {
quote! { 'a }
} else {
quote! {}
};
let impl_compact = if has_lifetime {
quote! {
impl<#lifetime> #reth_codecs::Compact for #ident<#lifetime>
}
} else {
quote! {
impl #reth_codecs::Compact for #ident
}
};
let fn_from_compact = if has_lifetime {
quote! { unimplemented!("from_compact not supported with ref structs") }
} else {
quote! {
let (flags, mut buf) = #flags::from(buf);
#from_compact
}
};
let fuzz_tests = if has_lifetime {
quote! {}
} else {
quote! {
#[cfg(test)]
#[allow(dead_code)]
#[test_fuzz::test_fuzz]
fn #fuzz(obj: #ident) {
use #reth_codecs::Compact;
let mut buf = vec![];
let len = obj.clone().to_compact(&mut buf);
let (same_obj, buf) = #ident::from_compact(buf.as_ref(), len);
assert_eq!(obj, same_obj);
}
#[test]
#[allow(missing_docs)]
pub fn #test() {
#fuzz(#ident::default())
}
}
};
quote! {
#fuzz_tests
#impl_compact {
fn to_compact<B>(&self, buf: &mut B) -> usize where B: bytes::BufMut + AsMut<[u8]> {
let mut flags = #flags::default();
let mut total_length = 0;
#(#to_compact)*
total_length
}
fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
#fn_from_compact
}
}
}
}
fn generate_from_compact(fields: &FieldList, ident: &Ident, is_zstd: bool) -> TokenStream2 {
let mut lines = vec![];
let mut known_types =
vec!["B256", "Address", "Bloom", "Vec", "TxHash", "BlockHash", "FixedBytes"];
known_types.extend_from_slice(&["TxKind", "AccessList", "Signature", "CheckpointBlockRange"]);
let is_enum = fields.iter().any(|field| matches!(field, FieldTypes::EnumVariant(_)));
if is_enum {
let enum_lines = EnumHandler::new(fields).generate_from(ident);
lines.push(quote! {
let obj = match flags.variant() {
#(#enum_lines)*
_ => unreachable!()
};
});
} else {
let mut struct_handler = StructHandler::new(fields);
lines.append(&mut struct_handler.generate_from(known_types.as_slice()));
if struct_handler.is_wrapper {
lines.push(quote! {
let obj = #ident(placeholder);
});
} else {
let fields = fields.iter().filter_map(|field| {
if let FieldTypes::StructField((name, _, _, _)) = field {
let ident = format_ident!("{name}");
return Some(quote! {
#ident: #ident,
})
}
None
});
lines.push(quote! {
let obj = #ident {
#(#fields)*
};
});
}
}
is_zstd
.then(|| {
let decompressor = format_ident!("{}_DECOMPRESSOR", ident.to_string().to_uppercase());
quote! {
if flags.__zstd() != 0 {
#decompressor.with(|decompressor| {
let decompressor = &mut decompressor.borrow_mut();
let decompressed = decompressor.decompress(buf);
let mut original_buf = buf;
let mut buf: &[u8] = decompressed;
#(#lines)*
(obj, original_buf)
})
} else {
#(#lines)*
(obj, buf)
}
}
})
.unwrap_or_else(|| {
quote! {
#(#lines)*
(obj, buf)
}
})
}
fn generate_to_compact(fields: &FieldList, ident: &Ident, is_zstd: bool) -> Vec<TokenStream2> {
let mut lines = vec![quote! {
let mut buffer = bytes::BytesMut::new();
}];
let is_enum = fields.iter().any(|field| matches!(field, FieldTypes::EnumVariant(_)));
if is_enum {
let enum_lines = EnumHandler::new(fields).generate_to(ident);
lines.push(quote! {
flags.set_variant(match self {
#(#enum_lines)*
});
})
} else {
lines.append(&mut StructHandler::new(fields).generate_to());
}
if is_zstd {
lines.push(quote! {
let mut zstd = buffer.len() > 7;
if zstd {
flags.set___zstd(1);
}
});
}
lines.push(quote! {
let flags = flags.into_bytes();
total_length += flags.len() + buffer.len();
buf.put_slice(&flags);
});
if is_zstd {
let compressor = format_ident!("{}_COMPRESSOR", ident.to_string().to_uppercase());
lines.push(quote! {
if zstd {
#compressor.with(|compressor| {
let mut compressor = compressor.borrow_mut();
let compressed = compressor.compress(&buffer).expect("Failed to compress.");
buf.put(compressed.as_slice());
});
} else {
buf.put(buffer);
}
});
} else {
lines.push(quote! {
buf.put(buffer);
})
}
lines
}
fn parse_reth_codecs_path(attrs: &[Attribute]) -> syn::Result<syn::Path> {
let mut reth_codecs_path: syn::Path = syn::parse_quote!(reth_codecs);
for attr in attrs {
if attr.path().is_ident("reth_codecs") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
let value = meta.value()?;
let lit: LitStr = value.parse()?;
reth_codecs_path = syn::parse_str(&lit.value())?;
Ok(())
} else {
Err(meta.error("unsupported attribute"))
}
})?;
}
}
Ok(reth_codecs_path)
}