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 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 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 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 #[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}