use proc_macro2::TokenStream; use quote::quote; use syn::{DeriveInput, Error, LitInt, LitStr, Result}; pub struct MessageAttr { pub id: u16, pub version: u16, pub direction: TokenStream, pub name: String, } pub fn expand_meta(input: &DeriveInput) -> Result { let attr = parse_message_attr(input)?; let ident = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let id = attr.id; let version = attr.version; let direction = attr.direction; let name = attr.name; Ok(quote! { impl #impl_generics ::titan::message::MessageMeta for #ident #ty_generics #where_clause { const MESSAGE_TYPE: u16 = #id; const MESSAGE_VERSION: u16 = #version; const DIRECTION: ::titan::message::Direction = #direction; const NAME: &'static str = #name; } }) } fn parse_message_attr(input: &DeriveInput) -> Result { let attr = input .attrs .iter() .find(|attr| attr.path().is_ident("message")) .ok_or_else(|| { Error::new_spanned( &input.ident, "missing #[message(id = ..., direction = ...)] attribute", ) })?; let mut id: Option = None; let mut version: u16 = 0; let mut direction: Option = None; let mut name: Option = None; attr.parse_nested_meta(|meta| { if meta.path.is_ident("id") { let value: LitInt = meta.value()?.parse()?; id = Some(value.base10_parse()?); return Ok(()); } if meta.path.is_ident("version") { let value: LitInt = meta.value()?.parse()?; version = value.base10_parse()?; return Ok(()); } if meta.path.is_ident("name") { let value: LitStr = meta.value()?.parse()?; name = Some(value.value()); return Ok(()); } if meta.path.is_ident("direction") { let value: LitStr = meta.value()?.parse()?; direction = Some(match value.value().as_str() { "client" | "client_to_server" => { quote!(::titan::message::Direction::ClientToServer) } "server" | "server_to_client" => { quote!(::titan::message::Direction::ServerToClient) } "both" | "bidirectional" => quote!(::titan::message::Direction::Bidirectional), other => { return Err(Error::new_spanned( &value, format!("unknown direction `{other}`, expected client, server or both"), )) } }); return Ok(()); } Err(meta.error("unsupported #[message(...)] key")) })?; let id = id.ok_or_else(|| Error::new_spanned(attr, "#[message(...)] requires `id`"))?; let direction = direction .ok_or_else(|| Error::new_spanned(attr, "#[message(...)] requires `direction`"))?; let name = name.unwrap_or_else(|| input.ident.to_string()); Ok(MessageAttr { id, version, direction, name, }) } pub fn expand_registration(input: &DeriveInput) -> TokenStream { if !input.generics.params.is_empty() { return TokenStream::new(); } let ident = &input.ident; quote! { ::titan::inventory::submit! { ::titan::factory::RegistryEntry::of::<#ident>() } } }