Skip to main content

serdavro_macros/
lib.rs

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}