From 0967d54235de19ea4a6ccd37006c4ca5fb8ec199 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Fri, 8 Sep 2023 19:16:31 +0200 Subject: [PATCH] Add Definition::Enum::tag_width field The field allows specifying how many bytes the discriminant byte takes. This in turn allows for better support of custom encoding formats with more than 256 variants and untagged unions. Issue: https://github.com/near/borsh-rs/issues/181 --- .../src/internals/schema/enums/mod.rs | 6 +- .../schema/enums/snapshots/complex_enum.snap | 12 +- .../snapshots/complex_enum_generics.snap | 13 +- ..._enum_generics_borsh_skip_named_field.snap | 14 +- ..._enum_generics_borsh_skip_tuple_field.snap | 13 +- .../enums/snapshots/filter_foreign_attrs.snap | 10 +- .../snapshots/generic_associated_type.snap | 10 +- ...eneric_associated_type_param_override.snap | 10 +- .../enums/snapshots/recursive_enum.snap | 10 +- .../schema/enums/snapshots/simple_enum.snap | 10 +- .../simple_enum_with_custom_crate.snap | 10 +- .../enums/snapshots/single_field_enum.snap | 8 +- .../snapshots/trailing_comma_generics.snap | 10 +- .../enums/snapshots/with_funcs_attr.snap | 10 +- borsh/src/lib.rs | 1 - borsh/src/schema.rs | 72 ++++++-- borsh/src/schema_helpers.rs | 158 ++++++++++++------ ...chema_enums__complex_enum_with_schema.snap | 1 + borsh/tests/test_schema_enums.rs | 44 +++-- borsh/tests/test_schema_nested.rs | 45 +++-- borsh/tests/test_schema_recursive.rs | 25 +-- borsh/tests/test_schema_with.rs | 11 +- 22 files changed, 314 insertions(+), 189 deletions(-) diff --git a/borsh-derive/src/internals/schema/enums/mod.rs b/borsh-derive/src/internals/schema/enums/mod.rs index 80825a3c2..17a84241a 100644 --- a/borsh-derive/src/internals/schema/enums/mod.rs +++ b/borsh-derive/src/internals/schema/enums/mod.rs @@ -53,8 +53,10 @@ pub fn process(input: &ItemEnum, cratename: Path) -> syn::Result { fn add_definitions_recursively(definitions: &mut #cratename::__private::maybestd::collections::BTreeMap<#cratename::schema::Declaration, #cratename::schema::Definition>) { #inner_defs #add_recursive_defs - let variants = #cratename::__private::maybestd::vec![#(#variants_defs),*]; - let definition = #cratename::schema::Definition::Enum{variants}; + let definition = #cratename::schema::Definition::Enum { + tag_width: 1, + variants: #cratename::__private::maybestd::vec![#(#variants_defs),*], + }; #cratename::schema::add_definition(Self::declaration(), definition, definitions); } }; diff --git a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum.snap b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum.snap index 032c5e413..027619667 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum.snap @@ -35,13 +35,13 @@ impl borsh::BorshSchema for A { ::add_definitions_recursively(definitions); ::add_definitions_recursively(definitions); ::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < - AEggs > ::declaration()), ("Salad".to_string(), < ASalad > ::declaration()), - ("Sausage".to_string(), < ASausage > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < + AEggs > ::declaration()), ("Salad".to_string(), < ASalad > + ::declaration()), ("Sausage".to_string(), < ASausage > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics.snap b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics.snap index 798e52f36..bc4c91503 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics.snap @@ -42,13 +42,14 @@ where ::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < - AEggs > ::declaration()), ("Salad".to_string(), < ASalad < C > > - ::declaration()), ("Sausage".to_string(), < ASausage < W > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < + AEggs > ::declaration()), ("Salad".to_string(), < ASalad < C > > + ::declaration()), ("Sausage".to_string(), < ASausage < W > > + ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_named_field.snap b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_named_field.snap index a7b557b6f..99f616396 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_named_field.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_named_field.snap @@ -44,14 +44,14 @@ where ::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < - AEggs > ::declaration()), ("Salad".to_string(), < ASalad < C > > - ::declaration()), ("Sausage".to_string(), < ASausage < W, U > > - ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < + AEggs > ::declaration()), ("Salad".to_string(), < ASalad < C > > + ::declaration()), ("Sausage".to_string(), < ASausage < W, U > > + ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_tuple_field.snap b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_tuple_field.snap index 0fbddbf5c..b9ee7f0c5 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_tuple_field.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/complex_enum_generics_borsh_skip_tuple_field.snap @@ -43,13 +43,14 @@ where ::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < - AEggs > ::declaration()), ("Salad".to_string(), < ASalad < C > > - ::declaration()), ("Sausage".to_string(), < ASausage < W > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < + AEggs > ::declaration()), ("Salad".to_string(), < ASalad < C > > + ::declaration()), ("Sausage".to_string(), < ASausage < W > > + ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/filter_foreign_attrs.snap b/borsh-derive/src/internals/schema/enums/snapshots/filter_foreign_attrs.snap index 96f79ce65..273b550ee 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/filter_foreign_attrs.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/filter_foreign_attrs.snap @@ -29,12 +29,12 @@ impl borsh::BorshSchema for A { } ::add_definitions_recursively(definitions); ::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("B".to_string(), < AB > ::declaration()), ("Negative".to_string(), < - ANegative > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("B".to_string(), < AB > ::declaration()), ("Negative".to_string(), < + ANegative > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type.snap b/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type.snap index ab5048dd5..0db595cc6 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type.snap @@ -55,12 +55,12 @@ where as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("B".to_string(), < EnumParametrizedB < K, V > > ::declaration()), ("C" - .to_string(), < EnumParametrizedC < T > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("B".to_string(), < EnumParametrizedB < K, V > > ::declaration()), ("C" + .to_string(), < EnumParametrizedC < T > > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type_param_override.snap b/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type_param_override.snap index 405b62f59..1b56f94f5 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type_param_override.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/generic_associated_type_param_override.snap @@ -56,12 +56,12 @@ where as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("B".to_string(), < EnumParametrizedB < K, V > > ::declaration()), ("C" - .to_string(), < EnumParametrizedC < T > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("B".to_string(), < EnumParametrizedB < K, V > > ::declaration()), ("C" + .to_string(), < EnumParametrizedC < T > > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/recursive_enum.snap b/borsh-derive/src/internals/schema/enums/snapshots/recursive_enum.snap index d895d6625..a1b906dfb 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/recursive_enum.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/recursive_enum.snap @@ -36,12 +36,12 @@ where struct AC(K, Vec); as borsh::BorshSchema>::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("B".to_string(), < AB < K, V > > ::declaration()), ("C".to_string(), < AC < - K > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("B".to_string(), < AB < K, V > > ::declaration()), ("C".to_string(), < + AC < K > > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/simple_enum.snap b/borsh-derive/src/internals/schema/enums/snapshots/simple_enum.snap index 320d884b9..603c0f776 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/simple_enum.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/simple_enum.snap @@ -22,12 +22,12 @@ impl borsh::BorshSchema for A { struct AEggs; ::add_definitions_recursively(definitions); ::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < - AEggs > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < + AEggs > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/simple_enum_with_custom_crate.snap b/borsh-derive/src/internals/schema/enums/snapshots/simple_enum_with_custom_crate.snap index c78d0848e..538e501a1 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/simple_enum_with_custom_crate.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/simple_enum_with_custom_crate.snap @@ -26,12 +26,12 @@ impl reexporter::borsh::BorshSchema for A { ::add_definitions_recursively( definitions, ); - let variants = reexporter::borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < - AEggs > ::declaration()) - ]; let definition = reexporter::borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: reexporter::borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()), ("Eggs".to_string(), < + AEggs > ::declaration()) + ], }; reexporter::borsh::schema::add_definition( Self::declaration(), diff --git a/borsh-derive/src/internals/schema/enums/snapshots/single_field_enum.snap b/borsh-derive/src/internals/schema/enums/snapshots/single_field_enum.snap index e903f8fc1..c3bf310dd 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/single_field_enum.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/single_field_enum.snap @@ -17,11 +17,11 @@ impl borsh::BorshSchema for A { #[borsh(crate = "borsh")] struct ABacon; ::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Bacon".to_string(), < ABacon > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Bacon".to_string(), < ABacon > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/trailing_comma_generics.snap b/borsh-derive/src/internals/schema/enums/snapshots/trailing_comma_generics.snap index 387c8141e..151473bd3 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/trailing_comma_generics.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/trailing_comma_generics.snap @@ -39,12 +39,12 @@ where B: Display + Debug; as borsh::BorshSchema>::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("Left".to_string(), < SideLeft < A > > ::declaration()), ("Right" - .to_string(), < SideRight < B > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("Left".to_string(), < SideLeft < A > > ::declaration()), ("Right" + .to_string(), < SideRight < B > > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh-derive/src/internals/schema/enums/snapshots/with_funcs_attr.snap b/borsh-derive/src/internals/schema/enums/snapshots/with_funcs_attr.snap index 9a08a5e5b..766464e7f 100644 --- a/borsh-derive/src/internals/schema/enums/snapshots/with_funcs_attr.snap +++ b/borsh-derive/src/internals/schema/enums/snapshots/with_funcs_attr.snap @@ -40,12 +40,12 @@ where ); ::add_definitions_recursively(definitions); as borsh::BorshSchema>::add_definitions_recursively(definitions); - let variants = borsh::__private::maybestd::vec![ - ("C3".to_string(), < CC3 > ::declaration()), ("C4".to_string(), < CC4 < K, V - > > ::declaration()) - ]; let definition = borsh::schema::Definition::Enum { - variants, + tag_width: 1, + variants: borsh::__private::maybestd::vec![ + ("C3".to_string(), < CC3 > ::declaration()), ("C4".to_string(), < CC4 < + K, V > > ::declaration()) + ], }; borsh::schema::add_definition(Self::declaration(), definition, definitions); } diff --git a/borsh/src/lib.rs b/borsh/src/lib.rs index d8387693b..68e861251 100644 --- a/borsh/src/lib.rs +++ b/borsh/src/lib.rs @@ -64,7 +64,6 @@ */ -#[cfg(not(feature = "std"))] extern crate alloc; /// Derive macro available if borsh is built with `features = ["derive", "schema"]`. diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs index ea937b16f..86a3eb24e 100644 --- a/borsh/src/schema.rs +++ b/borsh/src/schema.rs @@ -35,6 +35,16 @@ pub type VariantName = String; pub type FieldName = String; /// The type that we use to represent the definition of the Borsh type. +/// Description of data encoding on the wire. +/// +/// Note: Since at the end of the day users can define arbitrary serialisation, +/// it’s not always possible to express using definitions how a type is encoded. +/// For example, let’s say programmer uses [varint] encoding for their data. +/// Such type cannot be fully expressed using `BorshSchema` (or at least not +/// easily). As a consequence, a tool which validates whether binary data +/// matches a schema wouldn’t be able to validate data including such types. +/// +/// [varint]: https://en.wikipedia.org/wiki/Variable-length_quantity#Variants #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)] pub enum Definition { /// A fixed-size array with the length known at the compile time and the same-type elements. @@ -44,10 +54,31 @@ pub enum Definition { /// A fixed-size tuple with the length known at the compile time and the elements of different /// types. Tuple { elements: Vec }, - /// A tagged union, a.k.a enum. Tagged-unions have variants with associated structures. + + /// A possibly tagged union, a.k.a enum. + /// + /// Tagged unions are prefixed by a tag identifying encoded variant followed + /// by encoding of that variant. + /// + /// Untagged unions don’t have a separate tag which means that knowledge of + /// the type is necessary to fully analyse the binary. Variants may still + /// be used to list possible values or determine the longest possible + /// encoding. Enum { + /// Width in bytes of the discriminant tag. + /// + /// Zero indicates this is an untagged union. In standard borsh + /// encoding this is one however custom encoding formats may use larger + /// width if they need to encode more than 256 variants. + /// + /// Note: This definition must not be used if the tag is not encoded + /// using little-endian format. + tag_width: u8, + + /// Possible variants of the enumeration. variants: Vec<(VariantName, Declaration)>, }, + /// A structure, structurally similar to a tuple. Struct { fields: Fields }, } @@ -281,6 +312,7 @@ where { fn add_definitions_recursively(definitions: &mut BTreeMap) { let definition = Definition::Enum { + tag_width: 1, variants: vec![ ("None".to_string(), <()>::declaration()), ("Some".to_string(), T::declaration()), @@ -302,6 +334,7 @@ where { fn add_definitions_recursively(definitions: &mut BTreeMap) { let definition = Definition::Enum { + tag_width: 1, variants: vec![ ("Ok".to_string(), T::declaration()), ("Err".to_string(), E::declaration()), @@ -521,11 +554,14 @@ mod tests { Option::::add_definitions_recursively(&mut actual_defs); assert_eq!("Option", actual_name); assert_eq!( - map! {"Option" => - Definition::Enum{ variants: vec![ - ("None".to_string(), "nil".to_string()), - ("Some".to_string(), "u64".to_string()), - ]} + map! { + "Option" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("None".to_string(), "nil".to_string()), + ("Some".to_string(), "u64".to_string()), + ] + } }, actual_defs ); @@ -539,16 +575,20 @@ mod tests { assert_eq!("Option>", actual_name); assert_eq!( map! { - "Option" => - Definition::Enum {variants: vec![ - ("None".to_string(), "nil".to_string()), - ("Some".to_string(), "u64".to_string()), - ]}, - "Option>" => - Definition::Enum {variants: vec![ - ("None".to_string(), "nil".to_string()), - ("Some".to_string(), "Option".to_string()), - ]} + "Option" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("None".to_string(), "nil".to_string()), + ("Some".to_string(), "u64".to_string()), + ] + }, + "Option>" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("None".to_string(), "nil".to_string()), + ("Some".to_string(), "Option".to_string()), + ] + } }, actual_defs ); diff --git a/borsh/src/schema_helpers.rs b/borsh/src/schema_helpers.rs index ae7c73dc1..76081ee04 100644 --- a/borsh/src/schema_helpers.rs +++ b/borsh/src/schema_helpers.rs @@ -111,6 +111,12 @@ fn is_zero_size(declaration: &str, schema: &BorshSchemaContainer) -> bool { Ok(Definition::Tuple { elements }) => elements .iter() .all(|element| is_zero_size(element.as_str(), schema)), + Ok(Definition::Enum { + tag_width: 0, + variants, + }) => variants + .iter() + .all(|variant| is_zero_size(&variant.1, schema)), Ok(Definition::Enum { .. }) => false, Ok(Definition::Struct { fields }) => match fields { Fields::NamedFields(fields) => fields @@ -187,14 +193,17 @@ fn max_serialized_size_impl<'a>( mul(count, add(sz, 4)?) } - Ok(Definition::Enum { variants }) => { - // Size of an enum is the largest variant plus one for tag. + Ok(Definition::Enum { + tag_width, + variants, + }) => { let mut max = 0; for (_, variant) in variants { let sz = max_serialized_size_impl(1, variant, schema, stack)?; max = max.max(sz); } - max.checked_add(1).ok_or(MaxSizeError::Overflow) + max.checked_add(usize::from(*tag_width)) + .ok_or(MaxSizeError::Overflow) } // Tuples and structs sum sizes of all the members. @@ -225,80 +234,131 @@ fn max_serialized_size_impl<'a>( Ok(res) } -#[test] -fn test_max_serialized_size() { - #[cfg(not(feature = "std"))] +#[cfg(test)] +mod tests { + use super::*; + use alloc::{ boxed::Box, + collections::BTreeMap, string::{String, ToString}, + vec, }; #[track_caller] fn test_ok(want: usize) { - let schema = borsh::schema::BorshSchemaContainer::for_type::(); + let schema = BorshSchemaContainer::for_type::(); assert_eq!(Ok(want), max_serialized_size(&schema)); + assert_eq!( + want == 0, + is_zero_size(schema.declaration().as_str(), &schema) + ); } #[track_caller] fn test_err(err: MaxSizeError) { - let schema = borsh::schema::BorshSchemaContainer::for_type::(); + let schema = BorshSchemaContainer::for_type::(); assert_eq!(Err(err), max_serialized_size(&schema)); } const MAX_LEN: usize = u32::MAX as usize; - test_ok::(2); - test_ok::(8); + #[test] + fn max_serialized_size_built_in_types() { + test_ok::(2); + test_ok::(8); - test_ok::>(1); - test_ok::>(2); - test_ok::>(9); + test_ok::>(1); + test_ok::>(2); + test_ok::>(9); - test_ok::<()>(0); - test_ok::<(u8,)>(1); - test_ok::<(u8, u32)>(5); + test_ok::<()>(0); + test_ok::<(u8,)>(1); + test_ok::<(u8, u32)>(5); - test_ok::<[u8; 0]>(0); - test_ok::<[u8; 16]>(16); - test_ok::<[[u8; 4]; 4]>(16); + test_ok::<[u8; 0]>(0); + test_ok::<[u8; 16]>(16); + test_ok::<[[u8; 4]; 4]>(16); - test_ok::>(4 + MAX_LEN); - test_ok::(4 + MAX_LEN); + test_ok::>(4 + MAX_LEN); + test_ok::(4 + MAX_LEN); - test_err::>>(MaxSizeError::Overflow); - test_ok::>>(4 + MAX_LEN * 4); - test_ok::<[[[(); MAX_LEN]; MAX_LEN]; MAX_LEN]>(0); + test_err::>>(MaxSizeError::Overflow); + test_ok::>>(4 + MAX_LEN * 4); + test_ok::<[[[(); MAX_LEN]; MAX_LEN]; MAX_LEN]>(0); + } - use crate as borsh; + #[test] + fn max_serialized_size_derived_types() { + use crate as borsh; - #[derive(::borsh_derive::BorshSchema)] - pub struct Empty; + #[derive(::borsh_derive::BorshSchema)] + pub struct Empty; - #[derive(::borsh_derive::BorshSchema)] - pub struct Named { - _foo: usize, - _bar: [u8; 15], - } + #[derive(::borsh_derive::BorshSchema)] + pub struct Named { + _foo: usize, + _bar: [u8; 15], + } + + #[derive(::borsh_derive::BorshSchema)] + pub struct Unnamed(usize, [u8; 15]); + + #[derive(::borsh_derive::BorshSchema)] + struct Multiple { + _usz0: usize, + _usz1: usize, + _usz2: usize, + _vec0: Vec, + _vec1: Vec, + } - #[derive(::borsh_derive::BorshSchema)] - pub struct Unnamed(usize, [u8; 15]); + #[derive(::borsh_derive::BorshSchema)] + struct Recursive(Option>); - #[derive(::borsh_derive::BorshSchema)] - struct Multiple { - _usz0: usize, - _usz1: usize, - _usz2: usize, - _vec0: Vec, - _vec1: Vec, + test_ok::(0); + test_ok::(23); + test_ok::(23); + test_ok::(3 * 8 + 2 * (4 + MAX_LEN * 8)); + test_err::(MaxSizeError::Overflow); + test_err::(MaxSizeError::Recursive); } - #[derive(::borsh_derive::BorshSchema)] - struct Recursive(Option>); + #[test] + fn max_serialized_size_custom_enum() { + #[allow(dead_code)] + enum Maybe { + Just(T), + Nothing, + } + + impl BorshSchema for Maybe { + fn declaration() -> Declaration { + "Maybe".into() + } + fn add_definitions_recursively(definitions: &mut BTreeMap) { + let definition = Definition::Enum { + tag_width: N as u8, + variants: vec![ + ("Just".into(), T::declaration()), + ("Nothing".into(), "nil".into()), + ], + }; + crate::schema::add_definition(Self::declaration(), definition, definitions); + T::add_definitions_recursively(definitions); + } + } + + test_ok::>(0); + test_ok::>(2); + test_ok::>(8); - test_ok::(0); - test_ok::(23); - test_ok::(23); - test_ok::(3 * 8 + 2 * (4 + MAX_LEN * 8)); - test_err::(MaxSizeError::Overflow); - test_err::(MaxSizeError::Recursive); + test_ok::>(1); + test_ok::>(3); + test_ok::>(9); + + test_ok::>(4); + test_ok::>(6); + test_ok::>(12); + } } diff --git a/borsh/tests/snapshots/test_schema_enums__complex_enum_with_schema.snap b/borsh/tests/snapshots/test_schema_enums__complex_enum_with_schema.snap index 5acee471b..a9c7f34d7 100644 --- a/borsh/tests/snapshots/test_schema_enums__complex_enum_with_schema.snap +++ b/borsh/tests/snapshots/test_schema_enums__complex_enum_with_schema.snap @@ -18,6 +18,7 @@ expression: data 0, 65, 3, + 1, 4, 0, 0, diff --git a/borsh/tests/test_schema_enums.rs b/borsh/tests/test_schema_enums.rs index 4e902bcc7..30338b91f 100644 --- a/borsh/tests/test_schema_enums.rs +++ b/borsh/tests/test_schema_enums.rs @@ -46,7 +46,10 @@ pub fn simple_enum() { map! { "ABacon" => Definition::Struct{ fields: Fields::Empty }, "AEggs" => Definition::Struct{ fields: Fields::Empty }, - "A" => Definition::Enum { variants: vec![("Bacon".to_string(), "ABacon".to_string()), ("Eggs".to_string(), "AEggs".to_string())]} + "A" => Definition::Enum { + tag_width: 1, + variants: vec![("Bacon".to_string(), "ABacon".to_string()), ("Eggs".to_string(), "AEggs".to_string())] + } }, defs ); @@ -63,8 +66,11 @@ pub fn single_field_enum() { A::add_definitions_recursively(&mut defs); assert_eq!( map! { - "ABacon" => Definition::Struct {fields: Fields::Empty}, - "A" => Definition::Enum { variants: vec![("Bacon".to_string(), "ABacon".to_string())]} + "ABacon" => Definition::Struct {fields: Fields::Empty}, + "A" => Definition::Enum { + tag_width: 1, + variants: vec![("Bacon".to_string(), "ABacon".to_string())] + } }, defs ); @@ -176,11 +182,15 @@ pub fn complex_enum_with_schema() { "ASalad" => Definition::Struct{ fields: Fields::UnnamedFields(vec!["Tomatoes".to_string(), "Cucumber".to_string(), "Oil".to_string()])}, "ABacon" => Definition::Struct {fields: Fields::Empty}, "Oil" => Definition::Struct {fields: Fields::Empty}, - "A" => Definition::Enum{ variants: vec![ - ("Bacon".to_string(), "ABacon".to_string()), - ("Eggs".to_string(), "AEggs".to_string()), - ("Salad".to_string(), "ASalad".to_string()), - ("Sausage".to_string(), "ASausage".to_string())]}, + "A" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("Bacon".to_string(), "ABacon".to_string()), + ("Eggs".to_string(), "AEggs".to_string()), + ("Salad".to_string(), "ASalad".to_string()), + ("Sausage".to_string(), "ASausage".to_string()) + ] + }, "Wrapper" => Definition::Struct {fields: Fields::Empty}, "Tomatoes" => Definition::Struct {fields: Fields::Empty}, "ASausage" => Definition::Struct { fields: Fields::NamedFields(vec![ @@ -234,12 +244,13 @@ pub fn complex_enum_generics() { }, "ABacon" => Definition::Struct {fields: Fields::Empty}, "Oil" => Definition::Struct {fields: Fields::Empty}, - "A" => Definition::Enum{ + "A" => Definition::Enum { + tag_width: 1, variants: vec![ - ("Bacon".to_string(), "ABacon".to_string()), - ("Eggs".to_string(), "AEggs".to_string()), - ("Salad".to_string(), "ASalad".to_string()), - ("Sausage".to_string(), "ASausage".to_string()) + ("Bacon".to_string(), "ABacon".to_string()), + ("Eggs".to_string(), "AEggs".to_string()), + ("Salad".to_string(), "ASalad".to_string()), + ("Sausage".to_string(), "ASausage".to_string()) ] }, "Wrapper" => Definition::Struct {fields: Fields::Empty}, @@ -259,10 +270,13 @@ pub fn complex_enum_generics() { fn common_map() -> BTreeMap { map! { - "EnumParametrized" => Definition::Enum{ variants: vec![ + "EnumParametrized" => Definition::Enum { + tag_width: 1, + variants: vec![ ("B".to_string(), "EnumParametrizedB".to_string()), ("C".to_string(), "EnumParametrizedC".to_string()) - ]}, + ] + }, "EnumParametrizedB" => Definition::Struct { fields: Fields::NamedFields(vec![ ("x".to_string(), "BTreeMap".to_string()), ("y".to_string(), "string".to_string()), diff --git a/borsh/tests/test_schema_nested.rs b/borsh/tests/test_schema_nested.rs index 99c40d16f..550ed68f8 100644 --- a/borsh/tests/test_schema_nested.rs +++ b/borsh/tests/test_schema_nested.rs @@ -68,17 +68,24 @@ pub fn duplicated_instantiations() { >>::add_definitions_recursively(&mut defs); assert_eq!( map! { - "A>" => Definition::Enum {variants: vec![ - ("Bacon".to_string(), "ABacon".to_string()), - ("Eggs".to_string(), "AEggs".to_string()), - ("Salad".to_string(), "ASalad".to_string()), - ("Sausage".to_string(), "ASausage>".to_string()) - ]}, - "A" => Definition::Enum {variants: vec![ - ("Bacon".to_string(), "ABacon".to_string()), - ("Eggs".to_string(), "AEggs".to_string()), - ("Salad".to_string(), "ASalad".to_string()), - ("Sausage".to_string(), "ASausage".to_string())]}, + "A>" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("Bacon".to_string(), "ABacon".to_string()), + ("Eggs".to_string(), "AEggs".to_string()), + ("Salad".to_string(), "ASalad".to_string()), + ("Sausage".to_string(), "ASausage>".to_string()) + ] + }, + "A" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("Bacon".to_string(), "ABacon".to_string()), + ("Eggs".to_string(), "AEggs".to_string()), + ("Salad".to_string(), "ASalad".to_string()), + ("Sausage".to_string(), "ASausage".to_string()) + ] + }, "ABacon" => Definition::Struct {fields: Fields::Empty}, "AEggs" => Definition::Struct {fields: Fields::Empty}, "ASalad" => Definition::Struct {fields: Fields::UnnamedFields(vec!["Tomatoes".to_string(), "Cucumber".to_string(), "Oil".to_string()])}, @@ -89,8 +96,20 @@ pub fn duplicated_instantiations() { "Filling" => Definition::Struct {fields: Fields::Empty}, "HashMap" => Definition::Sequence { elements: "Tuple".to_string()}, "Oil" => Definition::Struct { fields: Fields::NamedFields(vec![("seeds".to_string(), "HashMap".to_string()), ("liquid".to_string(), "Option".to_string())])}, - "Option" => Definition::Enum {variants: vec![("None".to_string(), "nil".to_string()), ("Some".to_string(), "string".to_string())]}, - "Option" => Definition::Enum { variants: vec![("None".to_string(), "nil".to_string()), ("Some".to_string(), "u64".to_string())]}, + "Option" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("None".to_string(), "nil".to_string()), + ("Some".to_string(), "string".to_string()) + ] + }, + "Option" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("None".to_string(), "nil".to_string()), + ("Some".to_string(), "u64".to_string()) + ] + }, "Tomatoes" => Definition::Struct {fields: Fields::Empty}, "Tuple" => Definition::Tuple {elements: vec!["u64".to_string(), "string".to_string()]}, "Wrapper" => Definition::Struct{ fields: Fields::NamedFields(vec![("foo".to_string(), "Option".to_string()), ("bar".to_string(), "A".to_string())])} diff --git a/borsh/tests/test_schema_recursive.rs b/borsh/tests/test_schema_recursive.rs index 482b14afe..ee4a1f3ea 100644 --- a/borsh/tests/test_schema_recursive.rs +++ b/borsh/tests/test_schema_recursive.rs @@ -76,39 +76,24 @@ pub fn recursive_enum_schema() { assert_eq!( map! { "ERecD" => Definition::Enum { + tag_width: 1, variants: vec![ - - ( - "B".to_string(), - "ERecDB".to_string(), - ), - ( - "C".to_string(), - "ERecDC".to_string(), - ), + ("B".to_string(), "ERecDB".to_string()), + ("C".to_string(), "ERecDC".to_string()), ] }, "ERecDB" => Definition::Struct { - fields: Fields::NamedFields ( vec![ - ( - "x".to_string(), - "string".to_string(), - ), - ( - "y".to_string(), - "i32".to_string(), - ), + ("x".to_string(), "string".to_string()), + ("y".to_string(), "i32".to_string()), ] - ) }, "ERecDC" => Definition::Struct { fields: Fields::UnnamedFields( vec![ "u8".to_string(), "Vec".to_string(), - ]) }, "Vec" => Definition::Sequence { diff --git a/borsh/tests/test_schema_with.rs b/borsh/tests/test_schema_with.rs index 11550c9de..5274f8b56 100644 --- a/borsh/tests/test_schema_with.rs +++ b/borsh/tests/test_schema_with.rs @@ -130,10 +130,13 @@ pub fn enum_overriden() { >::add_definitions_recursively(&mut defs); assert_eq!( map! { - "C" => Definition::Enum { variants: vec![ - ("C3".to_string(), "CC3".to_string()), - ("C4".to_string(), "CC4".to_string()) - ] }, + "C" => Definition::Enum { + tag_width: 1, + variants: vec![ + ("C3".to_string(), "CC3".to_string()), + ("C4".to_string(), "CC4".to_string()) + ] + }, "CC3" => Definition::Struct { fields: Fields::UnnamedFields(vec!["u64".to_string(), "u64".to_string()]) }, "CC4" => Definition::Struct { fields: Fields::UnnamedFields(vec![ "u64".to_string(), "ThirdParty".to_string()