1extern crate proc_macro;
2use self::proc_macro::TokenStream;
3
4use quote::quote;
5use syn::{parse_macro_input, parse_quote, DeriveInput, Fields, GenericParam, Generics};
6
7fn add_trait_bounds(mut generics: Generics) -> Generics {
8 for param in &mut generics.params {
9 if let GenericParam::Type(ref mut type_param) = *param {
10 type_param.bounds.push(parse_quote!(FirebaseMapValue));
11 }
12 }
13 generics
14}
15
16fn error(span: proc_macro2::Span, message: &str) -> proc_macro2::TokenStream {
17 syn::Error::new(span, message).into_compile_error()
18}
19
20#[proc_macro_derive(AsFirebaseMap)]
21pub fn impl_as_firebase_map(input: TokenStream) -> TokenStream {
22 let ast = parse_macro_input!(input as DeriveInput);
23 let span = proc_macro2::Span::call_site();
24
25 let name = &ast.ident;
26 let generics = add_trait_bounds(ast.generics);
27 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
28
29 let data = match ast.data {
30 syn::Data::Struct(data) => data,
31 _ => return error(span, "AsFirebaseMap should be called on a struct").into(),
32 };
33
34 let fields = match data.fields {
35 Fields::Named(fields) => fields.named,
36 _ => return error(span, "AsFirebaseMap only works on named fields").into(),
37 };
38
39 let inserts = fields.into_iter().map(|f| {
40 let name = f.ident.unwrap();
41
42 quote! {
43 h.insert(stringify!(#name), &self.#name);
44 }
45 });
46
47 let mod_name = syn::Ident::new(
48 &format!("__impl_as_firebase_map_{}", name),
49 proc_macro2::Span::call_site(),
50 );
51
52 TokenStream::from(quote! {
53 mod #mod_name {
54 use firebae_cm::{
55 IntoFirebaseMap,
56 FirebaseMap,
57 FirebaseMapValue,
58 };
59 use super::{#name};
60
61 impl #impl_generics IntoFirebaseMap for #name #ty_generics #where_clause {
62 fn as_map(&self) -> FirebaseMap {
63 let mut h = FirebaseMap::new();
64 #(#inserts)*
65 h
66 }
67 }
68 }
69 })
70}