buffa_reflect_derive/
lib.rs1use proc_macro::TokenStream;
22use proc_macro2::TokenStream as TokenStream2;
23use quote::quote;
24use syn::{Attribute, DeriveInput, Error, Expr, ExprLit, Lit, LitStr, parse_macro_input};
25
26#[proc_macro_derive(ReflectMessage, attributes(buffa_reflect))]
30pub fn derive_reflect_message(input: TokenStream) -> TokenStream {
31 let input = parse_macro_input!(input as DeriveInput);
32 expand(&input)
33 .unwrap_or_else(Error::into_compile_error)
34 .into()
35}
36
37fn expand(input: &DeriveInput) -> Result<TokenStream2, Error> {
38 let parsed = parse_attributes(&input.attrs)?;
39 let ident = &input.ident;
40 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
41
42 let message_name = parsed.message_name.ok_or_else(|| {
43 Error::new_spanned(
44 input,
45 "missing `#[buffa_reflect(message_name = \"...\")]` attribute",
46 )
47 })?;
48 let message_name_lit = LitStr::new(&message_name.value(), message_name.span());
49
50 let body = match (parsed.pool_expr, parsed.bytes_expr) {
51 (None, None) => {
52 return Err(Error::new_spanned(
53 input,
54 "missing `#[buffa_reflect(...)]` binding: provide either `descriptor_pool = \
55 \"...\"` or `file_descriptor_set_bytes = \"...\"`",
56 ));
57 }
58 (Some(_), Some(_)) => {
59 return Err(Error::new_spanned(
60 input,
61 "conflicting `#[buffa_reflect(...)]` bindings: provide exactly one of \
62 `descriptor_pool` / `file_descriptor_set_bytes`",
63 ));
64 }
65 (Some(pool), None) => {
66 let pool_expr: Expr = pool.parse()?;
67 quote! {
68 let __pool: &::buffa_reflect::DescriptorPool = &#pool_expr;
69 __pool
70 .get_message_by_name(#message_name_lit)
71 .expect(concat!(
72 "buffa-reflect: descriptor for `",
73 #message_name_lit,
74 "` not found",
75 ))
76 }
77 }
78 (None, Some(bytes)) => {
79 let bytes_expr: Expr = bytes.parse()?;
80 quote! {
81 static __INIT: ::std::sync::OnceLock<::buffa_reflect::DescriptorPool> =
82 ::std::sync::OnceLock::new();
83 let __pool = __INIT.get_or_init(|| {
84 ::buffa_reflect::DescriptorPool::decode(#bytes_expr)
85 .expect("buffa-reflect: invalid FileDescriptorSet")
86 });
87 __pool
88 .get_message_by_name(#message_name_lit)
89 .expect(concat!(
90 "buffa-reflect: descriptor for `",
91 #message_name_lit,
92 "` not found",
93 ))
94 }
95 }
96 };
97
98 Ok(quote! {
99 #[automatically_derived]
100 impl #impl_generics ::buffa_reflect::ReflectMessage for #ident #ty_generics #where_clause {
101 fn descriptor(&self) -> ::buffa_reflect::MessageDescriptor {
102 #body
103 }
104 }
105 })
106}
107
108#[derive(Default)]
109struct ParsedAttrs {
110 message_name: Option<LitStr>,
111 pool_expr: Option<LitStr>,
112 bytes_expr: Option<LitStr>,
113}
114
115fn parse_attributes(attrs: &[Attribute]) -> Result<ParsedAttrs, Error> {
116 let mut out = ParsedAttrs::default();
117 let mut all_names: Vec<LitStr> = Vec::new();
122 for attr in attrs {
123 if !attr.path().is_ident("buffa_reflect") {
124 continue;
125 }
126 attr.parse_nested_meta(|meta| {
127 let key = meta
128 .path
129 .get_ident()
130 .ok_or_else(|| meta.error("expected a key like `message_name = \"...\"`"))?
131 .to_string();
132 let value: Expr = meta.value()?.parse()?;
133 let lit = match value {
134 Expr::Lit(ExprLit {
135 lit: Lit::Str(s), ..
136 }) => s,
137 other => {
138 return Err(Error::new_spanned(other, "expected a string literal value"));
139 }
140 };
141 match key.as_str() {
142 "message_name" => all_names.push(lit),
143 "descriptor_pool" => set_once(&mut out.pool_expr, lit, "descriptor_pool")?,
144 "file_descriptor_set_bytes" => {
145 set_once(&mut out.bytes_expr, lit, "file_descriptor_set_bytes")?;
146 }
147 other => {
148 return Err(meta.error(format!(
149 "unknown `buffa_reflect` key `{other}` (expected `message_name`, \
150 `descriptor_pool`, or `file_descriptor_set_bytes`)"
151 )));
152 }
153 }
154 Ok(())
155 })?;
156 }
157 out.message_name = all_names.into_iter().max_by_key(|lit| lit.value().len());
158 Ok(out)
159}
160
161fn set_once(slot: &mut Option<LitStr>, value: LitStr, key: &str) -> Result<(), Error> {
162 if slot.is_some() {
163 return Err(Error::new(
164 value.span(),
165 format!("duplicate `{key}` in `#[buffa_reflect(...)]`"),
166 ));
167 }
168 *slot = Some(value);
169 Ok(())
170}