battler_wamprat_message_proc_macro/
lib.rs1extern 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#[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}