ironsbe_schema/
validation.rs1use crate::error::SchemaError;
7use crate::types::Schema;
8
9pub fn validate_schema(schema: &Schema) -> Result<(), SchemaError> {
20 validate_types(schema)?;
21 validate_messages(schema)?;
22 Ok(())
23}
24
25fn validate_types(schema: &Schema) -> Result<(), SchemaError> {
27 for type_def in &schema.types {
28 match type_def {
29 crate::types::TypeDef::Composite(composite) => {
30 validate_composite(schema, composite)?;
31 }
32 crate::types::TypeDef::Enum(enum_def) => {
33 validate_enum(enum_def)?;
34 }
35 crate::types::TypeDef::Set(set_def) => {
36 validate_set(set_def)?;
37 }
38 _ => {}
39 }
40 }
41 Ok(())
42}
43
44fn validate_composite(
46 _schema: &Schema,
47 composite: &crate::types::CompositeDef,
48) -> Result<(), SchemaError> {
49 let mut expected_offset = 0;
50
51 for field in &composite.fields {
52 if let Some(offset) = field.offset {
53 if offset < expected_offset {
54 return Err(SchemaError::InvalidOffset {
55 field: field.name.clone(),
56 offset,
57 });
58 }
59 expected_offset = offset + field.encoded_length;
60 } else {
61 expected_offset += field.encoded_length;
62 }
63 }
64
65 Ok(())
66}
67
68fn validate_enum(enum_def: &crate::types::EnumDef) -> Result<(), SchemaError> {
70 use std::collections::HashSet;
71
72 let mut seen_names = HashSet::new();
73 let mut seen_values = HashSet::new();
74
75 for value in &enum_def.valid_values {
76 if !seen_names.insert(&value.name) {
77 return Err(SchemaError::Validation {
78 message: format!(
79 "Duplicate enum value name '{}' in enum '{}'",
80 value.name, enum_def.name
81 ),
82 });
83 }
84
85 if !seen_values.insert(&value.value) {
86 return Err(SchemaError::Validation {
87 message: format!(
88 "Duplicate enum value '{}' in enum '{}'",
89 value.value, enum_def.name
90 ),
91 });
92 }
93 }
94
95 Ok(())
96}
97
98fn validate_set(set_def: &crate::types::SetDef) -> Result<(), SchemaError> {
100 use std::collections::HashSet;
101
102 let max_bits = set_def.encoding_type.size() * 8;
103 let mut seen_positions = HashSet::new();
104
105 for choice in &set_def.choices {
106 if choice.bit_position as usize >= max_bits {
107 return Err(SchemaError::Validation {
108 message: format!(
109 "Bit position {} exceeds maximum {} for set '{}'",
110 choice.bit_position,
111 max_bits - 1,
112 set_def.name
113 ),
114 });
115 }
116
117 if !seen_positions.insert(choice.bit_position) {
118 return Err(SchemaError::Validation {
119 message: format!(
120 "Duplicate bit position {} in set '{}'",
121 choice.bit_position, set_def.name
122 ),
123 });
124 }
125 }
126
127 Ok(())
128}
129
130fn validate_messages(schema: &Schema) -> Result<(), SchemaError> {
132 use std::collections::HashSet;
133
134 let mut seen_ids = HashSet::new();
135 let mut seen_names = HashSet::new();
136
137 for msg in &schema.messages {
138 if !seen_ids.insert(msg.id) {
139 return Err(SchemaError::Validation {
140 message: format!("Duplicate message ID {} for message '{}'", msg.id, msg.name),
141 });
142 }
143
144 if !seen_names.insert(&msg.name) {
145 return Err(SchemaError::Validation {
146 message: format!("Duplicate message name '{}'", msg.name),
147 });
148 }
149
150 validate_message_fields(schema, msg)?;
151 }
152
153 Ok(())
154}
155
156fn validate_message_fields(
158 schema: &Schema,
159 msg: &crate::messages::MessageDef,
160) -> Result<(), SchemaError> {
161 let mut max_offset = 0;
162
163 for field in &msg.fields {
164 if !schema.has_type(&field.type_name) {
166 if crate::types::PrimitiveType::from_sbe_name(&field.type_name).is_none() {
168 return Err(SchemaError::TypeNotFound {
169 name: field.type_name.clone(),
170 });
171 }
172 }
173
174 if field.offset < max_offset && field.encoded_length > 0 {
176 return Err(SchemaError::InvalidOffset {
177 field: field.name.clone(),
178 offset: field.offset,
179 });
180 }
181
182 max_offset = field.offset + field.encoded_length;
183 }
184
185 if max_offset > msg.block_length as usize {
187 return Err(SchemaError::BlockLengthMismatch {
188 message: msg.name.clone(),
189 declared: msg.block_length,
190 calculated: max_offset as u16,
191 });
192 }
193
194 for group in &msg.groups {
196 validate_group_fields(schema, group)?;
197 }
198
199 Ok(())
200}
201
202fn validate_group_fields(
204 schema: &Schema,
205 group: &crate::messages::GroupDef,
206) -> Result<(), SchemaError> {
207 for field in &group.fields {
208 if !schema.has_type(&field.type_name)
209 && crate::types::PrimitiveType::from_sbe_name(&field.type_name).is_none()
210 {
211 return Err(SchemaError::TypeNotFound {
212 name: field.type_name.clone(),
213 });
214 }
215 }
216
217 for nested in &group.nested_groups {
219 validate_group_fields(schema, nested)?;
220 }
221
222 Ok(())
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::parser::parse_schema;
229
230 #[test]
231 fn test_validate_valid_schema() {
232 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
233<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
234 package="test" id="1" version="1" byteOrder="littleEndian">
235 <types>
236 <type name="uint64" primitiveType="uint64"/>
237 </types>
238 <sbe:message name="Test" id="1" blockLength="8">
239 <field name="value" id="1" type="uint64" offset="0"/>
240 </sbe:message>
241</sbe:messageSchema>"#;
242
243 let schema = parse_schema(xml).expect("Failed to parse");
244 assert!(validate_schema(&schema).is_ok());
245 }
246
247 #[test]
248 fn test_validate_duplicate_message_id() {
249 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
250<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
251 package="test" id="1" version="1" byteOrder="littleEndian">
252 <types>
253 <type name="uint64" primitiveType="uint64"/>
254 </types>
255 <sbe:message name="Test1" id="1" blockLength="8">
256 <field name="value" id="1" type="uint64" offset="0"/>
257 </sbe:message>
258 <sbe:message name="Test2" id="1" blockLength="8">
259 <field name="value" id="1" type="uint64" offset="0"/>
260 </sbe:message>
261</sbe:messageSchema>"#;
262
263 let schema = parse_schema(xml).expect("Failed to parse");
264 let result = validate_schema(&schema);
265 assert!(result.is_err());
266 }
267
268 #[test]
269 fn test_validate_duplicate_message_name() {
270 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
271<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
272 package="test" id="1" version="1" byteOrder="littleEndian">
273 <types>
274 <type name="uint64" primitiveType="uint64"/>
275 </types>
276 <sbe:message name="Test" id="1" blockLength="8">
277 <field name="value" id="1" type="uint64" offset="0"/>
278 </sbe:message>
279 <sbe:message name="Test" id="2" blockLength="8">
280 <field name="value" id="1" type="uint64" offset="0"/>
281 </sbe:message>
282</sbe:messageSchema>"#;
283
284 let schema = parse_schema(xml).expect("Failed to parse");
285 let result = validate_schema(&schema);
286 assert!(result.is_err());
287 }
288
289 #[test]
290 fn test_validate_unknown_field_type() {
291 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
292<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
293 package="test" id="1" version="1" byteOrder="littleEndian">
294 <types>
295 </types>
296 <sbe:message name="Test" id="1" blockLength="8">
297 <field name="value" id="1" type="UnknownType" offset="0"/>
298 </sbe:message>
299</sbe:messageSchema>"#;
300
301 let schema = parse_schema(xml).expect("Failed to parse");
302 let result = validate_schema(&schema);
303 assert!(result.is_err());
304 }
305
306 #[test]
307 fn test_validate_primitive_type_field() {
308 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
309<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
310 package="test" id="1" version="1" byteOrder="littleEndian">
311 <types>
312 </types>
313 <sbe:message name="Test" id="1" blockLength="8">
314 <field name="value" id="1" type="uint64" offset="0"/>
315 </sbe:message>
316</sbe:messageSchema>"#;
317
318 let schema = parse_schema(xml).expect("Failed to parse");
319 let result = validate_schema(&schema);
320 assert!(result.is_ok());
321 }
322
323 #[test]
324 fn test_validate_enum_with_valid_values() {
325 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
326<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
327 package="test" id="1" version="1" byteOrder="littleEndian">
328 <types>
329 <enum name="Side" encodingType="uint8">
330 <validValue name="Buy">1</validValue>
331 <validValue name="Sell">2</validValue>
332 </enum>
333 </types>
334 <sbe:message name="Test" id="1" blockLength="1">
335 <field name="side" id="1" type="Side" offset="0"/>
336 </sbe:message>
337</sbe:messageSchema>"#;
338
339 let schema = parse_schema(xml).expect("Failed to parse");
340 let result = validate_schema(&schema);
341 assert!(result.is_ok());
342 }
343
344 #[test]
345 fn test_validate_set_type() {
346 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
347<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
348 package="test" id="1" version="1" byteOrder="littleEndian">
349 <types>
350 <set name="Flags" encodingType="uint8">
351 <choice name="Active">0</choice>
352 <choice name="Visible">1</choice>
353 </set>
354 </types>
355 <sbe:message name="Test" id="1" blockLength="1">
356 <field name="flags" id="1" type="Flags" offset="0"/>
357 </sbe:message>
358</sbe:messageSchema>"#;
359
360 let schema = parse_schema(xml).expect("Failed to parse");
361 let result = validate_schema(&schema);
362 assert!(result.is_ok());
363 }
364
365 #[test]
366 fn test_validate_composite_type() {
367 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
368<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
369 package="test" id="1" version="1" byteOrder="littleEndian">
370 <types>
371 <composite name="Decimal">
372 <type name="mantissa" primitiveType="int64"/>
373 <type name="exponent" primitiveType="int8"/>
374 </composite>
375 </types>
376 <sbe:message name="Test" id="1" blockLength="9">
377 <field name="price" id="1" type="Decimal" offset="0"/>
378 </sbe:message>
379</sbe:messageSchema>"#;
380
381 let schema = parse_schema(xml).expect("Failed to parse");
382 let result = validate_schema(&schema);
383 assert!(result.is_ok());
384 }
385}