grpc_build_derive/
lib.rs

1//! proc macro to generate an associate function for all pb message types
2//! returning the full name of the message including the package namespace.
3
4use proc_macro::TokenStream;
5use syn::{spanned::Spanned, DeriveInput, Expr, Lit, MetaNameValue};
6
7#[proc_macro_derive(NamedMessage, attributes(name))]
8pub fn fully_qualified_name(input: TokenStream) -> TokenStream {
9    let ast = syn::parse_macro_input!(input as DeriveInput);
10
11    match impl_fully_qualified_name(&ast) {
12        Ok(tokens) => tokens,
13        Err(err) => err.to_compile_error().into(),
14    }
15}
16
17fn impl_fully_qualified_name(ast: &syn::DeriveInput) -> syn::Result<TokenStream> {
18    // We only annotate structs
19    match &ast.data {
20        syn::Data::Struct(_) => (),
21        syn::Data::Enum(_) | syn::Data::Union(_) => return Ok(TokenStream::default()),
22    };
23
24    // search for #[name]
25    let mut name_attrs = ast.attrs.iter().filter(|attr| attr.path().is_ident("name"));
26
27    // Let's assume we only have one annotation
28    let meta = match name_attrs.next() {
29        Some(attr) => &attr.meta,
30        None => return Err(syn::Error::new(ast.span(), "missing #[name] attribute")),
31    };
32
33    // #[name = "pbname"] should map to a NameValue
34    //   path    Lit
35    let message_name = match meta {
36        syn::Meta::NameValue(MetaNameValue {
37            value:
38                Expr::Lit(syn::ExprLit {
39                    lit: Lit::Str(name),
40                    ..
41                }),
42            ..
43        }) => name,
44        syn::Meta::NameValue(MetaNameValue { value, .. }) => {
45            return Err(syn::Error::new(
46                value.span(),
47                "message name MUST be a string",
48            ))
49        }
50        meta => return Err(syn::Error::new(meta.span(), "missing #[name] attribute")),
51    };
52
53    let name = &ast.ident;
54
55    Ok(quote::quote! {
56        impl ::grpc_build_core::NamedMessage for #name {
57            const NAME: &'static ::core::primitive::str = #message_name;
58        }
59    }
60    .into())
61}