Skip to main content

battler_wamprat_message_proc_macro/
lib.rs

1extern crate proc_macro;
2
3use itertools::Itertools;
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::quote;
7use syn::{
8    Error,
9    Field,
10    Ident,
11    Index,
12    ItemStruct,
13    Meta,
14    parse::{
15        Parse,
16        ParseStream,
17    },
18    parse_macro_input,
19};
20
21enum ApplicationMessageFieldType {
22    Arguments,
23    ArgumentsKeyword,
24}
25
26struct InputFieldAttrs {
27    field_type: ApplicationMessageFieldType,
28}
29
30fn parse_application_message_input_field_attrs(field: &Field) -> syn::Result<InputFieldAttrs> {
31    let call_site = Span::call_site();
32    let mut field_type = None;
33    for attr in &field.attrs {
34        if let Meta::Path(path) = &attr.meta {
35            if path.is_ident("arguments") {
36                field_type = Some(ApplicationMessageFieldType::Arguments);
37            } else if path.is_ident("arguments_keyword") {
38                field_type = Some(ApplicationMessageFieldType::ArgumentsKeyword);
39            }
40        }
41    }
42    let field_type = match field_type {
43        Some(field_type) => field_type,
44        None => {
45            return Err(Error::new(
46                call_site,
47                "field must be marked `arguments` or `arguments_keyword`",
48            ));
49        }
50    };
51    Ok(InputFieldAttrs { field_type })
52}
53
54struct InputField {
55    ident: Option<Ident>,
56    attrs: InputFieldAttrs,
57}
58
59struct Input {
60    ident: Ident,
61    fields: Vec<InputField>,
62}
63
64impl Parse for Input {
65    fn parse(input: ParseStream) -> syn::Result<Self> {
66        let call_site = Span::call_site();
67        let input = match ItemStruct::parse(input) {
68            Ok(item) => item,
69            Err(_) => return Err(Error::new(call_site, "input must be a struct")),
70        };
71        let ident = input.ident;
72        let fields = input
73            .fields
74            .into_iter()
75            .map(|field| {
76                let attrs = parse_application_message_input_field_attrs(&field)?;
77                Ok(InputField {
78                    ident: field.ident,
79                    attrs,
80                })
81            })
82            .collect::<Result<Vec<_>, Error>>()?;
83        Ok(Self { ident, fields })
84    }
85}
86
87/// Procedural macro for deriving `battler_wamp_values::WampApplicationMessage` for a struct.
88#[proc_macro_derive(WampApplicationMessage, attributes(arguments, arguments_keyword))]
89pub fn derive_wamp_application_message(input: TokenStream) -> TokenStream {
90    let input = parse_macro_input!(input as Input);
91    let call_site = Span::call_site();
92
93    let ident = input.ident;
94
95    let (field_serializers, field_deserializers, field_identifiers): (Vec<_>, Vec<_>, Vec<_>) = input.fields.iter().enumerate().map(|(i, field)| {
96        let accessor = match &field.ident {
97            Some(ident) => quote!(self.#ident),
98            None => { let i = Index::from(i); quote!(self.#i) },
99        };
100        let field_name = field.ident.clone().unwrap_or(Ident::new(&format!("field_{i}"), call_site));
101        let input_output_ident = match field.attrs.field_type {
102            ApplicationMessageFieldType::Arguments => quote!(arguments),
103            ApplicationMessageFieldType::ArgumentsKeyword => quote!(arguments_keyword),
104        };
105        let validate_serialize_output = match field.attrs.field_type {
106            ApplicationMessageFieldType::Arguments => quote! {
107                match val {
108                    battler_wamp_values::Value::List(val) => val,
109                    _ => return Err(battler_wamp_values::WampSerializeError::new(std::fmt::format(format_args!("arguments of {} did not produce a list", std::stringify!(#ident))))),
110                }
111            },
112            ApplicationMessageFieldType::ArgumentsKeyword => quote! {
113                match val {
114                    battler_wamp_values::Value::Dictionary(val) => val,
115                    _ => return Err(battler_wamp_values::WampSerializeError::new(std::fmt::format(format_args!("arguments of {} did not produce a list", std::stringify!(#ident))))),
116                }
117            },
118        };
119        (
120            quote! {
121                let #input_output_ident = match battler_wamp_values::WampSerialize::wamp_serialize(#accessor) {
122                    Ok(val) => #validate_serialize_output,
123                    Err(err) => return Err(err.annotate(std::fmt::format(format_args!("failed to serialize {} of {}", std::stringify!(#input_output_ident), std::stringify!(#ident))))),
124                };
125            },
126            quote! {
127                let #field_name = match battler_wamp_values::WampDeserialize::wamp_deserialize(#input_output_ident) {
128                    Ok(val) => val,
129                    Err(err) => return Err(err.annotate(std::fmt::format(format_args!("failed to deserialize {} of {}", std::stringify!(#input_output_ident), std::stringify!(#ident)))))
130                };
131            },
132            quote!(#field_name)
133        )
134    }).multiunzip();
135
136    let named = input.fields.is_empty() || input.fields.iter().any(|field| field.ident.is_some());
137    let struct_constructor = if named {
138        quote!(#ident { #(#field_identifiers,)* })
139    } else {
140        quote!(#ident(#(#field_identifiers,)*))
141    };
142
143    quote!{
144        impl battler_wamprat_message::WampApplicationMessage for #ident {
145            fn wamp_serialize_application_message(self) -> core::result::Result<(battler_wamp_values::List, battler_wamp_values::Dictionary), battler_wamp_values::WampSerializeError> {
146                let arguments = battler_wamp_values::List::default();
147                let arguments_keyword = battler_wamp_values::Dictionary::default();
148                #(#field_serializers)*
149                Ok((arguments, arguments_keyword))
150            }
151
152            fn wamp_deserialize_application_message(
153                arguments: battler_wamp_values::List,
154                arguments_keyword: battler_wamp_values::Dictionary,
155            ) -> core::result::Result<Self, battler_wamp_values::WampDeserializeError> {
156                let arguments = battler_wamp_values::Value::List(arguments);
157                let arguments_keyword = battler_wamp_values::Value::Dictionary(arguments_keyword);
158                #(#field_deserializers)*
159                Ok(#struct_constructor)
160            }
161        }
162    }.into()
163}