borsh/schema/container_ext/
validate.rs1use super::{is_zero_size, ZeroSizeError};
2use super::{BorshSchemaContainer, Declaration, Definition, Fields};
3use crate::__private::maybestd::{string::ToString, vec::Vec};
4
5impl BorshSchemaContainer {
6 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#[derive(Clone, PartialEq, Eq, Debug)]
26pub enum Error {
27 ZSTSequence(Declaration),
30 TagTooWide(Declaration),
32 TagTooNarrow(Declaration),
35 TagNotPowerOfTwo(Declaration),
37 MissingDefinition(Declaration),
40 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 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 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}