1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{Fields, ItemStruct, parse_macro_input, spanned::Spanned};
4
5#[proc_macro_attribute]
6pub fn serdavro(
7 _: proc_macro::TokenStream,
8 item: proc_macro::TokenStream,
9) -> proc_macro::TokenStream {
10 let item = parse_macro_input!(item as ItemStruct);
11 serdavro_impl(&item)
12 .unwrap_or_else(syn::Error::into_compile_error)
13 .into()
14}
15
16fn serdavro_impl(item: &ItemStruct) -> syn::Result<TokenStream> {
17 let ident = &item.ident;
18
19 let fake_ident = format_ident!("__SERDAVRO__{ident}__");
20 let mut fake = item.clone();
21 fake.ident = fake_ident.clone();
22
23 let fields = match &item.fields {
24 Fields::Named(fields) => fields
25 .named
26 .iter()
27 .filter_map(|field| {
28 field
29 .attrs
30 .iter()
31 .any(|attr| {
32 let mut flatten = false;
33 if attr.path().is_ident("serde") {
34 let _ = attr.parse_nested_meta(|meta| {
35 if meta.path.is_ident("flatten") {
36 flatten = true
37 }
38 Ok(())
39 });
40 }
41 flatten
42 })
43 .then_some(field.ident.clone().unwrap().to_string())
44 })
45 .collect::<Vec<_>>(),
46 _ => {
47 return Err(syn::Error::new(
48 item.fields.span(),
49 "Only named fields are supported",
50 ));
51 }
52 };
53
54 let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
55
56 Ok(quote! {
57 const _: () = {
58 use ::std::collections::HashMap;
59
60 use ::apache_avro::{
61 AvroSchema,
62 schema::{Name, RecordField, RecordSchema, Schema, derive::AvroSchemaComponent},
63 };
64
65 #[derive(AvroSchema)]
66 #fake
67
68 #[automatically_derived]
69 impl #impl_generics AvroSchemaComponent for #ident #ty_generics #where_clause {
70 fn get_schema_in_ctxt(named_schemas: &mut HashMap<Name, Schema>, enclosing_namespace: &Option<String>) -> Schema {
71 let mut fake_schema = #fake_ident::get_schema_in_ctxt(named_schemas, enclosing_namespace);
72 match fake_schema {
73 Schema::Record(fake) => {
74 let mut record = fake.clone();
75 record.name = Name::new(stringify!(#ident)).expect("Unable to parse schema name");
76 record.lookup.clear();
77 record.fields.clear();
78
79 fn add(record: &mut RecordSchema, mut field: RecordField, index: &mut usize) {
80 record.lookup.insert(field.name.clone(), *index);
81 field.position = *index;
82 record.fields.push(field);
83 *index += 1;
84 };
85
86 let mut index = 0;
87
88 for field in fake.fields {
89 if [#(#fields,)*].contains(&field.name.as_str()) {
90 match field.schema {
91 Schema::Record(r) => {
92 for field in r.fields {
93 add(&mut record, field, &mut index);
94 }
95 }
96 _ => panic!("Only record schemas can be flattened")
97 }
98 } else {
99 add(&mut record, field, &mut index);
100 }
101 }
102
103 Schema::Record(record)
104 }
105 _ => panic!("Only record schemas are supported")
106 }
107 }
108 }
109 };
110
111 #item
112 })
113}