borsh/schema/container_ext/
validate.rs

1use super::{is_zero_size, ZeroSizeError};
2use super::{BorshSchemaContainer, Declaration, Definition, Fields};
3use crate::__private::maybestd::{string::ToString, vec::Vec};
4
5impl BorshSchemaContainer {
6    /// Validates container for violation of any well-known rules with
7    /// respect to `borsh` serialization.
8    ///
9    /// # Example
10    ///
11    /// ```
12    /// use borsh::schema::BorshSchemaContainer;
13    ///
14    /// let schema = BorshSchemaContainer::for_type::<usize>();
15    /// assert_eq!(Ok(()), schema.validate());
16    /// ```
17    pub fn validate(&self) -> core::result::Result<(), Error> {
18        let mut stack = Vec::new();
19        validate_impl(self.declaration(), self, &mut stack)
20    }
21}
22
23/// Possible error when validating a [`BorshSchemaContainer`], generated for some type `T`,
24/// for violation of any well-known rules with respect to `borsh` serialization.
25#[derive(Clone, PartialEq, Eq, Debug)]
26pub enum Error {
27    /// sequences of zero-sized types of dynamic length are forbidden by definition
28    /// see <https://github.com/near/borsh-rs/pull/202> and related ones
29    ZSTSequence(Declaration),
30    /// Declared tag width is too large.  Tags may be at most eight bytes.
31    TagTooWide(Declaration),
32    /// Declared tag width is too small.  Tags must be large enough to represent
33    /// possible length of sequence.
34    TagTooNarrow(Declaration),
35    /// only 0, 1, 2, 4 and 8 bytes long sequences' `length_width` are allowed
36    TagNotPowerOfTwo(Declaration),
37    /// Some of the declared types were lacking definition, which is considered
38    /// a container's validation error
39    MissingDefinition(Declaration),
40    /// A Sequence defined with an empty length range.
41    EmptyLengthRange(Declaration),
42}
43
44fn check_length_width(declaration: &Declaration, width: u8, max: u64) -> Result<(), Error> {
45    match width {
46        0 => Ok(()),
47        3 | 5 | 6 | 7 => Err(Error::TagNotPowerOfTwo(declaration.clone())),
48        1..=7 if max < 1 << (width * 8) => Ok(()),
49        1..=7 => Err(Error::TagTooNarrow(declaration.clone())),
50        8 => Ok(()),
51        _ => Err(Error::TagTooWide(declaration.clone())),
52    }
53}
54
55const U64_LEN: u8 = 8;
56
57fn validate_impl<'a>(
58    declaration: &'a Declaration,
59    schema: &'a BorshSchemaContainer,
60    stack: &mut Vec<&'a Declaration>,
61) -> core::result::Result<(), Error> {
62    let definition = match schema.get_definition(declaration) {
63        Some(definition) => definition,
64        None => {
65            return Err(Error::MissingDefinition(declaration.to_string()));
66        }
67    };
68    if stack.contains(&declaration) {
69        return Ok(());
70    }
71    stack.push(declaration);
72    match definition {
73        Definition::Primitive(_size) => {}
74        // arrays branch
75        Definition::Sequence {
76            length_width,
77            length_range,
78            elements,
79        } if *length_width == Definition::ARRAY_LENGTH_WIDTH
80            && length_range.clone().count() == 1 =>
81        {
82            validate_impl(elements, schema, stack)?
83        }
84        Definition::Sequence {
85            length_width,
86            length_range,
87            elements,
88        } => {
89            if length_range.is_empty() {
90                return Err(Error::EmptyLengthRange(declaration.clone()));
91            }
92            check_length_width(declaration, *length_width, *length_range.end())?;
93            match is_zero_size(elements, schema) {
94                Ok(true) => return Err(Error::ZSTSequence(declaration.clone())),
95                Ok(false) => (),
96                // a recursive type either has no exit, so it cannot be instantiated
97                // or it uses `Definiotion::Enum` or `Definition::Sequence` to exit from recursion
98                // which make it non-zero size
99                Err(ZeroSizeError::Recursive) => (),
100                Err(ZeroSizeError::MissingDefinition(declaration)) => {
101                    return Err(Error::MissingDefinition(declaration));
102                }
103            }
104            validate_impl(elements, schema, stack)?;
105        }
106        Definition::Enum {
107            tag_width,
108            variants,
109        } => {
110            if *tag_width > U64_LEN {
111                return Err(Error::TagTooWide(declaration.to_string()));
112            }
113            for (_, _, variant) in variants {
114                validate_impl(variant, schema, stack)?;
115            }
116        }
117        Definition::Tuple { elements } => {
118            for element_type in elements {
119                validate_impl(element_type, schema, stack)?;
120            }
121        }
122        Definition::Struct { fields } => match fields {
123            Fields::NamedFields(fields) => {
124                for (_field_name, field_type) in fields {
125                    validate_impl(field_type, schema, stack)?;
126                }
127            }
128            Fields::UnnamedFields(fields) => {
129                for field_type in fields {
130                    validate_impl(field_type, schema, stack)?;
131                }
132            }
133            Fields::Empty => {}
134        },
135    };
136    stack.pop();
137    Ok(())
138}
139
140#[cfg(test)]
141mod tests {
142    use super::{check_length_width, Error};
143    use crate::__private::maybestd::string::ToString;
144
145    #[test]
146    fn test_check_tag_width() {
147        let narrow_err: Result<(), Error> = Err(Error::TagTooNarrow("test".to_string()));
148        let power_of_two_err: Result<(), Error> = Err(Error::TagNotPowerOfTwo("test".to_string()));
149
150        for (width, max, want) in [
151            (0, u64::MAX, Ok(())),
152            (1, u8::MAX as u64, Ok(())),
153            (1, u8::MAX as u64 + 1, narrow_err.clone()),
154            (2, u16::MAX as u64, Ok(())),
155            (2, u16::MAX as u64 + 1, narrow_err.clone()),
156            (3, 100, power_of_two_err.clone()),
157            (4, u32::MAX as u64, Ok(())),
158            (4, u32::MAX as u64 + 1, narrow_err),
159            (5, 100, power_of_two_err.clone()),
160            (6, 100, power_of_two_err.clone()),
161            (7, 100, power_of_two_err),
162            (8, u64::MAX, Ok(())),
163        ] {
164            assert_eq!(
165                want,
166                check_length_width(&"test".into(), width, max),
167                "width={width}; max={max}"
168            );
169        }
170    }
171}