use proc_macro2::TokenStream;
use quote::quote;
use sha2::{Digest, Sha256};
use syn::{parse::Parser, parse2, punctuated::Punctuated, ItemStruct, LitInt, LitStr, Meta, Token};
pub fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let input: ItemStruct = parse2(item)?;
let metas: Punctuated<Meta, Token![,]> = Punctuated::<Meta, Token![,]>::parse_terminated
.parse2(attr.clone())
.unwrap_or_default();
let mut tag: Option<u8> = None;
let mut name: Option<String> = None;
let mut segment_source: Option<u8> = None;
for m in &metas {
match m {
Meta::NameValue(nv) if nv.path.is_ident("tag") => {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(li),
..
}) = &nv.value
{
tag = Some(li.base10_parse::<u8>()?);
}
}
Meta::NameValue(nv) if nv.path.is_ident("name") => {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(ls),
..
}) = &nv.value
{
name = Some(ls.value());
}
}
Meta::NameValue(nv) if nv.path.is_ident("segment") => {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(li),
..
}) = &nv.value
{
segment_source = Some(li.base10_parse::<u8>()?);
}
}
_ => {}
}
}
let ident = &input.ident;
let ident_str = ident.to_string();
let event_name = name.unwrap_or_else(|| ident_str.clone());
let tag_byte = tag.unwrap_or_else(|| derive_tag(&event_name));
let name_lit = LitStr::new(&event_name, ident.span());
let tag_lit = LitInt::new(&tag_byte.to_string(), ident.span());
let segment_lit = segment_source.map(|s| LitInt::new(&s.to_string(), ident.span()));
let segment_expr = match segment_lit {
Some(l) => quote!(::core::option::Option::Some(#l)),
None => quote!(::core::option::Option::None),
};
let field_count = input.fields.len();
let field_count_lit = LitInt::new(&field_count.to_string(), ident.span());
let gen = quote! {
#input
#[allow(non_upper_case_globals)]
const _: () = {
};
impl #ident {
pub const EVENT_TAG: u8 = #tag_lit;
pub const EVENT_NAME: &'static str = #name_lit;
pub const SEGMENT_SOURCE: ::core::option::Option<u8> = #segment_expr;
pub const FIELD_COUNT: usize = #field_count_lit;
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
unsafe {
::core::slice::from_raw_parts(
self as *const Self as *const u8,
::core::mem::size_of::<Self>(),
)
}
}
}
};
let _ = attr;
Ok(gen)
}
fn derive_tag(name: &str) -> u8 {
let mut h = Sha256::new();
h.update(b"hopper:event:");
h.update(name.as_bytes());
let digest = h.finalize();
let mut i = 0;
while i < digest.len() {
if digest[i] != 0 {
return digest[i];
}
i += 1;
}
1
}