scroll.server/crates/titan-derive/src/meta.rs
WiseDev d25a6de423 move all crates into crates/, glob members
pure move, no code touched. readme and protocol.md paths fixed up.
2026-08-23 08:15:45 +03:00

100 lines
3.5 KiB
Rust

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<TokenStream> {
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<MessageAttr> {
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<u16> = None;
let mut version: u16 = 0;
let mut direction: Option<TokenStream> = None;
let mut name: Option<String> = 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>()
}
}
}