1use proc_macro::TokenStream;
67use proc_macro2::TokenStream as TokenStream2;
68use quote::quote;
69use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, LitStr, Type};
70
71#[derive(Clone, Copy, PartialEq, Eq)]
73enum Role {
74 Key,
75 SpanId,
76 ParentSpanId,
77}
78
79impl Role {
80 fn tokens(self) -> TokenStream2 {
82 match self {
83 Role::Key => quote! { ::backbeat::schema::FieldRole::Key },
84 Role::SpanId => quote! { ::backbeat::schema::FieldRole::SpanId },
85 Role::ParentSpanId => quote! { ::backbeat::schema::FieldRole::ParentSpanId },
86 }
87 }
88}
89
90#[derive(Clone, Copy, PartialEq, Eq)]
92enum Phase {
93 None,
94 Enter,
95 Exit,
96}
97
98impl Phase {
99 fn tokens(self) -> TokenStream2 {
100 match self {
101 Phase::None => quote! { ::backbeat::schema::Phase::None },
102 Phase::Enter => quote! { ::backbeat::schema::Phase::Enter },
103 Phase::Exit => quote! { ::backbeat::schema::Phase::Exit },
104 }
105 }
106}
107
108#[proc_macro_derive(Event, attributes(event))]
111pub fn derive_event(input: TokenStream) -> TokenStream {
112 let input = parse_macro_input!(input as DeriveInput);
113 match expand(input) {
114 Ok(ts) => ts.into(),
115 Err(e) => e.to_compile_error().into(),
116 }
117}
118
119#[proc_macro_derive(EventEnum)]
122pub fn derive_event_enum(input: TokenStream) -> TokenStream {
123 let input = parse_macro_input!(input as DeriveInput);
124 match expand_event_enum(input) {
125 Ok(ts) => ts.into(),
126 Err(e) => e.to_compile_error().into(),
127 }
128}
129
130fn expand_event_enum(input: DeriveInput) -> syn::Result<TokenStream2> {
131 let name = &input.ident;
132
133 let data = match &input.data {
134 Data::Enum(e) => e,
135 _ => {
136 return Err(syn::Error::new_spanned(
137 &input,
138 "`#[derive(EventEnum)]` can only be applied to an enum",
139 ))
140 }
141 };
142
143 let repr = enum_repr_width(&input)?;
146
147 let mut labels = Vec::with_capacity(data.variants.len());
152 for variant in &data.variants {
153 if !matches!(variant.fields, Fields::Unit) {
154 return Err(syn::Error::new_spanned(
155 variant,
156 "`#[derive(EventEnum)]` requires fieldless variants",
157 ));
158 }
159 let label = variant.ident.to_string();
160 let value =
161 match &variant.discriminant {
162 Some((_, expr)) => expr.clone(),
163 None => return Err(syn::Error::new_spanned(
164 variant,
165 "`#[derive(EventEnum)]` requires an explicit discriminant, e.g. `Variant = 0` \
166 (the value is part of the event's on-disk identity)",
167 )),
168 };
169 labels.push(quote! {
170 ::backbeat::schema::EnumLabel { value: (#value) as u64, label: #label }
171 });
172 }
173
174 Ok(quote! {
175 impl ::backbeat::EventEnum for #name {
176 const REPR: u8 = #repr;
177 const LABELS: &'static [::backbeat::schema::EnumLabel] = &[ #(#labels),* ];
178 }
179 })
180}
181
182fn enum_repr_width(input: &DeriveInput) -> syn::Result<u8> {
184 for attr in &input.attrs {
185 if !attr.path().is_ident("repr") {
186 continue;
187 }
188 let mut width = None;
189 attr.parse_nested_meta(|meta| {
190 width = meta
191 .path
192 .get_ident()
193 .and_then(|i| match i.to_string().as_str() {
194 "u8" => Some(1),
195 "u16" => Some(2),
196 "u32" => Some(4),
197 "u64" => Some(8),
198 _ => None,
199 });
200 Ok(())
201 })?;
202 if let Some(w) = width {
203 return Ok(w);
204 }
205 }
206 Err(syn::Error::new_spanned(
207 input,
208 "`#[derive(EventEnum)]` requires an explicit `#[repr(u8|u16|u32|u64)]`",
209 ))
210}
211
212fn expand(input: DeriveInput) -> syn::Result<TokenStream2> {
213 let name = &input.ident;
214
215 let container = ContainerAttrs::parse(&input)?;
217
218 let fields =
220 match &input.data {
221 Data::Struct(s) => match &s.fields {
222 Fields::Named(named) => named.named.iter().collect::<Vec<_>>(),
223 Fields::Unit => Vec::new(),
225 Fields::Unnamed(_) => return Err(syn::Error::new_spanned(
226 &s.fields,
227 "`#[derive(Event)]` requires named fields (tuple structs have no field names \
228 to use as column names)",
229 )),
230 },
231 _ => {
232 return Err(syn::Error::new_spanned(
233 &input,
234 "`#[derive(Event)]` can only be applied to a struct",
235 ))
236 }
237 };
238
239 let description = doc_of(&input.attrs);
240
241 let mut field_defs = Vec::with_capacity(fields.len());
242 let mut span_id_fields = 0usize;
244 let mut parent_span_fields = 0usize;
245 for field in &fields {
246 let ident = field.ident.as_ref().expect("named field");
247 let fname = ident.to_string();
248 let fdesc = doc_of(&field.attrs);
249 let attrs = FieldAttrs::parse(&field.attrs)?;
250
251 if matches!(attrs.role, Some(Role::SpanId | Role::ParentSpanId)) {
255 require_u64(&field.ty, attrs.role.unwrap())?;
256 }
257 match attrs.role {
258 Some(Role::SpanId) => span_id_fields += 1,
259 Some(Role::ParentSpanId) => parent_span_fields += 1,
260 _ => {}
261 }
262
263 let fty = &field.ty;
264
265 let (ty_expr, labels_expr) = if let Some(dynamic) = attrs.interned {
271 (
272 quote! { ::backbeat::schema::FieldType::Interned { dynamic: #dynamic } },
273 quote! { &[] },
274 )
275 } else {
276 (
277 quote! { <#fty as ::backbeat::FieldTy>::FIELD_TYPE },
278 quote! { <#fty as ::backbeat::FieldTy>::LABELS },
279 )
280 };
281
282 let desc_expr = opt_str(fdesc.as_deref());
283 let unit_expr = opt_str(attrs.unit.as_deref());
284 let role_expr = match attrs.role {
285 Some(r) => r.tokens(),
286 None => quote! { ::backbeat::schema::FieldRole::None },
287 };
288
289 field_defs.push(quote! {
290 ::backbeat::schema::FieldSchema {
291 name: #fname,
292 description: #desc_expr,
293 ty: #ty_expr,
294 offset: ::backbeat::schema::layout_u16(::core::mem::offset_of!(#name, #ident)),
295 width: ::backbeat::schema::layout_u16(::core::mem::size_of::<#fty>()),
296 role: #role_expr,
297 unit: #unit_expr,
298 enum_labels: #labels_expr,
299 }
300 });
301 }
302
303 if span_id_fields > 1 {
305 return Err(syn::Error::new_spanned(
306 &input,
307 "an event may declare at most one `#[event(span_id)]` field",
308 ));
309 }
310 if parent_span_fields > 1 {
311 return Err(syn::Error::new_spanned(
312 &input,
313 "an event may declare at most one `#[event(parent_span_id)]` field",
314 ));
315 }
316 match container.phase {
317 Phase::Enter | Phase::Exit if span_id_fields == 0 => {
319 return Err(syn::Error::new_spanned(
320 &input,
321 "`#[event(span = enter|exit)]` requires exactly one `#[event(span_id)]` field",
322 ));
323 }
324 Phase::None if span_id_fields > 0 => {
327 return Err(syn::Error::new_spanned(
328 &input,
329 "`#[event(span_id)]` requires the event to be a span \
330 (`#[event(span = enter)]` or `#[event(span = exit)]`)",
331 ));
332 }
333 _ => {}
334 }
335
336 Ok(emit(name, &container, description, field_defs))
337}
338
339fn emit(
341 name: &syn::Ident,
342 container: &ContainerAttrs,
343 description: Option<String>,
344 field_defs: Vec<TokenStream2>,
345) -> TokenStream2 {
346 let qualified = format!("{}::{name}", container.namespace);
347 let desc_expr = opt_str(description.as_deref());
348 let phase_expr = container.phase.tokens();
349
350 quote! {
351 impl #name {
352 pub const QUALIFIED_NAME: &'static str = #qualified;
354
355 const FIELDS: &'static [::backbeat::schema::FieldSchema] = &[ #(#field_defs),* ];
358
359 pub const ID: ::backbeat::id::EventId =
363 ::backbeat::schema::EventSchema::compute_id(
364 Self::QUALIFIED_NAME,
365 #phase_expr,
366 Self::FIELDS,
367 );
368
369 pub const SCHEMA: ::backbeat::schema::EventSchema =
371 ::backbeat::schema::EventSchema {
372 id: Self::ID,
373 qualified_name: Self::QUALIFIED_NAME,
374 description: #desc_expr,
375 record_size: ::backbeat::schema::layout_u16(::core::mem::size_of::<#name>()),
376 phase: #phase_expr,
377 fields: Self::FIELDS,
378 };
379 }
380
381 impl ::backbeat::Event for #name {
382 const SCHEMA: ::backbeat::schema::EventSchema = Self::SCHEMA;
383 const ID: ::backbeat::id::EventId = Self::ID;
384 const QUALIFIED_NAME: &'static str = Self::QUALIFIED_NAME;
385 }
386
387 ::backbeat::register_event!(#name);
390 }
391}
392
393#[derive(Default)]
395struct FieldAttrs {
396 role: Option<Role>,
399 unit: Option<String>,
400 interned: Option<bool>,
402}
403
404impl FieldAttrs {
405 fn parse(attrs: &[syn::Attribute]) -> syn::Result<Self> {
406 let mut out = FieldAttrs::default();
407 for attr in attrs {
408 if !attr.path().is_ident("event") {
409 continue;
410 }
411 attr.parse_nested_meta(|meta| {
412 if meta.path.is_ident("key") {
413 out.set_role(Role::Key, &meta)?;
414 Ok(())
415 } else if meta.path.is_ident("span_id") {
416 out.set_role(Role::SpanId, &meta)?;
417 Ok(())
418 } else if meta.path.is_ident("parent_span_id") {
419 out.set_role(Role::ParentSpanId, &meta)?;
420 Ok(())
421 } else if meta.path.is_ident("unit") {
422 let lit: LitStr = meta.value()?.parse()?;
423 out.unit = Some(lit.value());
424 Ok(())
425 } else if meta.path.is_ident("interned") {
426 let mut dynamic = false;
428 if meta.input.peek(syn::token::Paren) {
429 meta.parse_nested_meta(|inner| {
430 if inner.path.is_ident("dynamic") {
431 dynamic = true;
432 Ok(())
433 } else {
434 Err(inner.error("expected `dynamic`"))
435 }
436 })?;
437 }
438 out.interned = Some(dynamic);
439 Ok(())
440 } else {
441 Err(meta.error("unknown `#[event(...)]` field attribute"))
442 }
443 })?;
444 }
445 Ok(out)
446 }
447
448 fn set_role(&mut self, role: Role, meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<()> {
450 if self.role.is_some() {
451 return Err(meta.error(
452 "a field may have at most one role \
453 (`key` / `span_id` / `parent_span_id` are mutually exclusive)",
454 ));
455 }
456 self.role = Some(role);
457 Ok(())
458 }
459}
460
461struct ContainerAttrs {
464 namespace: String,
465 phase: Phase,
466}
467
468impl ContainerAttrs {
469 fn parse(input: &DeriveInput) -> syn::Result<Self> {
470 let mut namespace = None;
471 let mut phase = Phase::None;
472 for attr in &input.attrs {
473 if !attr.path().is_ident("event") {
474 continue;
475 }
476 attr.parse_nested_meta(|meta| {
477 if meta.path.is_ident("namespace") {
478 let lit: LitStr = meta.value()?.parse()?;
479 namespace = Some(lit.value());
480 Ok(())
481 } else if meta.path.is_ident("span") {
482 let ident: syn::Ident = meta.value()?.parse()?;
484 phase = match ident.to_string().as_str() {
485 "enter" => Phase::Enter,
486 "exit" => Phase::Exit,
487 _ => {
488 return Err(meta.error("`span` must be `enter` or `exit`"));
489 }
490 };
491 Ok(())
492 } else {
493 Err(meta.error("unknown container-level `#[event(...)]` attribute"))
494 }
495 })?;
496 }
497 let namespace = namespace.ok_or_else(|| {
498 syn::Error::new_spanned(
499 input,
500 "`#[derive(Event)]` requires `#[event(namespace = \"...\")]`",
501 )
502 })?;
503 Ok(Self { namespace, phase })
504 }
505}
506
507fn require_u64(ty: &Type, role: Role) -> syn::Result<()> {
509 let is_u64 = matches!(ty, Type::Path(p) if p.path.is_ident("u64"));
510 if is_u64 {
511 Ok(())
512 } else {
513 let attr = match role {
514 Role::SpanId => "span_id",
515 Role::ParentSpanId => "parent_span_id",
516 Role::Key => unreachable!("require_u64 is only called for span roles"),
517 };
518 Err(syn::Error::new_spanned(
519 ty,
520 format!(
521 "`#[event({attr})]` fields must be `u64` (span ids are compared for equality \
522 across the enter/exit events, so they need a uniform width)"
523 ),
524 ))
525 }
526}
527
528fn doc_of(attrs: &[syn::Attribute]) -> Option<String> {
531 let mut lines = Vec::new();
532 for attr in attrs {
533 if !attr.path().is_ident("doc") {
534 continue;
535 }
536 if let syn::Meta::NameValue(nv) = &attr.meta {
537 if let Expr::Lit(ExprLit {
538 lit: Lit::Str(s), ..
539 }) = &nv.value
540 {
541 lines.push(s.value().trim().to_string());
542 }
543 }
544 }
545 if lines.is_empty() {
546 None
547 } else {
548 Some(lines.join("\n"))
549 }
550}
551
552fn opt_str(s: Option<&str>) -> TokenStream2 {
554 match s {
555 Some(s) => quote! { ::core::option::Option::Some(#s) },
556 None => quote! { ::core::option::Option::None },
557 }
558}