borsh_derive/internals/deserialize/structs/
mod.rs

1use proc_macro2::TokenStream as TokenStream2;
2use quote::quote;
3use syn::{Fields, ItemStruct, Path};
4
5use crate::internals::{attributes::item, deserialize, generics};
6
7pub fn process(input: &ItemStruct, cratename: Path) -> syn::Result<TokenStream2> {
8    let name = &input.ident;
9    let generics = generics::without_defaults(&input.generics);
10    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
11    let mut where_clause = generics::default_where(where_clause);
12    let mut body = TokenStream2::new();
13    let mut generics_output = deserialize::GenericsOutput::new(&generics);
14
15    let return_value = match &input.fields {
16        Fields::Named(fields) => {
17            for field in &fields.named {
18                deserialize::process_field(field, &cratename, &mut body, &mut generics_output)?;
19            }
20            quote! {
21                Self { #body }
22            }
23        }
24        Fields::Unnamed(fields) => {
25            for field in fields.unnamed.iter() {
26                deserialize::process_field(field, &cratename, &mut body, &mut generics_output)?;
27            }
28            quote! {
29                Self( #body )
30            }
31        }
32        Fields::Unit => {
33            quote! {
34                Self {}
35            }
36        }
37    };
38    generics_output.extend(&mut where_clause, &cratename);
39
40    if let Some(method_ident) = item::contains_initialize_with(&input.attrs)? {
41        Ok(quote! {
42            impl #impl_generics #cratename::de::BorshDeserialize for #name #ty_generics #where_clause {
43                fn deserialize_reader<__R: #cratename::io::Read>(reader: &mut __R) -> ::core::result::Result<Self, #cratename::io::Error> {
44                    let mut return_value = #return_value;
45                    return_value.#method_ident();
46                    Ok(return_value)
47                }
48            }
49        })
50    } else {
51        Ok(quote! {
52            impl #impl_generics #cratename::de::BorshDeserialize for #name #ty_generics #where_clause {
53                fn deserialize_reader<__R: #cratename::io::Read>(reader: &mut __R) -> ::core::result::Result<Self, #cratename::io::Error> {
54                    Ok(#return_value)
55                }
56            }
57        })
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use crate::internals::test_helpers::{
64        default_cratename, local_insta_assert_snapshot, pretty_print_syn_str,
65    };
66
67    use super::*;
68
69    #[test]
70    fn simple_struct() {
71        let item_struct: ItemStruct = syn::parse2(quote! {
72            struct A {
73                x: u64,
74                y: String,
75            }
76        })
77        .unwrap();
78
79        let actual = process(&item_struct, default_cratename()).unwrap();
80
81        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
82    }
83
84    #[test]
85    fn simple_struct_with_custom_crate() {
86        let item_struct: ItemStruct = syn::parse2(quote! {
87            struct A {
88                x: u64,
89                y: String,
90            }
91        })
92        .unwrap();
93
94        let crate_: Path = syn::parse2(quote! { reexporter::borsh }).unwrap();
95        let actual = process(&item_struct, crate_).unwrap();
96
97        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
98    }
99
100    #[test]
101    fn simple_generics() {
102        let item_struct: ItemStruct = syn::parse2(quote! {
103            struct A<K, V> {
104                x: HashMap<K, V>,
105                y: String,
106            }
107        })
108        .unwrap();
109
110        let actual = process(&item_struct, default_cratename()).unwrap();
111        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
112    }
113
114    #[test]
115    fn simple_generic_tuple_struct() {
116        let item_struct: ItemStruct = syn::parse2(quote! {
117            struct TupleA<T>(T, u32);
118        })
119        .unwrap();
120
121        let actual = process(&item_struct, default_cratename()).unwrap();
122        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
123    }
124
125    #[test]
126    fn bound_generics() {
127        let item_struct: ItemStruct = syn::parse2(quote! {
128            struct A<K: Key, V> where V: Value {
129                x: HashMap<K, V>,
130                y: String,
131            }
132        })
133        .unwrap();
134
135        let actual = process(&item_struct, default_cratename()).unwrap();
136        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
137    }
138
139    #[test]
140    fn recursive_struct() {
141        let item_struct: ItemStruct = syn::parse2(quote! {
142            struct CRecC {
143                a: String,
144                b: HashMap<String, CRecC>,
145            }
146        })
147        .unwrap();
148
149        let actual = process(&item_struct, default_cratename()).unwrap();
150
151        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
152    }
153
154    #[test]
155    fn generic_tuple_struct_borsh_skip1() {
156        let item_struct: ItemStruct = syn::parse2(quote! {
157            struct G<K, V, U> (
158                #[borsh(skip)]
159                HashMap<K, V>,
160                U,
161            );
162        })
163        .unwrap();
164
165        let actual = process(&item_struct, default_cratename()).unwrap();
166
167        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
168    }
169
170    #[test]
171    fn generic_tuple_struct_borsh_skip2() {
172        let item_struct: ItemStruct = syn::parse2(quote! {
173            struct G<K, V, U> (
174                HashMap<K, V>,
175                #[borsh(skip)]
176                U,
177            );
178        })
179        .unwrap();
180
181        let actual = process(&item_struct, default_cratename()).unwrap();
182
183        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
184    }
185
186    #[test]
187    fn generic_named_fields_struct_borsh_skip() {
188        let item_struct: ItemStruct = syn::parse2(quote! {
189            struct G<K, V, U> {
190                #[borsh(skip)]
191                x: HashMap<K, V>,
192                y: U,
193            }
194        })
195        .unwrap();
196
197        let actual = process(&item_struct, default_cratename()).unwrap();
198
199        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
200    }
201
202    #[test]
203    fn generic_deserialize_bound() {
204        let item_struct: ItemStruct = syn::parse2(quote! {
205            struct C<T: Debug, U> {
206                a: String,
207                #[borsh(bound(deserialize =
208                    "T: PartialOrd + Hash + Eq + borsh::de::BorshDeserialize,
209                     U: borsh::de::BorshDeserialize"
210                ))]
211                b: HashMap<T, U>,
212            }
213        })
214        .unwrap();
215
216        let actual = process(&item_struct, default_cratename()).unwrap();
217
218        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
219    }
220
221    #[test]
222    fn test_override_automatically_added_default_trait() {
223        let item_struct: ItemStruct = syn::parse2(quote! {
224              struct G1<K, V, U>(
225                #[borsh(skip,bound(deserialize = ""))]
226                HashMap<K, V>,
227                U
228            );
229        })
230        .unwrap();
231
232        let actual = process(&item_struct, default_cratename()).unwrap();
233
234        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
235    }
236
237    #[test]
238    fn check_deserialize_with_attr() {
239        let item_struct: ItemStruct = syn::parse2(quote! {
240            struct A<K: Ord, V> {
241                #[borsh(deserialize_with = "third_party_impl::deserialize_third_party")]
242                x: ThirdParty<K, V>,
243                y: u64,
244            }
245        })
246        .unwrap();
247
248        let actual = process(&item_struct, default_cratename()).unwrap();
249
250        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
251    }
252    #[test]
253    fn borsh_init_func() {
254        let item_enum: ItemStruct = syn::parse2(quote! {
255            #[borsh(init=initialization_method)]
256            struct A {
257                x: u64,
258                y: String,
259            }
260        })
261        .unwrap();
262        let actual = process(&item_enum, default_cratename()).unwrap();
263        local_insta_assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
264    }
265}