borsh_derive/internals/attributes/field/
mod.rs

1use std::collections::BTreeMap;
2
3use once_cell::sync::Lazy;
4use syn::{meta::ParseNestedMeta, Attribute, WherePredicate};
5
6use self::bounds::BOUNDS_FIELD_PARSE_MAP;
7
8use super::{
9    get_one_attribute,
10    parsing::{attr_get_by_symbol_keys, meta_get_by_symbol_keys, parse_lit_into},
11    BoundType, Symbol, BORSH, BOUND, DESERIALIZE_WITH, SERIALIZE_WITH, SKIP,
12};
13
14#[cfg(feature = "schema")]
15use {
16    super::schema_keys::{PARAMS, SCHEMA, WITH_FUNCS},
17    schema::SCHEMA_FIELD_PARSE_MAP,
18};
19
20pub mod bounds;
21#[cfg(feature = "schema")]
22pub mod schema;
23
24enum Variants {
25    Bounds(bounds::Bounds),
26    SerializeWith(syn::ExprPath),
27    DeserializeWith(syn::ExprPath),
28    Skip(()),
29    #[cfg(feature = "schema")]
30    Schema(schema::Attributes),
31}
32
33type ParseFn = dyn Fn(Symbol, Symbol, &ParseNestedMeta) -> syn::Result<Variants> + Send + Sync;
34
35static BORSH_FIELD_PARSE_MAP: Lazy<BTreeMap<Symbol, Box<ParseFn>>> = Lazy::new(|| {
36    let mut m = BTreeMap::new();
37    // assigning closure `let f = |args| {...};` and boxing closure `let f: Box<ParseFn> = Box::new(f);`
38    // on 2 separate lines doesn't work
39    let f_bounds: Box<ParseFn> = Box::new(|_attr_name, _meta_item_name, meta| {
40        let map_result = meta_get_by_symbol_keys(BOUND, meta, &BOUNDS_FIELD_PARSE_MAP)?;
41        let bounds_attributes: bounds::Bounds = map_result.into();
42        Ok(Variants::Bounds(bounds_attributes))
43    });
44
45    let f_serialize_with: Box<ParseFn> = Box::new(|attr_name, meta_item_name, meta| {
46        parse_lit_into::<syn::ExprPath>(attr_name, meta_item_name, meta)
47            .map(Variants::SerializeWith)
48    });
49
50    let f_deserialize_with: Box<ParseFn> = Box::new(|attr_name, meta_item_name, meta| {
51        parse_lit_into::<syn::ExprPath>(attr_name, meta_item_name, meta)
52            .map(Variants::DeserializeWith)
53    });
54
55    #[cfg(feature = "schema")]
56    let f_schema: Box<ParseFn> = Box::new(|_attr_name, _meta_item_name, meta| {
57        let map_result = meta_get_by_symbol_keys(SCHEMA, meta, &SCHEMA_FIELD_PARSE_MAP)?;
58        let schema_attributes: schema::Attributes = map_result.into();
59        Ok(Variants::Schema(schema_attributes))
60    });
61
62    let f_skip: Box<ParseFn> =
63        Box::new(|_attr_name, _meta_item_name, _meta| Ok(Variants::Skip(())));
64    m.insert(BOUND, f_bounds);
65    m.insert(SERIALIZE_WITH, f_serialize_with);
66    m.insert(DESERIALIZE_WITH, f_deserialize_with);
67    m.insert(SKIP, f_skip);
68    #[cfg(feature = "schema")]
69    m.insert(SCHEMA, f_schema);
70    m
71});
72
73#[derive(Default, Clone)]
74pub(crate) struct Attributes {
75    pub bounds: Option<bounds::Bounds>,
76    pub serialize_with: Option<syn::ExprPath>,
77    pub deserialize_with: Option<syn::ExprPath>,
78    pub skip: bool,
79    #[cfg(feature = "schema")]
80    pub schema: Option<schema::Attributes>,
81}
82
83impl From<BTreeMap<Symbol, Variants>> for Attributes {
84    fn from(mut map: BTreeMap<Symbol, Variants>) -> Self {
85        let bounds = map.remove(&BOUND);
86        let serialize_with = map.remove(&SERIALIZE_WITH);
87        let deserialize_with = map.remove(&DESERIALIZE_WITH);
88        let skip = map.remove(&SKIP);
89        let bounds = bounds.map(|variant| match variant {
90            Variants::Bounds(bounds) => bounds,
91            _ => unreachable!("only one enum variant is expected to correspond to given map key"),
92        });
93
94        let serialize_with = serialize_with.map(|variant| match variant {
95            Variants::SerializeWith(serialize_with) => serialize_with,
96            _ => unreachable!("only one enum variant is expected to correspond to given map key"),
97        });
98
99        let deserialize_with = deserialize_with.map(|variant| match variant {
100            Variants::DeserializeWith(deserialize_with) => deserialize_with,
101            _ => unreachable!("only one enum variant is expected to correspond to given map key"),
102        });
103
104        let skip = skip.map(|variant| match variant {
105            Variants::Skip(skip) => skip,
106            _ => unreachable!("only one enum variant is expected to correspond to given map key"),
107        });
108
109        #[cfg(feature = "schema")]
110        let schema = {
111            let schema = map.remove(&SCHEMA);
112            schema.map(|variant| match variant {
113                Variants::Schema(schema) => schema,
114                _ => {
115                    unreachable!("only one enum variant is expected to correspond to given map key")
116                }
117            })
118        };
119        Self {
120            bounds,
121            serialize_with,
122            deserialize_with,
123            skip: skip.is_some(),
124            #[cfg(feature = "schema")]
125            schema,
126        }
127    }
128}
129
130#[cfg(feature = "schema")]
131pub(crate) fn filter_attrs(
132    attrs: impl Iterator<Item = Attribute>,
133) -> impl Iterator<Item = Attribute> {
134    attrs.filter(|attr| attr.path() == BORSH)
135}
136
137impl Attributes {
138    fn check(&self, attr: &Attribute) -> Result<(), syn::Error> {
139        if self.skip && (self.serialize_with.is_some() || self.deserialize_with.is_some()) {
140            return Err(syn::Error::new_spanned(
141                attr,
142                format!(
143                    "`{}` cannot be used at the same time as `{}` or `{}`",
144                    SKIP.0, SERIALIZE_WITH.0, DESERIALIZE_WITH.0
145                ),
146            ));
147        }
148
149        #[cfg(feature = "schema")]
150        self.check_schema(attr)?;
151
152        Ok(())
153    }
154    pub(crate) fn parse(attrs: &[Attribute]) -> Result<Self, syn::Error> {
155        let borsh = get_one_attribute(attrs)?;
156
157        let result: Self = if let Some(attr) = borsh {
158            let result: Self = attr_get_by_symbol_keys(BORSH, attr, &BORSH_FIELD_PARSE_MAP)?.into();
159            result.check(attr)?;
160            result
161        } else {
162            BTreeMap::new().into()
163        };
164
165        Ok(result)
166    }
167    pub(crate) fn needs_bounds_derive(&self, ty: BoundType) -> bool {
168        let predicates = self.get_bounds(ty);
169        predicates.is_none()
170    }
171
172    fn get_bounds(&self, ty: BoundType) -> Option<Vec<WherePredicate>> {
173        let bounds = self.bounds.as_ref();
174        bounds.and_then(|bounds| match ty {
175            BoundType::Serialize => bounds.serialize.clone(),
176            BoundType::Deserialize => bounds.deserialize.clone(),
177        })
178    }
179    pub(crate) fn collect_bounds(&self, ty: BoundType) -> Vec<WherePredicate> {
180        let predicates = self.get_bounds(ty);
181        predicates.unwrap_or_default()
182    }
183}
184
185#[cfg(feature = "schema")]
186impl Attributes {
187    fn check_schema(&self, attr: &Attribute) -> Result<(), syn::Error> {
188        if let Some(ref schema) = self.schema {
189            if self.skip && schema.params.is_some() {
190                return Err(syn::Error::new_spanned(
191                    attr,
192                    format!(
193                        "`{}` cannot be used at the same time as `{}({})`",
194                        SKIP.0, SCHEMA.0, PARAMS.1
195                    ),
196                ));
197            }
198
199            if self.skip && schema.with_funcs.is_some() {
200                return Err(syn::Error::new_spanned(
201                    attr,
202                    format!(
203                        "`{}` cannot be used at the same time as `{}({})`",
204                        SKIP.0, SCHEMA.0, WITH_FUNCS.1
205                    ),
206                ));
207            }
208        }
209        Ok(())
210    }
211
212    pub(crate) fn needs_schema_params_derive(&self) -> bool {
213        if let Some(ref schema) = self.schema {
214            if schema.params.is_some() {
215                return false;
216            }
217        }
218        true
219    }
220
221    pub(crate) fn schema_declaration(&self) -> Option<syn::ExprPath> {
222        self.schema.as_ref().and_then(|schema| {
223            schema
224                .with_funcs
225                .as_ref()
226                .and_then(|with_funcs| with_funcs.declaration.clone())
227        })
228    }
229
230    pub(crate) fn schema_definitions(&self) -> Option<syn::ExprPath> {
231        self.schema.as_ref().and_then(|schema| {
232            schema
233                .with_funcs
234                .as_ref()
235                .and_then(|with_funcs| with_funcs.definitions.clone())
236        })
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use quote::quote;
243    use syn::{Attribute, ItemStruct};
244
245    fn parse_bounds(attrs: &[Attribute]) -> Result<Option<bounds::Bounds>, syn::Error> {
246        // #[borsh(bound(serialize = "...", deserialize = "..."))]
247        let borsh_attrs = Attributes::parse(attrs)?;
248        Ok(borsh_attrs.bounds)
249    }
250
251    use crate::internals::test_helpers::{
252        debug_print_tokenizable, debug_print_vec_of_tokenizable, local_insta_assert_debug_snapshot,
253        local_insta_assert_snapshot,
254    };
255
256    use super::{bounds, Attributes};
257
258    #[test]
259    fn test_reject_multiple_borsh_attrs() {
260        let item_struct: ItemStruct = syn::parse2(quote! {
261            struct A {
262                #[borsh(skip)]
263                #[borsh(bound(deserialize = "K: Hash + Ord,
264                     V: Eq + Ord",
265                    serialize = "K: Hash + Eq + Ord,
266                     V: Ord"
267                ))]
268                x: u64,
269                y: String,
270            }
271        })
272        .unwrap();
273
274        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
275        let err = match Attributes::parse(&first_field.attrs) {
276            Ok(..) => unreachable!("expecting error here"),
277            Err(err) => err,
278        };
279        local_insta_assert_debug_snapshot!(err);
280    }
281
282    #[test]
283    fn test_bounds_parsing1() {
284        let item_struct: ItemStruct = syn::parse2(quote! {
285            struct A {
286                #[borsh(bound(deserialize = "K: Hash + Ord,
287                     V: Eq + Ord",
288                    serialize = "K: Hash + Eq + Ord,
289                     V: Ord"
290                ))]
291                x: u64,
292                y: String,
293            }
294        })
295        .unwrap();
296
297        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
298        let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap();
299        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.serialize.clone()));
300        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize));
301    }
302
303    #[test]
304    fn test_bounds_parsing2() {
305        let item_struct: ItemStruct = syn::parse2(quote! {
306            struct A {
307                #[borsh(bound(deserialize = "K: Hash + Eq + borsh::de::BorshDeserialize,
308                     V: borsh::de::BorshDeserialize",
309                    serialize = "K: Hash + Eq + borsh::ser::BorshSerialize,
310                     V: borsh::ser::BorshSerialize"
311                ))]
312                x: u64,
313                y: String,
314            }
315        })
316        .unwrap();
317
318        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
319        let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap();
320        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.serialize.clone()));
321        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize));
322    }
323
324    #[test]
325    fn test_bounds_parsing3() {
326        let item_struct: ItemStruct = syn::parse2(quote! {
327            struct A {
328                #[borsh(bound(deserialize = "K: Hash + Eq + borsh::de::BorshDeserialize,
329                     V: borsh::de::BorshDeserialize",
330                    serialize = ""
331                ))]
332                x: u64,
333                y: String,
334            }
335        })
336        .unwrap();
337
338        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
339        let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap();
340        assert_eq!(attrs.serialize.as_ref().unwrap().len(), 0);
341        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize));
342    }
343
344    #[test]
345    fn test_bounds_parsing4() {
346        let item_struct: ItemStruct = syn::parse2(quote! {
347            struct A {
348                #[borsh(bound(deserialize = "K: Hash"))]
349                x: u64,
350                y: String,
351            }
352        })
353        .unwrap();
354
355        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
356        let attrs = parse_bounds(&first_field.attrs).unwrap().unwrap();
357        assert!(attrs.serialize.is_none());
358        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(attrs.deserialize));
359    }
360
361    #[test]
362    fn test_bounds_parsing_error() {
363        let item_struct: ItemStruct = syn::parse2(quote! {
364            struct A {
365                #[borsh(bound(deser = "K: Hash"))]
366                x: u64,
367                y: String,
368            }
369        })
370        .unwrap();
371
372        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
373        let err = match parse_bounds(&first_field.attrs) {
374            Ok(..) => unreachable!("expecting error here"),
375            Err(err) => err,
376        };
377        local_insta_assert_debug_snapshot!(err);
378    }
379
380    #[test]
381    fn test_bounds_parsing_error2() {
382        let item_struct: ItemStruct = syn::parse2(quote! {
383            struct A {
384                #[borsh(bound(deserialize = "K Hash"))]
385                x: u64,
386                y: String,
387            }
388        })
389        .unwrap();
390
391        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
392        let err = match parse_bounds(&first_field.attrs) {
393            Ok(..) => unreachable!("expecting error here"),
394            Err(err) => err,
395        };
396        local_insta_assert_debug_snapshot!(err);
397    }
398
399    #[test]
400    fn test_bounds_parsing_error3() {
401        let item_struct: ItemStruct = syn::parse2(quote! {
402            struct A {
403                #[borsh(bound(deserialize = 42))]
404                x: u64,
405                y: String,
406            }
407        })
408        .unwrap();
409
410        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
411        let err = match parse_bounds(&first_field.attrs) {
412            Ok(..) => unreachable!("expecting error here"),
413            Err(err) => err,
414        };
415        local_insta_assert_debug_snapshot!(err);
416    }
417
418    #[test]
419    fn test_ser_de_with_parsing1() {
420        let item_struct: ItemStruct = syn::parse2(quote! {
421            struct A {
422                #[borsh(
423                    serialize_with = "third_party_impl::serialize_third_party",
424                    deserialize_with = "third_party_impl::deserialize_third_party",
425                )]
426                x: u64,
427                y: String,
428            }
429        })
430        .unwrap();
431
432        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
433        let attrs = Attributes::parse(&first_field.attrs).unwrap();
434        local_insta_assert_snapshot!(debug_print_tokenizable(attrs.serialize_with.as_ref()));
435        local_insta_assert_snapshot!(debug_print_tokenizable(attrs.deserialize_with));
436    }
437    #[test]
438    fn test_borsh_skip() {
439        let item_struct: ItemStruct = syn::parse2(quote! {
440            struct A {
441                #[borsh(skip)]
442                x: u64,
443                y: String,
444            }
445        })
446        .unwrap();
447
448        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
449
450        let result = Attributes::parse(&first_field.attrs).unwrap();
451        assert!(result.skip);
452    }
453    #[test]
454    fn test_borsh_no_skip() {
455        let item_struct: ItemStruct = syn::parse2(quote! {
456            struct A {
457                x: u64,
458                y: String,
459            }
460        })
461        .unwrap();
462
463        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
464
465        let result = Attributes::parse(&first_field.attrs).unwrap();
466        assert!(!result.skip);
467    }
468}
469
470#[cfg(feature = "schema")]
471#[cfg(test)]
472mod tests_schema {
473    use crate::internals::{
474        attributes::field::Attributes,
475        test_helpers::{
476            debug_print_tokenizable, debug_print_vec_of_tokenizable,
477            local_insta_assert_debug_snapshot, local_insta_assert_snapshot,
478        },
479    };
480
481    use quote::quote;
482    use syn::{Attribute, ItemStruct};
483
484    use super::schema;
485    fn parse_schema_attrs(attrs: &[Attribute]) -> Result<Option<schema::Attributes>, syn::Error> {
486        // #[borsh(schema(params = "..."))]
487        let borsh_attrs = Attributes::parse(attrs)?;
488        Ok(borsh_attrs.schema)
489    }
490
491    #[test]
492    fn test_root_bounds_and_params_combined() {
493        let item_struct: ItemStruct = syn::parse2(quote! {
494            struct A {
495                #[borsh(
496                    serialize_with = "third_party_impl::serialize_third_party",
497                    bound(deserialize = "K: Hash"),
498                    schema(params = "T => <T as TraitName>::Associated, V => Vec<V>")
499                )]
500                x: u64,
501                y: String,
502            }
503        })
504        .unwrap();
505
506        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
507
508        let attrs = Attributes::parse(&first_field.attrs).unwrap();
509        let bounds = attrs.bounds.clone().unwrap();
510        assert!(bounds.serialize.is_none());
511        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(bounds.deserialize));
512        assert!(attrs.deserialize_with.is_none());
513        let schema = attrs.schema.clone().unwrap();
514        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(schema.params.clone()));
515        local_insta_assert_snapshot!(debug_print_tokenizable(attrs.serialize_with));
516    }
517
518    #[test]
519    fn test_schema_params_parsing1() {
520        let item_struct: ItemStruct = syn::parse2(quote! {
521            struct Parametrized<V, T>
522            where
523                T: TraitName,
524            {
525                #[borsh(schema(params =
526                    "T => <T as TraitName>::Associated"
527               ))]
528                field: <T as TraitName>::Associated,
529                another: V,
530            }
531        })
532        .unwrap();
533
534        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
535        let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap();
536        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(schema_attrs.unwrap().params));
537    }
538    #[test]
539    fn test_schema_params_parsing_error() {
540        let item_struct: ItemStruct = syn::parse2(quote! {
541            struct Parametrized<V, T>
542            where
543                T: TraitName,
544            {
545                #[borsh(schema(params =
546                    "T => <T as TraitName, W>::Associated"
547               ))]
548                field: <T as TraitName>::Associated,
549                another: V,
550            }
551        })
552        .unwrap();
553
554        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
555        let err = match parse_schema_attrs(&first_field.attrs) {
556            Ok(..) => unreachable!("expecting error here"),
557            Err(err) => err,
558        };
559        local_insta_assert_debug_snapshot!(err);
560    }
561
562    #[test]
563    fn test_schema_params_parsing_error2() {
564        let item_struct: ItemStruct = syn::parse2(quote! {
565            struct Parametrized<V, T>
566            where
567                T: TraitName,
568            {
569                #[borsh(schema(paramsbum =
570                    "T => <T as TraitName>::Associated"
571               ))]
572                field: <T as TraitName>::Associated,
573                another: V,
574            }
575        })
576        .unwrap();
577
578        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
579        let err = match parse_schema_attrs(&first_field.attrs) {
580            Ok(..) => unreachable!("expecting error here"),
581            Err(err) => err,
582        };
583        local_insta_assert_debug_snapshot!(err);
584    }
585
586    #[test]
587    fn test_schema_params_parsing2() {
588        let item_struct: ItemStruct = syn::parse2(quote! {
589            struct Parametrized<V, T>
590            where
591                T: TraitName,
592            {
593                #[borsh(schema(params =
594                    "T => <T as TraitName>::Associated, V => Vec<V>"
595               ))]
596                field: <T as TraitName>::Associated,
597                another: V,
598            }
599        })
600        .unwrap();
601
602        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
603        let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap();
604        local_insta_assert_snapshot!(debug_print_vec_of_tokenizable(schema_attrs.unwrap().params));
605    }
606    #[test]
607    fn test_schema_params_parsing3() {
608        let item_struct: ItemStruct = syn::parse2(quote! {
609            struct Parametrized<V, T>
610            where
611                T: TraitName,
612            {
613                #[borsh(schema(params = "" ))]
614                field: <T as TraitName>::Associated,
615                another: V,
616            }
617        })
618        .unwrap();
619
620        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
621        let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap();
622        assert_eq!(schema_attrs.unwrap().params.unwrap().len(), 0);
623    }
624
625    #[test]
626    fn test_schema_params_parsing4() {
627        let item_struct: ItemStruct = syn::parse2(quote! {
628            struct Parametrized<V, T>
629            where
630                T: TraitName,
631            {
632                field: <T as TraitName>::Associated,
633                another: V,
634            }
635        })
636        .unwrap();
637
638        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
639        let schema_attrs = parse_schema_attrs(&first_field.attrs).unwrap();
640        assert!(schema_attrs.is_none());
641    }
642
643    #[test]
644    fn test_schema_with_funcs_parsing() {
645        let item_struct: ItemStruct = syn::parse2(quote! {
646            struct A {
647                #[borsh(schema(with_funcs(
648                    declaration = "third_party_impl::declaration::<K, V>",
649                    definitions = "third_party_impl::add_definitions_recursively::<K, V>"
650                )))]
651                x: u64,
652                y: String,
653            }
654        })
655        .unwrap();
656
657        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
658        let attrs = Attributes::parse(&first_field.attrs).unwrap();
659        let schema = attrs.schema.unwrap();
660        let with_funcs = schema.with_funcs.unwrap();
661
662        local_insta_assert_snapshot!(debug_print_tokenizable(with_funcs.declaration.clone()));
663        local_insta_assert_snapshot!(debug_print_tokenizable(with_funcs.definitions));
664    }
665
666    // both `declaration` and `definitions` have to be specified
667    #[test]
668    fn test_schema_with_funcs_parsing_error() {
669        let item_struct: ItemStruct = syn::parse2(quote! {
670            struct A {
671                #[borsh(schema(with_funcs(
672                    declaration = "third_party_impl::declaration::<K, V>"
673                )))]
674                x: u64,
675                y: String,
676            }
677        })
678        .unwrap();
679
680        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
681        let attrs = Attributes::parse(&first_field.attrs);
682
683        let err = match attrs {
684            Ok(..) => unreachable!("expecting error here"),
685            Err(err) => err,
686        };
687        local_insta_assert_debug_snapshot!(err);
688    }
689
690    #[test]
691    fn test_root_error() {
692        let item_struct: ItemStruct = syn::parse2(quote! {
693            struct A {
694                #[borsh(boons)]
695                x: u64,
696                y: String,
697            }
698        })
699        .unwrap();
700
701        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
702        let err = match Attributes::parse(&first_field.attrs) {
703            Ok(..) => unreachable!("expecting error here"),
704            Err(err) => err,
705        };
706        local_insta_assert_debug_snapshot!(err);
707    }
708
709    #[test]
710    fn test_root_bounds_and_wrong_key_combined() {
711        let item_struct: ItemStruct = syn::parse2(quote! {
712            struct A {
713                #[borsh(bound(deserialize = "K: Hash"),
714                        schhema(params = "T => <T as TraitName>::Associated, V => Vec<V>")
715                )]
716                x: u64,
717                y: String,
718            }
719        })
720        .unwrap();
721
722        let first_field = &item_struct.fields.into_iter().collect::<Vec<_>>()[0];
723
724        let err = match Attributes::parse(&first_field.attrs) {
725            Ok(..) => unreachable!("expecting error here"),
726            Err(err) => err,
727        };
728        local_insta_assert_debug_snapshot!(err);
729    }
730}