A better Rust ATProto crate
at main 76 lines 3.3 kB view raw
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}