valence_protocol_macros/
packet.rs

1use heck::ToShoutySnakeCase;
2use proc_macro2::{Ident, Span, TokenStream};
3use quote::quote;
4use syn::spanned::Spanned;
5use syn::{parse2, parse_quote, Attribute, DeriveInput, Error, Expr, LitInt, LitStr, Result};
6
7use crate::add_trait_bounds;
8
9pub(super) fn derive_packet(item: TokenStream) -> Result<TokenStream> {
10    let mut input = parse2::<DeriveInput>(item)?;
11
12    let packet_attr = parse_packet_helper_attr(&input.attrs)?.unwrap_or_default();
13
14    let name = input.ident.clone();
15
16    let name_str = if let Some(attr_name) = packet_attr.name {
17        attr_name.value()
18    } else {
19        name.to_string()
20    };
21
22    let packet_id: Expr = match packet_attr.id {
23        Some(expr) => expr,
24        None => match syn::parse_str::<Ident>(&name_str.to_shouty_snake_case()) {
25            Ok(ident) => parse_quote!(::valence_protocol::packet_id::#ident),
26            Err(_) => {
27                return Err(Error::new(
28                    packet_attr.span,
29                    "missing valid `id = ...` value from `packet` attr",
30                ))
31            }
32        },
33    };
34
35    add_trait_bounds(&mut input.generics, quote!(::std::fmt::Debug));
36
37    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
38
39    let side = if let Some(side_attr) = packet_attr.side {
40        side_attr
41    } else if name_str.to_lowercase().ends_with("s2c") {
42        parse_quote!(::valence_protocol::PacketSide::Clientbound)
43    } else if name_str.to_lowercase().ends_with("c2s") {
44        parse_quote!(::valence_protocol::PacketSide::Serverbound)
45    } else {
46        return Err(Error::new(
47            packet_attr.span,
48            "missing `side = PacketSide::...` value from `packet` attribute",
49        ));
50    };
51
52    let state = packet_attr
53        .state
54        .unwrap_or_else(|| parse_quote!(::valence_protocol::PacketState::Play));
55
56    Ok(quote! {
57        impl #impl_generics ::valence_protocol::__private::Packet for #name #ty_generics
58        #where_clause
59        {
60            const ID: i32 = #packet_id;
61            const NAME: &'static str = #name_str;
62            const SIDE: ::valence_protocol::PacketSide = #side;
63            const STATE: ::valence_protocol::PacketState = #state;
64        }
65    })
66}
67
68struct PacketAttr {
69    span: Span,
70    id: Option<Expr>,
71    tag: Option<i32>,
72    name: Option<LitStr>,
73    side: Option<Expr>,
74    state: Option<Expr>,
75}
76
77impl Default for PacketAttr {
78    fn default() -> Self {
79        Self {
80            span: Span::call_site(),
81            id: Default::default(),
82            tag: Default::default(),
83            name: Default::default(),
84            side: Default::default(),
85            state: Default::default(),
86        }
87    }
88}
89
90fn parse_packet_helper_attr(attrs: &[Attribute]) -> Result<Option<PacketAttr>> {
91    for attr in attrs {
92        if attr.path().is_ident("packet") {
93            let mut res = PacketAttr {
94                span: attr.span(),
95                id: None,
96                tag: None,
97                name: None,
98                side: None,
99                state: None,
100            };
101
102            attr.parse_nested_meta(|meta| {
103                if meta.path.is_ident("id") {
104                    res.id = Some(meta.value()?.parse::<Expr>()?);
105                    Ok(())
106                } else if meta.path.is_ident("tag") {
107                    res.tag = Some(meta.value()?.parse::<LitInt>()?.base10_parse::<i32>()?);
108                    Ok(())
109                } else if meta.path.is_ident("name") {
110                    res.name = Some(meta.value()?.parse::<LitStr>()?);
111                    Ok(())
112                } else if meta.path.is_ident("side") {
113                    res.side = Some(meta.value()?.parse::<Expr>()?);
114                    Ok(())
115                } else if meta.path.is_ident("state") {
116                    res.state = Some(meta.value()?.parse::<Expr>()?);
117                    Ok(())
118                } else {
119                    Err(meta.error("unrecognized packet argument"))
120                }
121            })?;
122
123            return Ok(Some(res));
124        }
125    }
126
127    Ok(None)
128}