use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Fields, ItemStruct, parse_macro_input, spanned::Spanned};
#[proc_macro_attribute]
pub fn serdavro(
_: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as ItemStruct);
serdavro_impl(&item)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn serdavro_impl(item: &ItemStruct) -> syn::Result<TokenStream> {
let ident = &item.ident;
let fake_ident = format_ident!("__SERDAVRO__{ident}__");
let mut fake = item.clone();
fake.ident = fake_ident.clone();
let fields = match &item.fields {
Fields::Named(fields) => fields
.named
.iter()
.filter_map(|field| {
field
.attrs
.iter()
.any(|attr| {
let mut flatten = false;
if attr.path().is_ident("serde") {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("flatten") {
flatten = true
}
Ok(())
});
}
flatten
})
.then_some(field.ident.clone().unwrap().to_string())
})
.collect::<Vec<_>>(),
_ => {
return Err(syn::Error::new(
item.fields.span(),
"Only named fields are supported",
));
}
};
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
Ok(quote! {
const _: () = {
use ::std::collections::HashMap;
use ::apache_avro::{
AvroSchema,
schema::{Name, RecordField, RecordSchema, Schema, derive::AvroSchemaComponent},
};
#[derive(AvroSchema)]
#fake
#[automatically_derived]
impl #impl_generics AvroSchemaComponent for #ident #ty_generics #where_clause {
fn get_schema_in_ctxt(named_schemas: &mut HashMap<Name, Schema>, enclosing_namespace: &Option<String>) -> Schema {
let mut fake_schema = #fake_ident::get_schema_in_ctxt(named_schemas, enclosing_namespace);
match fake_schema {
Schema::Record(fake) => {
let mut record = fake.clone();
record.name = Name::new(stringify!(#ident)).expect("Unable to parse schema name");
record.lookup.clear();
record.fields.clear();
fn add(record: &mut RecordSchema, mut field: RecordField, index: &mut usize) {
record.lookup.insert(field.name.clone(), *index);
field.position = *index;
record.fields.push(field);
*index += 1;
};
let mut index = 0;
for field in fake.fields {
if [#(#fields,)*].contains(&field.name.as_str()) {
match field.schema {
Schema::Record(r) => {
for field in r.fields {
add(&mut record, field, &mut index);
}
}
_ => panic!("Only record schemas can be flattened")
}
} else {
add(&mut record, field, &mut index);
}
}
Schema::Record(record)
}
_ => panic!("Only record schemas are supported")
}
}
}
};
#item
})
}