From 246f08079ce3dee858a353fe0ea2d6cacce3621e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 13 Sep 2023 21:03:33 +0300 Subject: [PATCH 1/4] chore: add recursive check to `is_zero_size`; make `max_serialized_size` a container method --- borsh/src/lib.rs | 3 +- borsh/src/schema.rs | 4 + borsh/src/schema/container_ext.rs | 7 + borsh/src/schema/container_ext/max_size.rs | 421 +++++++++++++++++++++ borsh/src/schema_helpers.rs | 351 +---------------- 5 files changed, 439 insertions(+), 347 deletions(-) create mode 100644 borsh/src/schema/container_ext.rs create mode 100644 borsh/src/schema/container_ext/max_size.rs diff --git a/borsh/src/lib.rs b/borsh/src/lib.rs index 5049dd047..0fcab9a0a 100644 --- a/borsh/src/lib.rs +++ b/borsh/src/lib.rs @@ -91,8 +91,7 @@ pub use de::{from_reader, from_slice}; pub use schema::BorshSchema; #[cfg(feature = "unstable__schema")] pub use schema_helpers::{ - max_serialized_size, schema_container_of, try_from_slice_with_schema, try_to_vec_with_schema, - MaxSizeError, + max_size_of, schema_container_of, try_from_slice_with_schema, try_to_vec_with_schema, }; pub use ser::helpers::{to_vec, to_writer}; pub use ser::BorshSerialize; diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs index 826fd4638..452975192 100644 --- a/borsh/src/schema.rs +++ b/borsh/src/schema.rs @@ -27,6 +27,10 @@ use core::borrow::Borrow; use core::cmp::Ord; use core::marker::PhantomData; +mod container_ext; + +pub use container_ext::MaxSizeError; + /// The type that we use to represent the declaration of the Borsh type. pub type Declaration = String; /// The type that we use for the name of the variant. diff --git a/borsh/src/schema/container_ext.rs b/borsh/src/schema/container_ext.rs new file mode 100644 index 000000000..4bc73d31d --- /dev/null +++ b/borsh/src/schema/container_ext.rs @@ -0,0 +1,7 @@ +use super::{BorshSchemaContainer, Declaration, Definition, Fields}; + +// #[allow(unused_imports)] +// pub(self) use max_size::is_zero_size; +pub use max_size::MaxSizeError; + +mod max_size; diff --git a/borsh/src/schema/container_ext/max_size.rs b/borsh/src/schema/container_ext/max_size.rs new file mode 100644 index 000000000..ee4945e1f --- /dev/null +++ b/borsh/src/schema/container_ext/max_size.rs @@ -0,0 +1,421 @@ +use super::{BorshSchemaContainer, Declaration, Definition, Fields}; +use crate::__private::maybestd::{string::ToString, vec::Vec}; + +impl BorshSchemaContainer { + /// Returns the largest possible size of a serialised object based solely on its type. + /// + /// Zero-sized types should follow the convention of either providing a [Definition] or + /// specifying `"nil"` as their [Declaration] for this method to work correctly. + /// + /// The function has limitations which may lead it to overestimate the size. + /// For example, hypothetical `IPv4Packet` would be encoded as at most ~64 KiB. + /// However, if it uses sequence schema, this function will claim that the + /// maximum size is ~4 GiB. + /// + /// Even when if returned upper bound is correct, the theoretical value may be + /// *much* larger than any practical length. For example, maximum encoded + /// length of `String` is 4 GiB while in practice one may encounter strings of + /// at most dozen of characters. Depending on usage, caller should apply upper + /// bound on the result. + /// + /// # Example + /// + /// ``` + /// use borsh::schema::BorshSchemaContainer; + /// + /// let schema = BorshSchemaContainer::for_type::<()>(); + /// assert_eq!(Ok(0), schema.max_serialized_size()); + /// + /// let schema = BorshSchemaContainer::for_type::(); + /// assert_eq!(Ok(8), schema.max_serialized_size()); + /// + /// // 4 bytes of length and u32::MAX for the longest possible string. + /// let schema = BorshSchemaContainer::for_type::(); + /// assert_eq!(Ok(4 + 4294967295), schema.max_serialized_size()); + /// + /// let schema = BorshSchemaContainer::for_type::>(); + /// assert_eq!(Err(borsh::schema::MaxSizeError::Overflow), + /// schema.max_serialized_size()); + /// ``` + pub fn max_serialized_size(&self) -> core::result::Result { + let mut stack = Vec::new(); + max_serialized_size_impl(1, self.declaration(), self, &mut stack) + } +} + +/// Possible error when calculating theoretical maximum size of encoded type. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum MaxSizeError { + /// The theoretical maximum size of the encoded value overflows `usize`. + /// + /// This may happen for nested dynamically-sized types such as + /// `Vec>` whose maximum size is `4 + u32::MAX * (4 + u32::MAX)`. + Overflow, + + /// The type is recursive and thus theoretical maximum size is infinite. + /// + /// Simple type in which this triggers is `struct Rec(Option>)`. + Recursive, + + /// Some of the declared types were lacking definition making it impossible + /// to calculate the size. + MissingDefinition, + + /// sequences of zero sized types of dynamic length are forbidden by definition + /// see and related ones + ZSTSequenceNotArray, +} + +/// Implementation of [`BorshSchema::max_serialized_size`]. +fn max_serialized_size_impl<'a>( + count: usize, + declaration: &'a str, + schema: &'a BorshSchemaContainer, + stack: &mut Vec<&'a str>, +) -> core::result::Result { + use core::convert::TryFrom; + + /// Maximum number of elements in a vector or length of a string which can + /// be serialised. + const MAX_LEN: usize = u32::MAX as usize; + + fn add(x: usize, y: usize) -> core::result::Result { + x.checked_add(y).ok_or(MaxSizeError::Overflow) + } + + fn mul(x: usize, y: usize) -> core::result::Result { + x.checked_mul(y).ok_or(MaxSizeError::Overflow) + } + + /// Calculates max serialised size of a tuple with given members. + fn tuple<'a>( + count: usize, + elements: impl core::iter::IntoIterator, + schema: &'a BorshSchemaContainer, + stack: &mut Vec<&'a str>, + ) -> ::core::result::Result { + let mut sum: usize = 0; + for el in elements { + sum = add(sum, max_serialized_size_impl(1, el, schema, stack)?)?; + } + mul(count, sum) + } + + if stack.iter().any(|dec| *dec == declaration) { + return Err(MaxSizeError::Recursive); + } + stack.push(declaration); + + let res = match schema.get_definition(declaration).ok_or(declaration) { + Ok(Definition::Array { length, elements }) => { + // Aggregate `count` and `length` to a single number. If this + // overflows, check if array’s element is zero-sized. + let count = usize::try_from(*length) + .ok() + .and_then(|len| len.checked_mul(count)); + match count { + Some(0) => Ok(0), + Some(count) => max_serialized_size_impl(count, elements, schema, stack), + None if is_zero_size(elements, schema) + .map_err(|_err| MaxSizeError::Recursive)? => + { + Ok(0) + } + None => Err(MaxSizeError::Overflow), + } + } + Ok(Definition::Sequence { elements }) => { + if is_zero_size(elements, schema).map_err(|_err| MaxSizeError::Recursive)? { + return Err(MaxSizeError::ZSTSequenceNotArray); + } + // Assume that sequence has MAX_LEN elements since that’s the most + // it can have. + let sz = max_serialized_size_impl(MAX_LEN, elements, schema, stack)?; + mul(count, add(sz, 4)?) + } + + 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(usize::from(*tag_width)) + .ok_or(MaxSizeError::Overflow) + } + + // Tuples and structs sum sizes of all the members. + Ok(Definition::Tuple { elements }) => tuple(count, elements, schema, stack), + Ok(Definition::Struct { fields }) => match fields { + Fields::NamedFields(fields) => { + tuple(count, fields.iter().map(|(_, field)| field), schema, stack) + } + Fields::UnnamedFields(fields) => tuple(count, fields, schema, stack), + Fields::Empty => Ok(0), + }, + + // Primitive types. + Err("nil") => Ok(0), + Err("bool" | "i8" | "u8" | "nonzero_i8" | "nonzero_u8") => Ok(count), + Err("i16" | "u16" | "nonzero_i16" | "nonzero_u16") => mul(count, 2), + Err("i32" | "u32" | "f32" | "nonzero_i32" | "nonzero_u32") => mul(count, 4), + Err("i64" | "u64" | "f64" | "nonzero_i64" | "nonzero_u64") => mul(count, 8), + Err("i128" | "u128" | "nonzero_i128" | "nonzero_u128") => mul(count, 16), + + // string is just Vec + Err("string") => mul(count, add(MAX_LEN, 4)?), + + Err(_) => Err(MaxSizeError::MissingDefinition), + }?; + + stack.pop(); + Ok(res) +} +/// Checks whether given declaration schema serialises to an empty string. +/// +/// Certain types always serialise to an empty string (most notably `()`). This +/// function checks whether `declaration` is one of such types. +/// +/// This is used by [`BorshSchema::max_serialized_size()`] to handle weird types +/// such as `[[[(); u32::MAX]; u32::MAX]; u32::MAX]` which serialises to an +/// empty string even though its number of elements overflows `usize`. +/// +/// Error value means that the method has been called recursively. +pub(super) fn is_zero_size( + declaration: &Declaration, + schema: &BorshSchemaContainer, +) -> Result { + let mut stack = Vec::new(); + is_zero_size_impl(declaration, schema, &mut stack) +} +const RECURSIVE: () = (); + +fn is_zero_size_impl<'a>( + declaration: &'a str, + schema: &'a BorshSchemaContainer, + stack: &mut Vec, +) -> Result { + println!("is_zero_size_impl :{}", declaration); + fn all( + iter: impl Iterator, + f_key: impl Fn(&T) -> &Declaration, + schema: &BorshSchemaContainer, + stack: &mut Vec, + ) -> Result { + let mut all = true; + for element in iter { + let declaration = f_key(&element); + if !is_zero_size_impl(declaration.as_str(), schema, stack)? { + all = false; + } + } + Ok(all) + } + + if stack.iter().any(|dec| *dec == declaration) { + return Err(RECURSIVE); + } + stack.push(declaration.to_string()); + + let res = match schema.get_definition(declaration).ok_or(declaration) { + Ok(Definition::Array { length, elements }) => { + *length == 0 || is_zero_size_impl(elements.as_str(), schema, stack)? + } + Ok(Definition::Sequence { .. }) => false, + Ok(Definition::Tuple { elements }) => all(elements.iter(), |key| *key, schema, stack)?, + Ok(Definition::Enum { + tag_width: 0, + variants, + }) => all( + variants.iter(), + |(_variant_name, declaration)| declaration, + schema, + stack, + )?, + Ok(Definition::Enum { .. }) => false, + Ok(Definition::Struct { fields }) => match fields { + Fields::NamedFields(fields) => all( + fields.iter(), + |(_field_name, declaration)| declaration, + schema, + stack, + )?, + Fields::UnnamedFields(fields) => { + all(fields.iter(), |declaration| declaration, schema, stack)? + } + Fields::Empty => true, + }, + + Err(dec) => dec == "nil", + }; + stack.pop(); + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + + // this is not integration test module, so can use __private for ease of imports; + // it cannot be made integration, as it tests `is_zero_size` function, chosen to be non-pub + use crate::{ + BorshSchema, + __private::maybestd::{ + boxed::Box, + collections::BTreeMap, + format, + string::{String, ToString}, + vec, + }, + }; + + #[track_caller] + fn test_ok(want: usize) { + let schema = BorshSchemaContainer::for_type::(); + assert_eq!(Ok(want), schema.max_serialized_size()); + assert_eq!( + want == 0, + is_zero_size(schema.declaration(), &schema).unwrap() + ); + } + + #[track_caller] + fn test_err(err: MaxSizeError) { + let schema = BorshSchemaContainer::for_type::(); + assert_eq!(Err(err), schema.max_serialized_size()); + } + + const MAX_LEN: usize = u32::MAX as usize; + + #[test] + fn test_is_zero_size_recursive_check_bypassed() { + use crate as borsh; + + #[derive(::borsh_derive::BorshSchema)] + struct RecursiveExitSequence(Vec); + + let schema = BorshSchemaContainer::for_type::(); + assert_eq!(Ok(false), is_zero_size(schema.declaration(), &schema)); + } + + #[test] + fn test_is_zero_size_no_recursive_check_err() { + use crate as borsh; + + #[derive(::borsh_derive::BorshSchema)] + struct RecursiveNoExitStructUnnamed(Box); + + let schema = BorshSchemaContainer::for_type::(); + assert_eq!(Err(()), is_zero_size(schema.declaration(), &schema)); + } + + #[test] + fn max_serialized_size_built_in_types() { + test_ok::(2); + test_ok::(8); + + test_ok::(2); + test_ok::(4); + + test_ok::(0); + test_ok::>(2); + test_ok::>(8); + + test_ok::>(1); + test_ok::>(2); + test_ok::>(9); + + 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::>(4 + MAX_LEN); + test_ok::(4 + MAX_LEN); + + test_err::>>(MaxSizeError::Overflow); + test_err::>>(MaxSizeError::ZSTSequenceNotArray); + test_ok::<[[[(); MAX_LEN]; MAX_LEN]; MAX_LEN]>(0); + } + + #[test] + fn max_serialized_size_derived_types() { + use crate as borsh; + + #[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 Unnamed(usize, [u8; 15]); + + #[derive(::borsh_derive::BorshSchema)] + struct Multiple { + _usz0: usize, + _usz1: usize, + _usz2: usize, + _vec0: Vec, + _vec1: Vec, + } + + #[derive(::borsh_derive::BorshSchema)] + struct Recursive(Option>); + + 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] + fn max_serialized_size_custom_enum() { + #[allow(dead_code)] + enum Maybe { + Just(T), + Nothing, + } + + impl BorshSchema for Maybe { + fn declaration() -> Declaration { + let res = format!(r#"Maybe<{}>"#, T::declaration()); + res + } + 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::>(1); + test_ok::>(3); + test_ok::>(9); + + test_ok::>(4); + test_ok::>(6); + test_ok::>(12); + } +} diff --git a/borsh/src/schema_helpers.rs b/borsh/src/schema_helpers.rs index 6cf826042..977f36477 100644 --- a/borsh/src/schema_helpers.rs +++ b/borsh/src/schema_helpers.rs @@ -1,7 +1,7 @@ use crate::__private::maybestd::vec::Vec; use crate::from_slice; use crate::io::{Error, ErrorKind, Result}; -use crate::schema::{BorshSchemaContainer, Declaration, Definition, Fields}; +use crate::schema::{BorshSchemaContainer, MaxSizeError}; use crate::{BorshDeserialize, BorshSchema, BorshSerialize}; /// Deserialize this instance from a slice of bytes, but assume that at the beginning we have @@ -34,349 +34,10 @@ pub fn schema_container_of() -> BorshSchemaContainer { BorshSchemaContainer::for_type::() } -/// Possible error when calculating theoretical maximum size of encoded type. +/// Returns the largest possible size of a serialised object based solely on its type `T`. /// -/// This is error returned by [`max_serialized_size`] function. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum MaxSizeError { - /// The theoretical maximum size of the encoded value overflows `usize`. - /// - /// This may happen for nested dynamically-sized types such as - /// `Vec>` whose maximum size is `4 + u32::MAX * (4 + u32::MAX)`. - Overflow, - - /// The type is recursive and thus theoretical maximum size is infinite. - /// - /// Simple type in which this triggers is `struct Rec(Option>)`. - Recursive, - - /// Some of the declared types were lacking definition making it impossible - /// to calculate the size. - MissingDefinition, - - /// sequences of zero sized types of dynamic length are forbidden by definition - /// see and related ones - ZSTSequenceNotArray, -} - -/// Returns the largest possible size of a serialised object based solely on its type. -/// -/// The function has limitations which may lead it to overestimate the size. -/// For example, hypothetical `IPv4Packet` would be encoded as at most ~64 KiB. -/// However, if it uses sequence schema, this function will claim that the -/// maximum size is ~4 GiB. -/// -/// Even when if returned upper bound is correct, the theoretical value may be -/// *much* larger than any practical length. For example, maximum encoded -/// length of `String` is 4 GiB while in practice one may encounter strings of -/// at most dozen of characters. Depending on usage, caller should apply upper -/// bound on the result. -/// -/// # Example -/// -/// ``` -/// use borsh::schema::BorshSchemaContainer; -/// -/// let schema = BorshSchemaContainer::for_type::<()>(); -/// assert_eq!(Ok(0), borsh::max_serialized_size(&schema)); -/// -/// let schema = BorshSchemaContainer::for_type::(); -/// assert_eq!(Ok(8), borsh::max_serialized_size(&schema)); -/// -/// // 4 bytes of length and u32::MAX for the longest possible string. -/// let schema = BorshSchemaContainer::for_type::(); -/// assert_eq!(Ok(4 + 4294967295), borsh::max_serialized_size(&schema)); -/// -/// let schema = BorshSchemaContainer::for_type::>(); -/// assert_eq!(Err(borsh::MaxSizeError::Overflow), -/// borsh::max_serialized_size(&schema)); -/// ``` -pub fn max_serialized_size( - schema: &BorshSchemaContainer, -) -> core::result::Result { - let mut stack = Vec::new(); - max_serialized_size_impl(1, schema.declaration(), schema, &mut stack) -} - -/// Checks whether given declaration schema serialises to an empty string. -/// -/// Certain types always serialise to an empty string (most notably `()`). This -/// function checks whether `declaration` is one of such types. -/// -/// This is used by [`BorshSchema::max_serialized_size()`] to handle weird types -/// such as `[[[(); u32::MAX]; u32::MAX]; u32::MAX]` which serialises to an -/// empty string even though its number of elements overflows `usize`. -fn is_zero_size(declaration: &str, schema: &BorshSchemaContainer) -> bool { - match schema.get_definition(declaration).ok_or(declaration) { - Ok(Definition::Array { length, elements }) => { - *length == 0 || is_zero_size(elements.as_str(), schema) - } - Ok(Definition::Sequence { .. }) => false, - 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 - .iter() - .all(|(_, field)| is_zero_size(field.as_str(), schema)), - Fields::UnnamedFields(fields) => fields - .iter() - .all(|field| is_zero_size(field.as_str(), schema)), - Fields::Empty => true, - }, - - Err(dec) => dec == "nil", - } -} - -/// Implementation of [`BorshSchema::max_serialized_size`]. -fn max_serialized_size_impl<'a>( - count: usize, - declaration: &'a str, - schema: &'a BorshSchemaContainer, - stack: &mut Vec<&'a str>, -) -> core::result::Result { - use core::convert::TryFrom; - - /// Maximum number of elements in a vector or length of a string which can - /// be serialised. - const MAX_LEN: usize = u32::MAX as usize; - - fn add(x: usize, y: usize) -> core::result::Result { - x.checked_add(y).ok_or(MaxSizeError::Overflow) - } - - fn mul(x: usize, y: usize) -> core::result::Result { - x.checked_mul(y).ok_or(MaxSizeError::Overflow) - } - - /// Calculates max serialised size of a tuple with given members. - fn tuple<'a>( - count: usize, - elements: impl core::iter::IntoIterator, - schema: &'a BorshSchemaContainer, - stack: &mut Vec<&'a str>, - ) -> ::core::result::Result { - let mut sum: usize = 0; - for el in elements { - sum = add(sum, max_serialized_size_impl(1, el, schema, stack)?)?; - } - mul(count, sum) - } - - if stack.iter().any(|dec| *dec == declaration) { - return Err(MaxSizeError::Recursive); - } - stack.push(declaration); - - let res = match schema.get_definition(declaration).ok_or(declaration) { - Ok(Definition::Array { length, elements }) => { - // Aggregate `count` and `length` to a single number. If this - // overflows, check if array’s element is zero-sized. - let count = usize::try_from(*length) - .ok() - .and_then(|len| len.checked_mul(count)); - match count { - Some(0) => Ok(0), - Some(count) => max_serialized_size_impl(count, elements, schema, stack), - None if is_zero_size(elements, schema) => Ok(0), - None => Err(MaxSizeError::Overflow), - } - } - Ok(Definition::Sequence { elements }) => { - if is_zero_size(elements, schema) { - return Err(MaxSizeError::ZSTSequenceNotArray); - } - // Assume that sequence has MAX_LEN elements since that’s the most - // it can have. - let sz = max_serialized_size_impl(MAX_LEN, elements, schema, stack)?; - mul(count, add(sz, 4)?) - } - - 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(usize::from(*tag_width)) - .ok_or(MaxSizeError::Overflow) - } - - // Tuples and structs sum sizes of all the members. - Ok(Definition::Tuple { elements }) => tuple(count, elements, schema, stack), - Ok(Definition::Struct { fields }) => match fields { - Fields::NamedFields(fields) => { - tuple(count, fields.iter().map(|(_, field)| field), schema, stack) - } - Fields::UnnamedFields(fields) => tuple(count, fields, schema, stack), - Fields::Empty => Ok(0), - }, - - // Primitive types. - Err("nil") => Ok(0), - Err("bool" | "i8" | "u8" | "nonzero_i8" | "nonzero_u8") => Ok(count), - Err("i16" | "u16" | "nonzero_i16" | "nonzero_u16") => mul(count, 2), - Err("i32" | "u32" | "f32" | "nonzero_i32" | "nonzero_u32") => mul(count, 4), - Err("i64" | "u64" | "f64" | "nonzero_i64" | "nonzero_u64") => mul(count, 8), - Err("i128" | "u128" | "nonzero_i128" | "nonzero_u128") => mul(count, 16), - - // string is just Vec - Err("string") => mul(count, add(MAX_LEN, 4)?), - - Err(_) => Err(MaxSizeError::MissingDefinition), - }?; - - stack.pop(); - Ok(res) -} - -#[cfg(test)] -mod tests { - use super::*; - - // this is not integration test module, so can use __private for ease of imports; - // it cannot be made integration, as it tests `is_zero_size` function, chosen to be non-pub - use crate::__private::maybestd::{ - boxed::Box, - collections::BTreeMap, - format, - string::{String, ToString}, - vec, - }; - - #[track_caller] - fn test_ok(want: usize) { - 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 = BorshSchemaContainer::for_type::(); - assert_eq!(Err(err), max_serialized_size(&schema)); - } - - const MAX_LEN: usize = u32::MAX as usize; - - #[test] - fn max_serialized_size_built_in_types() { - test_ok::(2); - test_ok::(8); - - test_ok::(2); - test_ok::(4); - - test_ok::(0); - test_ok::>(2); - test_ok::>(8); - - test_ok::>(1); - test_ok::>(2); - test_ok::>(9); - - 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::>(4 + MAX_LEN); - test_ok::(4 + MAX_LEN); - - test_err::>>(MaxSizeError::Overflow); - test_err::>>(MaxSizeError::ZSTSequenceNotArray); - test_ok::<[[[(); MAX_LEN]; MAX_LEN]; MAX_LEN]>(0); - } - - #[test] - fn max_serialized_size_derived_types() { - use crate as borsh; - - #[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 Unnamed(usize, [u8; 15]); - - #[derive(::borsh_derive::BorshSchema)] - struct Multiple { - _usz0: usize, - _usz1: usize, - _usz2: usize, - _vec0: Vec, - _vec1: Vec, - } - - #[derive(::borsh_derive::BorshSchema)] - struct Recursive(Option>); - - 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] - fn max_serialized_size_custom_enum() { - #[allow(dead_code)] - enum Maybe { - Just(T), - Nothing, - } - - impl BorshSchema for Maybe { - fn declaration() -> Declaration { - let res = format!(r#"Maybe<{}>"#, T::declaration()); - res - } - 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::>(1); - test_ok::>(3); - test_ok::>(9); - - test_ok::>(4); - test_ok::>(6); - test_ok::>(12); - } +/// this is a shortcut for using [BorshSchemaContainer::max_serialized_size] +pub fn max_size_of() -> core::result::Result { + let schema = BorshSchemaContainer::for_type::(); + schema.max_serialized_size() } From d790ba48d7ae307056fde5b4d51990532b27d62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 14 Sep 2023 14:33:44 +0300 Subject: [PATCH 2/4] feat: split out `ValidationError` from `MaxSizeError`; add `validate` method to `BorshSchemaContainer` --- borsh/src/schema.rs | 2 +- borsh/src/schema/container_ext.rs | 5 +- borsh/src/schema/container_ext/max_size.rs | 21 +++--- borsh/src/schema/container_ext/validate.rs | 79 ++++++++++++++++++++++ borsh/tests/test_schema_validate.rs | 61 +++++++++++++++++ 5 files changed, 152 insertions(+), 16 deletions(-) create mode 100644 borsh/src/schema/container_ext/validate.rs create mode 100644 borsh/tests/test_schema_validate.rs diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs index 452975192..212a63ac0 100644 --- a/borsh/src/schema.rs +++ b/borsh/src/schema.rs @@ -29,7 +29,7 @@ use core::marker::PhantomData; mod container_ext; -pub use container_ext::MaxSizeError; +pub use container_ext::{MaxSizeError, ValidateError}; /// The type that we use to represent the declaration of the Borsh type. pub type Declaration = String; diff --git a/borsh/src/schema/container_ext.rs b/borsh/src/schema/container_ext.rs index 4bc73d31d..a3e1da263 100644 --- a/borsh/src/schema/container_ext.rs +++ b/borsh/src/schema/container_ext.rs @@ -1,7 +1,8 @@ use super::{BorshSchemaContainer, Declaration, Definition, Fields}; -// #[allow(unused_imports)] -// pub(self) use max_size::is_zero_size; +use max_size::is_zero_size; pub use max_size::MaxSizeError; +pub use validate::ValidateError; mod max_size; +mod validate; diff --git a/borsh/src/schema/container_ext/max_size.rs b/borsh/src/schema/container_ext/max_size.rs index ee4945e1f..51f1ef954 100644 --- a/borsh/src/schema/container_ext/max_size.rs +++ b/borsh/src/schema/container_ext/max_size.rs @@ -60,10 +60,6 @@ pub enum MaxSizeError { /// Some of the declared types were lacking definition making it impossible /// to calculate the size. MissingDefinition, - - /// sequences of zero sized types of dynamic length are forbidden by definition - /// see and related ones - ZSTSequenceNotArray, } /// Implementation of [`BorshSchema::max_serialized_size`]. @@ -125,9 +121,6 @@ fn max_serialized_size_impl<'a>( } } Ok(Definition::Sequence { elements }) => { - if is_zero_size(elements, schema).map_err(|_err| MaxSizeError::Recursive)? { - return Err(MaxSizeError::ZSTSequenceNotArray); - } // Assume that sequence has MAX_LEN elements since that’s the most // it can have. let sz = max_serialized_size_impl(MAX_LEN, elements, schema, stack)?; @@ -176,14 +169,17 @@ fn max_serialized_size_impl<'a>( } /// Checks whether given declaration schema serialises to an empty string. /// -/// Certain types always serialise to an empty string (most notably `()`). This -/// function checks whether `declaration` is one of such types. +/// Zero-sized types should follow the convention of either providing a [Definition] or +/// specifying `"nil"` as their [Declaration] for this method to work correctly. /// -/// This is used by [`BorshSchema::max_serialized_size()`] to handle weird types +/// This is used by [`BorshSchemaContainer::max_serialized_size`] to handle weird types /// such as `[[[(); u32::MAX]; u32::MAX]; u32::MAX]` which serialises to an /// empty string even though its number of elements overflows `usize`. /// /// Error value means that the method has been called recursively. +/// A recursive type either has no exit, so it cannot be instantiated +/// or it uses `Definiotion::Enum` or `Definition::Sequence` to exit from recursion +/// which make it non-zero size pub(super) fn is_zero_size( declaration: &Declaration, schema: &BorshSchemaContainer, @@ -198,7 +194,6 @@ fn is_zero_size_impl<'a>( schema: &'a BorshSchemaContainer, stack: &mut Vec, ) -> Result { - println!("is_zero_size_impl :{}", declaration); fn all( iter: impl Iterator, f_key: impl Fn(&T) -> &Declaration, @@ -302,7 +297,7 @@ mod tests { } #[test] - fn test_is_zero_size_no_recursive_check_err() { + fn test_is_zero_size_recursive_check_err() { use crate as borsh; #[derive(::borsh_derive::BorshSchema)] @@ -340,7 +335,7 @@ mod tests { test_ok::(4 + MAX_LEN); test_err::>>(MaxSizeError::Overflow); - test_err::>>(MaxSizeError::ZSTSequenceNotArray); + test_ok::>>(4 + MAX_LEN * 4); test_ok::<[[[(); MAX_LEN]; MAX_LEN]; MAX_LEN]>(0); } diff --git a/borsh/src/schema/container_ext/validate.rs b/borsh/src/schema/container_ext/validate.rs new file mode 100644 index 000000000..71742c1b0 --- /dev/null +++ b/borsh/src/schema/container_ext/validate.rs @@ -0,0 +1,79 @@ +use super::is_zero_size; +use super::{BorshSchemaContainer, Declaration, Definition, Fields}; +use crate::__private::maybestd::vec::Vec; + +impl BorshSchemaContainer { + /// Validates container for violation of any well-known rules with + /// respect to `borsh` serialization. + /// + /// Zero-sized types should follow the convention of either providing a [Definition] or + /// specifying `"nil"` as their [Declaration] for this method to work correctly. + /// + pub fn validate(&self) -> core::result::Result<(), ValidateError> { + let mut stack = Vec::new(); + validate_impl(self.declaration(), self, &mut stack) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ValidateError { + /// sequences of zero-sized types of dynamic length are forbidden by definition + /// see and related ones + ZSTSequence, +} + +fn validate_impl<'a>( + declaration: &'a Declaration, + schema: &'a BorshSchemaContainer, + stack: &mut Vec<&'a Declaration>, +) -> core::result::Result<(), ValidateError> { + let definition = match schema.get_definition(declaration) { + Some(definition) => definition, + // it's not an error for a type to not contain any definition + // it's either a `borsh`'s lib type like `"string"` or type declared by user + None => { + return Ok(()); + } + }; + if stack.iter().any(|dec| *dec == declaration) { + return Ok(()); + } + stack.push(declaration); + match definition { + Definition::Array { elements, .. } => validate_impl(elements, schema, stack)?, + Definition::Sequence { elements } => { + // a recursive type either has no exit, so it cannot be instantiated + // or it uses `Definiotion::Enum` or `Definition::Sequence` to exit from recursion + // which make it non-zero size + if is_zero_size(elements, schema).unwrap_or(false) { + return Err(ValidateError::ZSTSequence); + } + validate_impl(elements, schema, stack)?; + } + Definition::Enum { variants, .. } => { + for (_, variant) in variants { + validate_impl(variant, schema, stack)?; + } + } + Definition::Tuple { elements } => { + for element_type in elements { + validate_impl(element_type, schema, stack)?; + } + } + Definition::Struct { fields } => match fields { + Fields::NamedFields(fields) => { + for (_field_name, field_type) in fields { + validate_impl(field_type, schema, stack)?; + } + } + Fields::UnnamedFields(fields) => { + for field_type in fields { + validate_impl(field_type, schema, stack)?; + } + } + Fields::Empty => {} + }, + }; + stack.pop(); + Ok(()) +} diff --git a/borsh/tests/test_schema_validate.rs b/borsh/tests/test_schema_validate.rs new file mode 100644 index 000000000..ad4369495 --- /dev/null +++ b/borsh/tests/test_schema_validate.rs @@ -0,0 +1,61 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg(feature = "unstable__schema")] + +#[cfg(not(feature = "std"))] +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, string::ToString, vec::Vec}; + +use borsh::schema::*; +use borsh::BorshSchema; + +#[track_caller] +fn test_ok() { + let schema = BorshSchemaContainer::for_type::(); + assert_eq!(Ok(()), schema.validate()); +} + +#[track_caller] +fn test_err(err: ValidateError) { + let schema = BorshSchemaContainer::for_type::(); + assert_eq!(Err(err), schema.validate()); +} + +#[test] +fn validate_for_derived_types() { + #[derive(BorshSchema)] + pub struct Empty; + + #[derive(BorshSchema)] + pub struct Named { + _foo: usize, + _bar: [u8; 15], + } + + #[derive(BorshSchema)] + pub struct Unnamed(usize, [u8; 15]); + + #[derive(BorshSchema)] + struct Recursive(Option>); + + #[derive(BorshSchema)] + struct RecursiveSequence(Vec); + + // thankfully, this one cannot be constructed + #[derive(BorshSchema)] + struct RecursiveArray(Box<[RecursiveArray; 3]>); + + test_ok::(); + test_ok::(); + test_ok::(); + test_ok::(); + test_ok::(); + test_ok::(); + test_ok::(); +} + +#[test] +fn validate_for_zst_sequences() { + test_err::>>(ValidateError::ZSTSequence); + test_err::>(ValidateError::ZSTSequence); +} From 1a855203c2f1ef6e6c0c75f2866c0f204268c9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 15 Sep 2023 09:54:37 +0300 Subject: [PATCH 3/4] chore: fix review comments + attach `Declaration` tuple field to `SchemaMaxSerializedSizeError::MissingDefinition` --- borsh/src/lib.rs | 2 +- borsh/src/schema.rs | 2 +- borsh/src/schema/container_ext.rs | 4 +- borsh/src/schema/container_ext/max_size.rs | 46 ++++++++++++---------- borsh/src/schema/container_ext/validate.rs | 18 +++++++-- borsh/src/schema_helpers.rs | 12 +++++- borsh/tests/test_schema_validate.rs | 6 +-- 7 files changed, 56 insertions(+), 34 deletions(-) diff --git a/borsh/src/lib.rs b/borsh/src/lib.rs index 0fcab9a0a..3aff5665e 100644 --- a/borsh/src/lib.rs +++ b/borsh/src/lib.rs @@ -91,7 +91,7 @@ pub use de::{from_reader, from_slice}; pub use schema::BorshSchema; #[cfg(feature = "unstable__schema")] pub use schema_helpers::{ - max_size_of, schema_container_of, try_from_slice_with_schema, try_to_vec_with_schema, + max_serialized_size, schema_container_of, try_from_slice_with_schema, try_to_vec_with_schema, }; pub use ser::helpers::{to_vec, to_writer}; pub use ser::BorshSerialize; diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs index 212a63ac0..b7397fdc1 100644 --- a/borsh/src/schema.rs +++ b/borsh/src/schema.rs @@ -29,7 +29,7 @@ use core::marker::PhantomData; mod container_ext; -pub use container_ext::{MaxSizeError, ValidateError}; +pub use container_ext::{SchemaContainerValidateError, SchemaMaxSerializedSizeError}; /// The type that we use to represent the declaration of the Borsh type. pub type Declaration = String; diff --git a/borsh/src/schema/container_ext.rs b/borsh/src/schema/container_ext.rs index a3e1da263..ca6d4d085 100644 --- a/borsh/src/schema/container_ext.rs +++ b/borsh/src/schema/container_ext.rs @@ -1,8 +1,8 @@ use super::{BorshSchemaContainer, Declaration, Definition, Fields}; use max_size::is_zero_size; -pub use max_size::MaxSizeError; -pub use validate::ValidateError; +pub use max_size::SchemaMaxSerializedSizeError; +pub use validate::SchemaContainerValidateError; mod max_size; mod validate; diff --git a/borsh/src/schema/container_ext/max_size.rs b/borsh/src/schema/container_ext/max_size.rs index 51f1ef954..2f8f81149 100644 --- a/borsh/src/schema/container_ext/max_size.rs +++ b/borsh/src/schema/container_ext/max_size.rs @@ -34,18 +34,18 @@ impl BorshSchemaContainer { /// assert_eq!(Ok(4 + 4294967295), schema.max_serialized_size()); /// /// let schema = BorshSchemaContainer::for_type::>(); - /// assert_eq!(Err(borsh::schema::MaxSizeError::Overflow), + /// assert_eq!(Err(borsh::schema::SchemaMaxSerializedSizeError::Overflow), /// schema.max_serialized_size()); /// ``` - pub fn max_serialized_size(&self) -> core::result::Result { + pub fn max_serialized_size(&self) -> core::result::Result { let mut stack = Vec::new(); max_serialized_size_impl(1, self.declaration(), self, &mut stack) } } -/// Possible error when calculating theoretical maximum size of encoded type. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum MaxSizeError { +/// Possible error when calculating theoretical maximum size of encoded type `T`. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum SchemaMaxSerializedSizeError { /// The theoretical maximum size of the encoded value overflows `usize`. /// /// This may happen for nested dynamically-sized types such as @@ -59,7 +59,7 @@ pub enum MaxSizeError { /// Some of the declared types were lacking definition making it impossible /// to calculate the size. - MissingDefinition, + MissingDefinition(Declaration), } /// Implementation of [`BorshSchema::max_serialized_size`]. @@ -68,19 +68,21 @@ fn max_serialized_size_impl<'a>( declaration: &'a str, schema: &'a BorshSchemaContainer, stack: &mut Vec<&'a str>, -) -> core::result::Result { +) -> core::result::Result { use core::convert::TryFrom; /// Maximum number of elements in a vector or length of a string which can /// be serialised. const MAX_LEN: usize = u32::MAX as usize; - fn add(x: usize, y: usize) -> core::result::Result { - x.checked_add(y).ok_or(MaxSizeError::Overflow) + fn add(x: usize, y: usize) -> core::result::Result { + x.checked_add(y) + .ok_or(SchemaMaxSerializedSizeError::Overflow) } - fn mul(x: usize, y: usize) -> core::result::Result { - x.checked_mul(y).ok_or(MaxSizeError::Overflow) + fn mul(x: usize, y: usize) -> core::result::Result { + x.checked_mul(y) + .ok_or(SchemaMaxSerializedSizeError::Overflow) } /// Calculates max serialised size of a tuple with given members. @@ -89,7 +91,7 @@ fn max_serialized_size_impl<'a>( elements: impl core::iter::IntoIterator, schema: &'a BorshSchemaContainer, stack: &mut Vec<&'a str>, - ) -> ::core::result::Result { + ) -> ::core::result::Result { let mut sum: usize = 0; for el in elements { sum = add(sum, max_serialized_size_impl(1, el, schema, stack)?)?; @@ -98,7 +100,7 @@ fn max_serialized_size_impl<'a>( } if stack.iter().any(|dec| *dec == declaration) { - return Err(MaxSizeError::Recursive); + return Err(SchemaMaxSerializedSizeError::Recursive); } stack.push(declaration); @@ -113,11 +115,11 @@ fn max_serialized_size_impl<'a>( Some(0) => Ok(0), Some(count) => max_serialized_size_impl(count, elements, schema, stack), None if is_zero_size(elements, schema) - .map_err(|_err| MaxSizeError::Recursive)? => + .map_err(|_err| SchemaMaxSerializedSizeError::Recursive)? => { Ok(0) } - None => Err(MaxSizeError::Overflow), + None => Err(SchemaMaxSerializedSizeError::Overflow), } } Ok(Definition::Sequence { elements }) => { @@ -137,7 +139,7 @@ fn max_serialized_size_impl<'a>( max = max.max(sz); } max.checked_add(usize::from(*tag_width)) - .ok_or(MaxSizeError::Overflow) + .ok_or(SchemaMaxSerializedSizeError::Overflow) } // Tuples and structs sum sizes of all the members. @@ -161,7 +163,9 @@ fn max_serialized_size_impl<'a>( // string is just Vec Err("string") => mul(count, add(MAX_LEN, 4)?), - Err(_) => Err(MaxSizeError::MissingDefinition), + Err(declaration) => Err(SchemaMaxSerializedSizeError::MissingDefinition( + declaration.to_string(), + )), }?; stack.pop(); @@ -278,7 +282,7 @@ mod tests { } #[track_caller] - fn test_err(err: MaxSizeError) { + fn test_err(err: SchemaMaxSerializedSizeError) { let schema = BorshSchemaContainer::for_type::(); assert_eq!(Err(err), schema.max_serialized_size()); } @@ -334,7 +338,7 @@ mod tests { test_ok::>(4 + MAX_LEN); test_ok::(4 + MAX_LEN); - test_err::>>(MaxSizeError::Overflow); + test_err::>>(SchemaMaxSerializedSizeError::Overflow); test_ok::>>(4 + MAX_LEN * 4); test_ok::<[[[(); MAX_LEN]; MAX_LEN]; MAX_LEN]>(0); } @@ -371,8 +375,8 @@ mod tests { test_ok::(23); test_ok::(23); test_ok::(3 * 8 + 2 * (4 + MAX_LEN * 8)); - test_err::(MaxSizeError::Overflow); - test_err::(MaxSizeError::Recursive); + test_err::(SchemaMaxSerializedSizeError::Overflow); + test_err::(SchemaMaxSerializedSizeError::Recursive); } #[test] diff --git a/borsh/src/schema/container_ext/validate.rs b/borsh/src/schema/container_ext/validate.rs index 71742c1b0..1640e0c87 100644 --- a/borsh/src/schema/container_ext/validate.rs +++ b/borsh/src/schema/container_ext/validate.rs @@ -9,14 +9,24 @@ impl BorshSchemaContainer { /// Zero-sized types should follow the convention of either providing a [Definition] or /// specifying `"nil"` as their [Declaration] for this method to work correctly. /// - pub fn validate(&self) -> core::result::Result<(), ValidateError> { + /// # Example + /// + /// ``` + /// use borsh::schema::BorshSchemaContainer; + /// + /// let schema = BorshSchemaContainer::for_type::(); + /// assert_eq!(Ok(()), schema.validate()); + /// + pub fn validate(&self) -> core::result::Result<(), SchemaContainerValidateError> { let mut stack = Vec::new(); validate_impl(self.declaration(), self, &mut stack) } } +/// Possible error when validating a [`BorshSchemaContainer`], generated for some type `T`, +/// for violation of any well-known rules with respect to `borsh` serialization. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum ValidateError { +pub enum SchemaContainerValidateError { /// sequences of zero-sized types of dynamic length are forbidden by definition /// see and related ones ZSTSequence, @@ -26,7 +36,7 @@ fn validate_impl<'a>( declaration: &'a Declaration, schema: &'a BorshSchemaContainer, stack: &mut Vec<&'a Declaration>, -) -> core::result::Result<(), ValidateError> { +) -> core::result::Result<(), SchemaContainerValidateError> { let definition = match schema.get_definition(declaration) { Some(definition) => definition, // it's not an error for a type to not contain any definition @@ -46,7 +56,7 @@ fn validate_impl<'a>( // or it uses `Definiotion::Enum` or `Definition::Sequence` to exit from recursion // which make it non-zero size if is_zero_size(elements, schema).unwrap_or(false) { - return Err(ValidateError::ZSTSequence); + return Err(SchemaContainerValidateError::ZSTSequence); } validate_impl(elements, schema, stack)?; } diff --git a/borsh/src/schema_helpers.rs b/borsh/src/schema_helpers.rs index 977f36477..a59812424 100644 --- a/borsh/src/schema_helpers.rs +++ b/borsh/src/schema_helpers.rs @@ -1,7 +1,7 @@ use crate::__private::maybestd::vec::Vec; use crate::from_slice; use crate::io::{Error, ErrorKind, Result}; -use crate::schema::{BorshSchemaContainer, MaxSizeError}; +use crate::schema::{BorshSchemaContainer, SchemaMaxSerializedSizeError}; use crate::{BorshDeserialize, BorshSchema, BorshSerialize}; /// Deserialize this instance from a slice of bytes, but assume that at the beginning we have @@ -37,7 +37,15 @@ pub fn schema_container_of() -> BorshSchemaContainer { /// Returns the largest possible size of a serialised object based solely on its type `T`. /// /// this is a shortcut for using [BorshSchemaContainer::max_serialized_size] -pub fn max_size_of() -> core::result::Result { +/// # Example +/// +/// ``` +/// use borsh::schema::BorshSchemaContainer; +/// +/// assert_eq!(Ok(8), borsh::max_serialized_size::()); +/// ``` +pub fn max_serialized_size( +) -> core::result::Result { let schema = BorshSchemaContainer::for_type::(); schema.max_serialized_size() } diff --git a/borsh/tests/test_schema_validate.rs b/borsh/tests/test_schema_validate.rs index ad4369495..a704fc82d 100644 --- a/borsh/tests/test_schema_validate.rs +++ b/borsh/tests/test_schema_validate.rs @@ -16,7 +16,7 @@ fn test_ok() { } #[track_caller] -fn test_err(err: ValidateError) { +fn test_err(err: SchemaContainerValidateError) { let schema = BorshSchemaContainer::for_type::(); assert_eq!(Err(err), schema.validate()); } @@ -56,6 +56,6 @@ fn validate_for_derived_types() { #[test] fn validate_for_zst_sequences() { - test_err::>>(ValidateError::ZSTSequence); - test_err::>(ValidateError::ZSTSequence); + test_err::>>(SchemaContainerValidateError::ZSTSequence); + test_err::>(SchemaContainerValidateError::ZSTSequence); } From d39a2146775407149c973e8a616da354fe79acae Mon Sep 17 00:00:00 2001 From: Vlad Frolov Date: Fri, 15 Sep 2023 09:18:53 +0200 Subject: [PATCH 4/4] Update borsh/src/schema/container_ext/validate.rs --- borsh/src/schema/container_ext/validate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borsh/src/schema/container_ext/validate.rs b/borsh/src/schema/container_ext/validate.rs index 1640e0c87..50324d199 100644 --- a/borsh/src/schema/container_ext/validate.rs +++ b/borsh/src/schema/container_ext/validate.rs @@ -16,7 +16,7 @@ impl BorshSchemaContainer { /// /// let schema = BorshSchemaContainer::for_type::(); /// assert_eq!(Ok(()), schema.validate()); - /// + /// ``` pub fn validate(&self) -> core::result::Result<(), SchemaContainerValidateError> { let mut stack = Vec::new(); validate_impl(self.declaration(), self, &mut stack)