bitcoin_cash_code/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::spanned::Spanned;
6
7fn single_path(path: &syn::Path) -> Option<syn::Ident> {
8    if path.segments.len() == 1 {
9        Some(path.segments[0].ident.clone())
10    } else {
11        None
12    }
13}
14
15fn parse_string_lit(lit: &syn::Lit) -> Option<String> {
16    if let syn::Lit::Str(predicate_str) = lit {
17        Some(predicate_str.value())
18    } else {
19        None
20    }
21}
22
23fn generate(item_struct: syn::ItemStruct) -> Result<TokenStream, syn::Error> {
24    let struct_name = &item_struct.ident;
25
26    let mut crate_name = quote! {bitcoin_cash};
27
28    for attr in &item_struct.attrs {
29        match attr.parse_meta()? {
30            syn::Meta::List(list) if list.path.to_token_stream().to_string() == "bitcoin_code" => {
31                for nested_attr in list.nested.iter() {
32                    match nested_attr {
33                        syn::NestedMeta::Meta(attr_meta) => match attr_meta {
34                            syn::Meta::NameValue(name_value) => {
35                                let ident = single_path(&name_value.path).ok_or_else(|| {
36                                    syn::Error::new(attr.span(), "Invalid attribute, invalid name")
37                                })?;
38                                match ident.to_string().as_str() {
39                                    "crate" => {
40                                        let crate_name_str = parse_string_lit(&name_value.lit)
41                                            .ok_or_else(|| {
42                                                syn::Error::new(
43                                                    name_value.lit.span(),
44                                                    "Invalid attribute, invalid value",
45                                                )
46                                            })?;
47                                        crate_name =
48                                            syn::Ident::new(&crate_name_str, name_value.lit.span())
49                                                .to_token_stream();
50                                    }
51                                    _ => {
52                                        return Err(syn::Error::new(
53                                            name_value.span(),
54                                            "Invalid attribute, unknown name",
55                                        ))
56                                    }
57                                }
58                            }
59                            _ => {
60                                return Err(syn::Error::new(
61                                    attr_meta.span(),
62                                    "Invalid parameter, must provide values like this: a=\"b\"",
63                                ))
64                            }
65                        },
66                        syn::NestedMeta::Lit(_) => {
67                            return Err(syn::Error::new(
68                                nested_attr.span(),
69                                "Invalid literal, must provide values like this: a=\"b\"",
70                            ))
71                        }
72                    }
73                }
74            }
75            _ => {}
76        }
77    }
78
79    let fields = match &item_struct.fields {
80        syn::Fields::Named(fields) => fields,
81        syn::Fields::Unnamed(_) => {
82            return Err(syn::Error::new(
83                struct_name.span(),
84                "Cannot use macro for unnamed struct fields",
85            ))
86        }
87        syn::Fields::Unit => {
88            return Err(syn::Error::new(
89                struct_name.span(),
90                "Cannot use macro for unit structs",
91            ))
92        }
93    };
94
95    let parts_ident = quote! {__serialize_parts};
96
97    let mut ser_calls = Vec::new();
98    let mut deser_calls = Vec::new();
99    let mut field_idents = Vec::new();
100
101    for field in fields.named.iter() {
102        let mut is_skipped = false;
103        for attr in &field.attrs {
104            match attr.parse_meta()? {
105                syn::Meta::Path(_) => {
106                    return Err(syn::Error::new(attr.span(), "Invalid attribute"))
107                }
108                syn::Meta::NameValue(_) => {
109                    return Err(syn::Error::new(attr.span(), "Invalid attribute"))
110                }
111                syn::Meta::List(list) => {
112                    let params = list
113                        .nested
114                        .iter()
115                        .map(|param| param.to_token_stream().to_string())
116                        .collect::<Vec<_>>();
117                    match params
118                        .iter()
119                        .map(|param| param.as_str())
120                        .collect::<Vec<_>>()
121                        .as_slice()
122                    {
123                        &["skip"] => is_skipped = true,
124                        _ => return Err(syn::Error::new(list.nested.span(), "Invalid attribute")),
125                    }
126                }
127            }
128        }
129        let field_name = field.ident.as_ref().unwrap();
130        field_idents.push(field_name);
131        let field_name_str = field_name.to_string();
132        let field_type = &field.ty;
133        if !is_skipped {
134            ser_calls.push(quote! {
135                #parts_ident.push(self.#field_name.ser().named(#field_name_str));
136            });
137            deser_calls.push(quote! {
138                let (#field_name, data) = <#field_type as #crate_name::BitcoinCode>::deser(data)?;
139            })
140        } else {
141            deser_calls.push(quote! {
142                let #field_name = Default::default();
143            })
144        }
145    }
146
147    let capacity = fields.named.len();
148
149    let result = quote! {
150        impl #crate_name::BitcoinCode for #struct_name {
151            fn ser(&self) -> #crate_name::ByteArray {
152                let mut #parts_ident = Vec::with_capacity(#capacity);
153                #(#ser_calls)*
154                #crate_name::ByteArray::from_parts(#parts_ident)
155            }
156
157            fn deser(data: #crate_name::ByteArray) -> std::result::Result<(Self, #crate_name::ByteArray), #crate_name::error::Error> {
158                #(#deser_calls)*
159                Ok((
160                    #struct_name { #(#field_idents),* },
161                    data,
162                ))
163            }
164        }
165    };
166    Ok(result)
167}
168
169#[proc_macro_derive(BitcoinCode, attributes(bitcoin_code))]
170pub fn serialize_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
171    let item_struct = syn::parse_macro_input!(item as syn::ItemStruct);
172
173    let result = match generate(item_struct) {
174        Ok(tokens) => tokens,
175        Err(err) => err.to_compile_error(),
176    };
177
178    result.into()
179}