A better Rust ATProto crate
1//! Implementation of #[lexicon] attribute macro
2
3use proc_macro2::TokenStream;
4use quote::quote;
5use syn::{Data, DeriveInput, Fields, parse2};
6
7use super::helpers::{conflicts_with_builder_macro, has_derive_builder};
8
9/// Implementation for the lexicon attribute macro
10pub fn impl_lexicon(_attr: TokenStream, item: TokenStream) -> TokenStream {
11 let mut input = match parse2::<DeriveInput>(item) {
12 Ok(input) => input,
13 Err(e) => return e.to_compile_error(),
14 };
15
16 match &mut input.data {
17 Data::Struct(data_struct) => {
18 if let Fields::Named(fields) = &mut data_struct.fields {
19 // Check if extra_data field already exists
20 let has_extra_data = fields
21 .named
22 .iter()
23 .any(|f| f.ident.as_ref().map(|i| i == "extra_data").unwrap_or(false));
24
25 if !has_extra_data {
26 // Check if the struct derives bon::Builder and doesn't conflict with builder macro
27 let has_bon_builder = has_derive_builder(&input.attrs)
28 && !conflicts_with_builder_macro(&input.ident);
29
30 // Determine the lifetime parameter to use
31 let lifetime = if let Some(lt) = input.generics.lifetimes().next() {
32 quote! { #lt }
33 } else {
34 quote! { 'static }
35 };
36
37 // Add the extra_data field with serde(borrow) if there's a lifetime
38 let new_field: syn::Field = if has_bon_builder {
39 syn::parse_quote! {
40 #[serde(flatten)]
41 #[serde(borrow)]
42 #[serde(skip_serializing_if = "Option::is_none")]
43 #[serde(default)]
44 pub extra_data: ::core::option::Option<::alloc::collections::BTreeMap<
45 ::jacquard_common::smol_str::SmolStr,
46 ::jacquard_common::types::value::Data<#lifetime>
47 >>
48 }
49 } else {
50 syn::parse_quote! {
51 #[serde(flatten)]
52 #[serde(borrow)]
53 #[serde(skip_serializing_if = "Option::is_none")]
54 #[serde(default)]
55 pub extra_data: ::core::option::Option<::alloc::collections::BTreeMap<
56 ::jacquard_common::smol_str::SmolStr,
57 ::jacquard_common::types::value::Data<#lifetime>
58 >>
59 }
60 };
61 fields.named.push(new_field);
62 }
63 } else {
64 return syn::Error::new_spanned(
65 input,
66 "lexicon attribute can only be used on structs with named fields",
67 )
68 .to_compile_error();
69 }
70
71 quote! { #input }
72 }
73 _ => syn::Error::new_spanned(input, "lexicon attribute can only be used on structs")
74 .to_compile_error(),
75 }
76}