1extern crate proc_macro;
2
3use syn::{parse_macro_input, punctuated::Punctuated, ItemStruct, Meta, Token};
4
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7
8#[proc_macro_attribute]
9pub fn prpc_serde_bytes(_attr: TokenStream, item: TokenStream) -> TokenStream {
10 patch(parse_macro_input!(item)).into()
11}
12
13pub(crate) fn patch(item: TokenStream2) -> TokenStream2 {
14 match patch_or_err(item) {
15 Ok(tokens) => tokens,
16 Err(err) => err.to_compile_error(),
17 }
18}
19
20fn has_attr(attrs: &[syn::Attribute], name: &str, field: &str) -> bool {
21 for attr in attrs.iter() {
22 if attr.path().is_ident(name) {
23 let Ok(nested) = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
24 else {
25 continue;
26 };
27 for meta in nested.iter() {
28 if meta.path().is_ident(field) {
29 return true;
30 }
31 }
32 }
33 }
34 false
35}
36
37fn patch_or_err(input: TokenStream2) -> syn::Result<TokenStream2> {
38 let Ok(mut input) = syn::parse2::<ItemStruct>(input.clone()) else {
39 return Ok(input);
40 };
41 for field in input.fields.iter_mut() {
42 if !has_attr(&field.attrs, "serde", "with") && has_attr(&field.attrs, "prost", "bytes") {
43 if has_attr(&field.attrs, "prost", "optional") {
44 field
45 .attrs
46 .push(syn::parse_quote!(#[serde(with = "::prpc::serde_helpers::option_bytes_as_hex_str")]));
47 } else if has_attr(&field.attrs, "prost", "repeated") {
48 field
49 .attrs
50 .push(syn::parse_quote!(#[serde(with = "::prpc::serde_helpers::vec_bytes_as_hex_str")]));
51 } else {
52 field.attrs.push(
53 syn::parse_quote!(#[serde(with = "::prpc::serde_helpers::bytes_as_hex_str")]),
54 );
55 }
56 }
57 if !has_attr(&field.attrs, "serde", "default")
58 && has_attr(&field.attrs, "prost", "repeated")
59 {
60 field.attrs.push(syn::parse_quote!(#[serde(default)]));
61 }
62 }
63
64 Ok(syn::parse_quote! {
65 #input
66 })
67}
68
69#[cfg(test)]
70mod tests;