1use crate::ir::{Ir, PrimitiveType, Signal, Token};
11
12#[derive(Debug, thiserror::Error, miette::Diagnostic)]
19pub enum ResolveError {
20 #[error("duplicate template id {id} for message {name}")]
22 #[diagnostic(code(ergo_sbe::resolve::duplicate_template_id))]
23 #[diagnostic(help("each message must have a unique template id"))]
24 DuplicateTemplateId {
25 id: u16,
27 name: String,
29 #[source_code]
31 source_code: Option<miette::NamedSource<String>>,
32 #[label("first defined here")]
34 first_label: Option<miette::SourceSpan>,
35 #[label("duplicate definition")]
37 second_label: Option<miette::SourceSpan>,
38 },
39 #[error("unknown type reference {name}")]
41 #[diagnostic(code(ergo_sbe::resolve::unknown_type))]
42 #[diagnostic(help("ensure the type is defined in the schema or an include"))]
43 UnknownType {
44 name: String,
46 #[source_code]
48 source_code: Option<miette::NamedSource<String>>,
49 #[label("unknown type")]
51 span: Option<miette::SourceSpan>,
52 },
53 #[error("overlapping offsets or invalid alignment at offset {offset}")]
55 #[diagnostic(code(ergo_sbe::resolve::invalid_offset))]
56 #[diagnostic(help("check explicit offset attributes for clashes"))]
57 InvalidOffset {
58 offset: usize,
60 #[source_code]
62 source_code: Option<miette::NamedSource<String>>,
63 #[label("overlap here")]
65 span: Option<miette::SourceSpan>,
66 },
67 #[error("composite {name} has no fields")]
69 #[diagnostic(code(ergo_sbe::resolve::empty_composite))]
70 #[diagnostic(help("add at least one <type> member to the composite"))]
71 EmptyComposite {
72 name: String,
74 #[source_code]
76 source_code: Option<miette::NamedSource<String>>,
77 #[label("empty composite")]
79 span: Option<miette::SourceSpan>,
80 },
81 #[error("sinceVersion {version} exceeds schema version {schema_version} for {name}")]
83 #[diagnostic(code(ergo_sbe::resolve::since_version_beyond))]
84 #[diagnostic(help("the sinceVersion must be <= the schema version"))]
85 SinceVersionBeyondSchema {
86 version: u16,
88 schema_version: u16,
90 name: String,
92 #[source_code]
94 source_code: Option<miette::NamedSource<String>>,
95 #[label("sinceVersion too high")]
97 span: Option<miette::SourceSpan>,
98 },
99 #[error("declared blockLength {declared} is smaller than required {required} for {name}")]
101 #[diagnostic(code(ergo_sbe::resolve::block_length_too_short))]
102 #[diagnostic(help("increase blockLength to at least the end offset of the final fixed field"))]
103 BlockLengthTooShort {
104 name: String,
106 declared: usize,
108 required: usize,
110 #[source_code]
112 source_code: Option<miette::NamedSource<String>>,
113 #[label("insufficient blockLength")]
115 span: Option<miette::SourceSpan>,
116 },
117}
118
119impl ResolveError {
120 pub(crate) fn take_source_code(&mut self) -> Option<miette::NamedSource<String>> {
124 match self {
125 ResolveError::DuplicateTemplateId { source_code, .. } => source_code.take(),
126 ResolveError::UnknownType { source_code, .. } => source_code.take(),
127 ResolveError::InvalidOffset { source_code, .. } => source_code.take(),
128 ResolveError::EmptyComposite { source_code, .. } => source_code.take(),
129 ResolveError::SinceVersionBeyondSchema { source_code, .. } => source_code.take(),
130 ResolveError::BlockLengthTooShort { source_code, .. } => source_code.take(),
131 }
132 }
133
134 pub(crate) fn take_spans(
137 &mut self,
138 ) -> (Option<miette::SourceSpan>, Option<miette::SourceSpan>) {
139 match self {
140 ResolveError::DuplicateTemplateId {
141 first_label,
142 second_label,
143 ..
144 } => (first_label.take(), second_label.take()),
145 ResolveError::UnknownType { span, .. }
146 | ResolveError::InvalidOffset { span, .. }
147 | ResolveError::EmptyComposite { span, .. }
148 | ResolveError::SinceVersionBeyondSchema { span, .. }
149 | ResolveError::BlockLengthTooShort { span, .. } => (span.take(), None),
150 }
151 }
152}
153
154pub fn resolve_schema(ir: &mut Ir, source: Option<&str>) -> Result<(), ResolveError> {
164 let src = source.map(|s| miette::NamedSource::new("schema.xml", s.to_owned()));
165
166 {
167 let mut seen_ids: std::collections::HashMap<u16, (&str, Option<std::ops::Range<usize>>)> =
168 std::collections::HashMap::new();
169 for token in &ir.tokens {
170 if token.signal == Signal::BeginMessage {
171 if let Some(id) = token.id {
172 if let Some((_prev_name, prev_span)) =
173 seen_ids.insert(id, (&token.name, token.span.clone()))
174 {
175 return Err(ResolveError::DuplicateTemplateId {
176 id,
177 name: token.name.clone(),
178 source_code: src.clone(),
179 first_label: prev_span.map(miette::SourceSpan::from),
180 second_label: token.span.clone().map(miette::SourceSpan::from),
181 });
182 }
183 }
184 }
185 }
186 }
187
188 for token in &ir.tokens {
189 let sv = token.encoding.since_version;
190 if sv > ir.version {
191 return Err(ResolveError::SinceVersionBeyondSchema {
192 version: sv,
193 schema_version: ir.version,
194 name: token.name.clone(),
195 source_code: src.clone(),
196 span: token.span.clone().map(miette::SourceSpan::from),
197 });
198 }
199 }
200
201 for token in &mut ir.tokens {
202 if let Some(prim) = token.encoding.primitive_type {
203 if token.encoding.null_value.is_none() {
204 token.encoding.null_value = default_null(prim);
205 }
206 if token.encoding.min_value.is_none() {
207 token.encoding.min_value = default_min(prim);
208 }
209 if token.encoding.max_value.is_none() {
210 token.encoding.max_value = default_max(prim);
211 }
212 }
213 }
214
215 let mut i = 0;
216 while i < ir.tokens.len() {
217 match ir.tokens[i].signal {
218 Signal::BeginComposite => {
219 let end_idx =
220 find_matching_end(&ir.tokens, i, Signal::BeginComposite, Signal::EndComposite);
221 resolve_composite_offsets(&mut ir.tokens[i..=end_idx], &src)?;
222 i = end_idx + 1;
223 }
224 Signal::BeginMessage => {
225 let end_idx =
226 find_matching_end(&ir.tokens, i, Signal::BeginMessage, Signal::EndMessage);
227 resolve_message_offsets(&mut ir.tokens[i..=end_idx], &src)?;
228 i = end_idx + 1;
229 }
230 _ => {
231 i += 1;
232 }
233 }
234 }
235
236 Ok(())
237}
238
239fn find_matching_end(tokens: &[Token], start: usize, begin: Signal, end: Signal) -> usize {
240 let mut depth = 1;
241 for j in (start + 1)..tokens.len() {
242 if tokens[j].signal == begin {
243 depth += 1;
244 } else if tokens[j].signal == end {
245 depth -= 1;
246 if depth == 0 {
247 return j;
248 }
249 }
250 }
251 tokens.len() - 1
252}
253
254fn get_token_block_size(tokens: &[Token], start: usize) -> (usize, usize) {
255 match tokens[start].signal {
256 Signal::BeginField => {
257 let end_idx = find_matching_end(tokens, start, Signal::BeginField, Signal::EndField);
258 if tokens[start].encoding.is_variable_length {
260 return (0, end_idx + 1);
261 }
262 if tokens[start].encoding.presence == crate::ir::Presence::Constant {
265 return (0, end_idx + 1);
266 }
267 if end_idx > start + 1 {
268 let mut size = 0;
270 let mut j = start + 1;
271 while j < end_idx {
272 let (s, next_j) = get_token_block_size(tokens, j);
273 size += s;
274 j = next_j;
275 }
276 (size, end_idx + 1)
277 } else {
278 let count = tokens[start].encoding.length.unwrap_or(1);
279 let size = tokens[start]
280 .encoding
281 .primitive_type
282 .map_or(0, |p| p.size())
283 * count;
284 (size, end_idx + 1)
285 }
286 }
287 Signal::BeginComposite => {
288 let end_idx =
289 find_matching_end(tokens, start, Signal::BeginComposite, Signal::EndComposite);
290 let mut current = 0;
291 let mut size = 0;
292 let mut j = start + 1;
293 while j < end_idx {
294 let (s, next_j) = get_token_block_size(tokens, j);
295 let member_offset = tokens[j].encoding.offset.unwrap_or(current);
300 current = member_offset + s;
301 size = size.max(current);
302 j = next_j;
303 }
304 (size, end_idx + 1)
305 }
306 Signal::BeginEnum | Signal::BeginSet => {
307 let end_idx = find_matching_end(
308 tokens,
309 start,
310 tokens[start].signal,
311 match tokens[start].signal {
312 Signal::BeginEnum => Signal::EndEnum,
313 _ => Signal::EndSet,
314 },
315 );
316 let size = tokens[start]
317 .encoding
318 .primitive_type
319 .map_or(0, |p| p.size());
320 (size, end_idx + 1)
321 }
322 _ => (0, start + 1),
326 }
327}
328
329#[allow(clippy::only_used_in_recursion)]
330fn resolve_composite_offsets(
331 tokens: &mut [Token],
332 src: &Option<miette::NamedSource<String>>,
333) -> Result<(), ResolveError> {
334 let mut current_offset = 0;
335 let mut i = 1; let end_limit = tokens.len() - 1; while i < end_limit {
339 let (size, next_i) = get_token_block_size(tokens, i);
340
341 let resolved_offset = if let Some(off) = tokens[i].encoding.offset {
342 off
343 } else {
344 current_offset
345 };
346 if resolved_offset < current_offset {
347 return Err(ResolveError::InvalidOffset {
348 offset: resolved_offset,
349 source_code: src.clone(),
350 span: tokens[i].span.clone().map(miette::SourceSpan::from),
351 });
352 }
353
354 tokens[i].encoding.offset = Some(resolved_offset);
355
356 if tokens[i].signal == Signal::BeginField && i + 1 < next_i {
362 if tokens[i + 1].signal == Signal::BeginComposite {
363 let nested_end =
364 find_matching_end(tokens, i + 1, Signal::BeginComposite, Signal::EndComposite);
365 resolve_composite_offsets(&mut tokens[i + 1..=nested_end], src)?;
366 }
367 }
368
369 current_offset = resolved_offset + size;
372 i = next_i;
373 }
374
375 let composite_size = current_offset;
376 tokens[0].encoding.offset = Some(composite_size);
377 Ok(())
378}
379
380fn resolve_message_offsets(
381 tokens: &mut [Token],
382 src: &Option<miette::NamedSource<String>>,
383) -> Result<(), ResolveError> {
384 let mut current_offset = 0;
385 let mut i = 1; let end_limit = tokens.len() - 1; while i < end_limit {
389 if tokens[i].signal == Signal::BeginGroup || tokens[i].signal == Signal::BeginVarData {
392 if tokens[i].signal == Signal::BeginGroup {
394 let end_idx = find_matching_end(tokens, i, Signal::BeginGroup, Signal::EndGroup);
395 resolve_group_offsets(&mut tokens[i..=end_idx], src)?;
396 i = end_idx + 1;
397 } else {
398 let end_idx =
399 find_matching_end(tokens, i, Signal::BeginVarData, Signal::EndVarData);
400 resolve_vardata_offsets(&mut tokens[i..=end_idx], src)?;
401 i = end_idx + 1;
402 }
403 continue;
404 }
405
406 let (size, next_i) = get_token_block_size(tokens, i);
407
408 let resolved_offset = if let Some(off) = tokens[i].encoding.offset {
409 off
410 } else {
411 current_offset
412 };
413 if resolved_offset < current_offset {
414 return Err(ResolveError::InvalidOffset {
415 offset: resolved_offset,
416 source_code: src.clone(),
417 span: tokens[i].span.clone().map(miette::SourceSpan::from),
418 });
419 }
420
421 tokens[i].encoding.offset = Some(resolved_offset);
422 current_offset = resolved_offset + size;
423 i = next_i;
424 }
425
426 let declared = tokens[0].encoding.offset;
430 if let Some(declared) = declared
431 && declared != 0
432 && declared < current_offset
433 {
434 return Err(ResolveError::BlockLengthTooShort {
435 name: tokens[0].name.clone(),
436 declared,
437 required: current_offset,
438 source_code: src.clone(),
439 span: tokens[0].span.clone().map(miette::SourceSpan::from),
440 });
441 }
442 let block_length = match declared {
443 Some(d) if d > current_offset => d,
444 _ => current_offset,
445 };
446 tokens[0].encoding.offset = Some(block_length);
447 Ok(())
448}
449
450fn resolve_group_offsets(
451 tokens: &mut [Token],
452 src: &Option<miette::NamedSource<String>>,
453) -> Result<(), ResolveError> {
454 let mut i = 1;
458 if tokens[i].signal == Signal::BeginComposite {
459 let dim_end = find_matching_end(tokens, i, Signal::BeginComposite, Signal::EndComposite);
460 resolve_composite_offsets(&mut tokens[i..=dim_end], src)?;
461 i = dim_end + 1;
462 }
463
464 let mut current_offset = 0;
466 let end_limit = tokens.len() - 1;
467 while i < end_limit {
468 if tokens[i].signal == Signal::BeginGroup || tokens[i].signal == Signal::BeginVarData {
469 if tokens[i].signal == Signal::BeginGroup {
470 let end_idx = find_matching_end(tokens, i, Signal::BeginGroup, Signal::EndGroup);
471 resolve_group_offsets(&mut tokens[i..=end_idx], src)?;
472 i = end_idx + 1;
473 } else {
474 let end_idx =
475 find_matching_end(tokens, i, Signal::BeginVarData, Signal::EndVarData);
476 resolve_vardata_offsets(&mut tokens[i..=end_idx], src)?;
477 i = end_idx + 1;
478 }
479 continue;
480 }
481
482 let (size, next_i) = get_token_block_size(tokens, i);
483 let resolved_offset = if let Some(off) = tokens[i].encoding.offset {
484 off
485 } else {
486 current_offset
487 };
488 if resolved_offset < current_offset {
489 return Err(ResolveError::InvalidOffset {
490 offset: resolved_offset,
491 source_code: src.clone(),
492 span: tokens[i].span.clone().map(miette::SourceSpan::from),
493 });
494 }
495 tokens[i].encoding.offset = Some(resolved_offset);
496 current_offset = resolved_offset + size;
497 i = next_i;
498 }
499
500 let declared = tokens[0].encoding.offset;
503 if let Some(declared) = declared
504 && declared != 0
505 && declared < current_offset
506 {
507 return Err(ResolveError::BlockLengthTooShort {
508 name: tokens[0].name.clone(),
509 declared,
510 required: current_offset,
511 source_code: src.clone(),
512 span: tokens[0].span.clone().map(miette::SourceSpan::from),
513 });
514 }
515 let block_length = match declared {
516 Some(d) if d > current_offset => d,
517 _ => current_offset,
518 };
519 tokens[0].encoding.offset = Some(block_length);
520 Ok(())
521}
522
523fn resolve_vardata_offsets(
524 tokens: &mut [Token],
525 src: &Option<miette::NamedSource<String>>,
526) -> Result<(), ResolveError> {
527 let i = 1;
530 if tokens[i].signal == Signal::BeginComposite {
531 let type_end = find_matching_end(tokens, i, Signal::BeginComposite, Signal::EndComposite);
532 resolve_composite_offsets(&mut tokens[i..=type_end], src)?;
533 }
534 Ok(())
535}
536
537fn default_null(prim: PrimitiveType) -> Option<u64> {
538 match prim {
539 PrimitiveType::Char => Some(0),
540 PrimitiveType::Int8 => Some(-128i8 as u64),
541 PrimitiveType::UInt8 => Some(255),
542 PrimitiveType::Int16 => Some(-32768i16 as u64),
543 PrimitiveType::UInt16 => Some(65535),
544 PrimitiveType::Int32 => Some(-2147483648i32 as u64),
545 PrimitiveType::UInt32 => Some(4294967295),
546 PrimitiveType::Int64 => Some(-9223372036854775808i64 as u64),
547 PrimitiveType::UInt64 => Some(18446744073709551615),
548 PrimitiveType::Float => Some(f32::NAN.to_bits() as u64),
550 PrimitiveType::Double => Some(f64::NAN.to_bits()),
551 }
552}
553
554fn default_min(prim: PrimitiveType) -> Option<u64> {
555 match prim {
556 PrimitiveType::Char => Some(0x20),
557 PrimitiveType::Int8 => Some(-127i8 as u64),
558 PrimitiveType::UInt8 => Some(0),
559 PrimitiveType::Int16 => Some(-32767i16 as u64),
560 PrimitiveType::UInt16 => Some(0),
561 PrimitiveType::Int32 => Some(-2147483647i32 as u64),
562 PrimitiveType::UInt32 => Some(0),
563 PrimitiveType::Int64 => Some(-9223372036854775807i64 as u64),
564 PrimitiveType::UInt64 => Some(0),
565 PrimitiveType::Float => Some(f32::MIN.to_bits() as u64), PrimitiveType::Double => Some(f64::MIN.to_bits()), }
568}
569
570fn default_max(prim: PrimitiveType) -> Option<u64> {
571 match prim {
572 PrimitiveType::Char => Some(0x7E),
573 PrimitiveType::Int8 => Some(127),
574 PrimitiveType::UInt8 => Some(254),
575 PrimitiveType::Int16 => Some(32767),
576 PrimitiveType::UInt16 => Some(65534),
577 PrimitiveType::Int32 => Some(2147483647),
578 PrimitiveType::UInt32 => Some(4294967294),
579 PrimitiveType::Int64 => Some(9223372036854775807),
580 PrimitiveType::UInt64 => Some(18446744073709551614),
581 PrimitiveType::Float => Some(f32::MAX.to_bits() as u64), PrimitiveType::Double => Some(f64::MAX.to_bits()), }
584}
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589
590 fn minimal_schema() -> Ir {
591 crate::parse(
592 r#"<?xml version="1.0"?>
593<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
594 package="test" id="1" version="0" byteOrder="littleEndian">
595 <types>
596 <composite name="messageHeader">
597 <type name="blockLength" primitiveType="uint16"/>
598 <type name="templateId" primitiveType="uint16"/>
599 <type name="schemaId" primitiveType="uint16"/>
600 <type name="version" primitiveType="uint16"/>
601 </composite>
602 </types>
603 <sbe:message name="A" id="1"><field name="x" id="1" type="uint32"/></sbe:message>
604</sbe:messageSchema>"#,
605 )
606 .unwrap()
607 }
608
609 #[test]
610 fn duplicate_template_id_rejected() -> Result<(), Box<dyn std::error::Error>> {
611 let result = crate::parse(
612 r#"<?xml version="1.0"?>
613<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
614 package="test" id="1" version="0" byteOrder="littleEndian">
615 <types><composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite></types>
616 <sbe:message name="A" id="1"><field name="x" id="1" type="uint32"/></sbe:message>
617 <sbe:message name="B" id="1"><field name="y" id="1" type="uint32"/></sbe:message>
618</sbe:messageSchema>"#,
619 );
620 assert!(result.is_err());
621
622 Ok(())
623 }
624
625 #[test]
626 fn since_version_beyond_schema_rejected() -> Result<(), Box<dyn std::error::Error>> {
627 let mut ir = minimal_schema();
628 ir.tokens[5].encoding.since_version = 5;
629 let result = resolve_schema(&mut ir, None);
630 assert!(matches!(
631 result,
632 Err(ResolveError::SinceVersionBeyondSchema { .. })
633 ));
634 Ok(())
635 }
636
637 #[test]
638 fn resolve_schema_ok_on_valid_schema() -> Result<(), Box<dyn std::error::Error>> {
639 let mut ir = minimal_schema();
640 assert!(resolve_schema(&mut ir, None).is_ok());
641
642 Ok(())
643 }
644
645 #[test]
646 fn resolve_schema_with_source_code() -> Result<(), Box<dyn std::error::Error>> {
647 let xml = r#"<?xml version="1.0"?>
648<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
649<types><composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite></types>
650<sbe:message name="A" id="1"><field name="x" id="1" type="uint32"/></sbe:message>
651</sbe:messageSchema>"#;
652 let mut ir = crate::parse(xml).unwrap();
653 assert!(resolve_schema(&mut ir, Some(xml)).is_ok());
654
655 Ok(())
656 }
657
658 #[test]
659 fn default_null_all_primitives() -> Result<(), Box<dyn std::error::Error>> {
660 assert_eq!(default_null(PrimitiveType::Char), Some(0));
661 assert_eq!(default_null(PrimitiveType::Int8), Some(-128i8 as u64));
662 assert_eq!(default_null(PrimitiveType::UInt8), Some(255));
663 assert_eq!(default_null(PrimitiveType::Int16), Some(-32768i16 as u64));
664 assert_eq!(default_null(PrimitiveType::UInt16), Some(65535));
665 assert_eq!(
666 default_null(PrimitiveType::Int32),
667 Some(-2147483648i32 as u64)
668 );
669 assert_eq!(default_null(PrimitiveType::UInt32), Some(4294967295));
670 assert_eq!(
671 default_null(PrimitiveType::Int64),
672 Some(9223372036854775808u64)
673 ); assert_eq!(default_null(PrimitiveType::UInt64), Some(u64::MAX));
675 assert!(default_null(PrimitiveType::Float).is_some());
676 assert!(default_null(PrimitiveType::Double).is_some());
677
678 Ok(())
679 }
680
681 #[test]
682 fn default_min_all_primitives() -> Result<(), Box<dyn std::error::Error>> {
683 assert_eq!(default_min(PrimitiveType::Char), Some(0x20));
684 assert_eq!(default_min(PrimitiveType::UInt8), Some(0));
685 assert_eq!(default_min(PrimitiveType::UInt16), Some(0));
686 assert_eq!(default_min(PrimitiveType::UInt32), Some(0));
687 assert_eq!(default_min(PrimitiveType::UInt64), Some(0));
688 assert!(default_min(PrimitiveType::Int8).is_some());
689 assert!(default_min(PrimitiveType::Int16).is_some());
690 assert!(default_min(PrimitiveType::Int32).is_some());
691 assert!(default_min(PrimitiveType::Int64).is_some());
692 assert!(default_min(PrimitiveType::Float).is_some());
693 assert!(default_min(PrimitiveType::Double).is_some());
694
695 Ok(())
696 }
697
698 #[test]
699 fn default_max_all_primitives() -> Result<(), Box<dyn std::error::Error>> {
700 assert_eq!(default_max(PrimitiveType::Char), Some(0x7E));
701 assert_eq!(default_max(PrimitiveType::Int8), Some(127));
702 assert_eq!(default_max(PrimitiveType::UInt8), Some(254));
703 assert_eq!(default_max(PrimitiveType::Int16), Some(32767));
704 assert_eq!(default_max(PrimitiveType::UInt16), Some(65534));
705 assert_eq!(default_max(PrimitiveType::Int32), Some(2147483647));
706 assert_eq!(default_max(PrimitiveType::UInt32), Some(4294967294));
707 assert_eq!(default_max(PrimitiveType::Int64), Some(9223372036854775807));
708 assert_eq!(
709 default_max(PrimitiveType::UInt64),
710 Some(18446744073709551614)
711 );
712 assert!(default_max(PrimitiveType::Float).is_some());
713 assert!(default_max(PrimitiveType::Double).is_some());
714
715 Ok(())
716 }
717
718 #[test]
719 fn composite_offsets_assigned_sequentially() -> Result<(), Box<dyn std::error::Error>> {
720 let mut ir = minimal_schema();
721 resolve_schema(&mut ir, None).unwrap();
722 let hdr = ir
723 .tokens
724 .iter()
725 .find(|t| t.name == "messageHeader")
726 .unwrap();
727 assert!(hdr.encoding.offset.is_some());
729
730 Ok(())
731 }
732
733 #[test]
734 fn message_offsets_assigned_correctly() -> Result<(), Box<dyn std::error::Error>> {
735 let mut ir = minimal_schema();
736 resolve_schema(&mut ir, None).unwrap();
737 let msg = ir
738 .tokens
739 .iter()
740 .find(|t| t.signal == Signal::BeginMessage)
741 .unwrap();
742 assert!(msg.encoding.offset.is_some());
743 let field = ir.tokens.iter().find(|t| t.name == "x").unwrap();
745 assert_eq!(field.encoding.offset, Some(0));
746
747 Ok(())
748 }
749
750 #[test]
751 fn explicit_offset_preserved() -> Result<(), Box<dyn std::error::Error>> {
752 let ir = crate::parse(
753 r#"<?xml version="1.0"?>
754<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
755<types><composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite></types>
756<sbe:message name="A" id="1"><field name="x" id="1" type="uint32" offset="0"/><field name="y" id="2" type="uint16" offset="4"/></sbe:message>
757</sbe:messageSchema>"#,
758 ).unwrap();
759 let mut ir = ir;
760 resolve_schema(&mut ir, None).unwrap();
761 let x = ir.tokens.iter().find(|t| t.name == "x").unwrap();
762 assert_eq!(x.encoding.offset, Some(0));
763 let y = ir.tokens.iter().find(|t| t.name == "y").unwrap();
764 assert_eq!(y.encoding.offset, Some(4));
765
766 Ok(())
767 }
768
769 #[test]
770 fn group_offsets_resolved() -> Result<(), Box<dyn std::error::Error>> {
771 let ir = crate::parse(
772 r#"<?xml version="1.0"?>
773<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
774<types>
775 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
776 <composite name="groupSizeEncoding"><type name="blockLength" primitiveType="uint16"/><type name="numInGroup" primitiveType="uint16"/></composite>
777</types>
778<sbe:message name="A" id="1">
779 <field name="x" id="1" type="uint32"/>
780 <group name="items" id="2" dimensionType="groupSizeEncoding">
781 <field name="a" id="1" type="uint32"/>
782 <field name="b" id="2" type="uint16"/>
783 </group>
784</sbe:message>
785</sbe:messageSchema>"#,
786 ).unwrap();
787 let mut ir = ir;
788 resolve_schema(&mut ir, None).unwrap();
789 let a = ir.tokens.iter().find(|t| t.name == "a").unwrap();
790 assert_eq!(a.encoding.offset, Some(0));
791 let b = ir.tokens.iter().find(|t| t.name == "b").unwrap();
792 assert_eq!(b.encoding.offset, Some(4));
793
794 Ok(())
795 }
796
797 #[test]
798 fn vardata_offsets_resolved() -> Result<(), Box<dyn std::error::Error>> {
799 let ir = crate::parse(
800 r#"<?xml version="1.0"?>
801<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
802<types>
803 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
804 <composite name="varDataEncoding"><type name="length" primitiveType="uint32"/><type name="varData" primitiveType="uint8" length="0"/></composite>
805</types>
806<sbe:message name="A" id="1">
807 <field name="x" id="1" type="uint32"/>
808 <data name="payload" id="2" type="varDataEncoding"/>
809</sbe:message>
810</sbe:messageSchema>"#,
811 ).unwrap();
812 let mut ir = ir;
813 resolve_schema(&mut ir, None).unwrap();
814 let msg = ir
815 .tokens
816 .iter()
817 .find(|t| t.signal == Signal::BeginMessage)
818 .unwrap();
819 assert!(msg.encoding.offset.is_some());
820
821 Ok(())
822 }
823
824 #[test]
825 fn enum_block_size_calculated() -> Result<(), Box<dyn std::error::Error>> {
826 let ir = crate::parse(
827 r#"<?xml version="1.0"?>
828<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
829<types>
830 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
831 <enum name="Colour" encodingType="uint8"><validValue name="R">1</validValue></enum>
832</types>
833<sbe:message name="A" id="1"><field name="c" id="1" type="Colour"/></sbe:message>
834</sbe:messageSchema>"#,
835 ).unwrap();
836 let mut ir = ir;
837 resolve_schema(&mut ir, None).unwrap();
838 let msg = ir
839 .tokens
840 .iter()
841 .find(|t| t.signal == Signal::BeginMessage)
842 .unwrap();
843 assert_eq!(msg.encoding.offset, Some(1));
845
846 Ok(())
847 }
848
849 #[test]
850 fn set_block_size_calculated() -> Result<(), Box<dyn std::error::Error>> {
851 let ir = crate::parse(
852 r#"<?xml version="1.0"?>
853<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
854<types>
855 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
856 <set name="Flags" encodingType="uint8"><choice name="A">0</choice></set>
857</types>
858<sbe:message name="A" id="1"><field name="f" id="1" type="Flags"/></sbe:message>
859</sbe:messageSchema>"#,
860 ).unwrap();
861 let mut ir = ir;
862 resolve_schema(&mut ir, None).unwrap();
863 let msg = ir
864 .tokens
865 .iter()
866 .find(|t| t.signal == Signal::BeginMessage)
867 .unwrap();
868 assert_eq!(msg.encoding.offset, Some(1));
869
870 Ok(())
871 }
872
873 #[test]
874 fn constant_field_does_not_affect_block_length() -> Result<(), Box<dyn std::error::Error>> {
875 let ir = crate::parse(
876 r#"<?xml version="1.0"?>
877<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
878<types>
879 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
880 <type name="ConstVal" primitiveType="char" presence="constant">X</type>
881</types>
882<sbe:message name="A" id="1"><field name="x" id="1" type="uint32"/><field name="c" id="2" type="ConstVal"/></sbe:message>
883</sbe:messageSchema>"#,
884 ).unwrap();
885 let mut ir = ir;
886 resolve_schema(&mut ir, None).unwrap();
887 let msg = ir
888 .tokens
889 .iter()
890 .find(|t| t.signal == Signal::BeginMessage)
891 .unwrap();
892 assert_eq!(msg.encoding.offset, Some(4));
894 Ok(())
895 }
896
897 #[test]
898 fn nested_composite_offsets_resolved() -> Result<(), Box<dyn std::error::Error>> {
899 let ir = crate::parse(
900 r#"<?xml version="1.0"?>
901<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
902<types>
903 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
904 <composite name="Point"><type name="px" primitiveType="int32"/><type name="py" primitiveType="int32"/></composite>
905</types>
906<sbe:message name="A" id="1"><field name="p" id="1" type="Point"/></sbe:message>
907</sbe:messageSchema>"#,
908 ).unwrap();
909 let mut ir = ir;
910 resolve_schema(&mut ir, None).unwrap();
911 let msg = ir
912 .tokens
913 .iter()
914 .find(|t| t.signal == Signal::BeginMessage)
915 .unwrap();
916 assert_eq!(msg.encoding.offset, Some(8));
918
919 Ok(())
920 }
921
922 #[test]
923 fn take_source_code_from_duplicate_template_id() -> Result<(), Box<dyn std::error::Error>> {
924 let mut err = ResolveError::DuplicateTemplateId {
925 id: 1,
926 name: "test".to_string(),
927 source_code: None,
928 first_label: None,
929 second_label: None,
930 };
931 assert!(err.take_source_code().is_none()); Ok(())
934 }
935
936 #[test]
937 fn take_source_code_from_unknown_type() -> Result<(), Box<dyn std::error::Error>> {
938 let mut err = ResolveError::UnknownType {
939 name: "Foo".to_string(),
940 source_code: None,
941 span: None,
942 };
943 assert!(err.take_source_code().is_none());
944
945 Ok(())
946 }
947
948 #[test]
949 fn take_source_code_from_invalid_offset() -> Result<(), Box<dyn std::error::Error>> {
950 let mut err = ResolveError::InvalidOffset {
951 offset: 99,
952 source_code: None,
953 span: None,
954 };
955 assert!(err.take_source_code().is_none());
956
957 Ok(())
958 }
959
960 #[test]
961 fn take_source_code_from_empty_composite() -> Result<(), Box<dyn std::error::Error>> {
962 let mut err = ResolveError::EmptyComposite {
963 name: "Empty".to_string(),
964 source_code: None,
965 span: None,
966 };
967 assert!(err.take_source_code().is_none());
968
969 Ok(())
970 }
971
972 #[test]
973 fn take_source_code_from_since_version() -> Result<(), Box<dyn std::error::Error>> {
974 let mut err = ResolveError::SinceVersionBeyondSchema {
975 version: 5,
976 schema_version: 0,
977 name: "field".to_string(),
978 source_code: None,
979 span: None,
980 };
981 assert!(err.take_source_code().is_none());
982
983 Ok(())
984 }
985
986 #[test]
987 fn resolve_error_displays() -> Result<(), Box<dyn std::error::Error>> {
988 let err = ResolveError::DuplicateTemplateId {
989 id: 1,
990 name: "A".to_string(),
991 source_code: None,
992 first_label: None,
993 second_label: None,
994 };
995 assert!(format!("{err}").contains("duplicate template id 1"));
996
997 let err = ResolveError::UnknownType {
998 name: "Foo".to_string(),
999 source_code: None,
1000 span: None,
1001 };
1002 assert!(format!("{err}").contains("unknown type reference Foo"));
1003
1004 let err = ResolveError::InvalidOffset {
1005 offset: 42,
1006 source_code: None,
1007 span: None,
1008 };
1009 assert!(format!("{err}").contains("offset 42"));
1010
1011 let err = ResolveError::EmptyComposite {
1012 name: "X".to_string(),
1013 source_code: None,
1014 span: None,
1015 };
1016 assert!(format!("{err}").contains("composite X"));
1017
1018 let err = ResolveError::SinceVersionBeyondSchema {
1019 version: 3,
1020 schema_version: 0,
1021 name: "y".to_string(),
1022 source_code: None,
1023 span: None,
1024 };
1025 assert!(format!("{err}").contains("sinceVersion 3"));
1026
1027 Ok(())
1028 }
1029
1030 #[test]
1031 fn fixed_array_field_offset() -> Result<(), Box<dyn std::error::Error>> {
1032 let ir = crate::parse(
1033 r#"<?xml version="1.0"?>
1034<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
1035<types>
1036 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
1037 <type name="int32array4" primitiveType="int32" length="4"/>
1038</types>
1039<sbe:message name="A" id="1"><field name="nums" id="1" type="int32array4"/></sbe:message>
1040</sbe:messageSchema>"#,
1041 ).unwrap();
1042 let mut ir = ir;
1043 resolve_schema(&mut ir, None).unwrap();
1044 let msg = ir
1045 .tokens
1046 .iter()
1047 .find(|t| t.signal == Signal::BeginMessage)
1048 .unwrap();
1049 assert_eq!(msg.encoding.offset, Some(16));
1051
1052 Ok(())
1053 }
1054
1055 #[test]
1056 fn nested_group_offsets_resolved() -> Result<(), Box<dyn std::error::Error>> {
1057 let ir = crate::parse(
1058 r#"<?xml version="1.0"?>
1059<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe" package="t" id="1" version="0" byteOrder="littleEndian">
1060<types>
1061 <composite name="messageHeader"><type name="blockLength" primitiveType="uint16"/><type name="templateId" primitiveType="uint16"/><type name="schemaId" primitiveType="uint16"/><type name="version" primitiveType="uint16"/></composite>
1062 <composite name="groupSizeEncoding"><type name="blockLength" primitiveType="uint16"/><type name="numInGroup" primitiveType="uint16"/></composite>
1063</types>
1064<sbe:message name="A" id="1">
1065 <group name="outer" id="2" dimensionType="groupSizeEncoding">
1066 <field name="a" id="1" type="uint32"/>
1067 <group name="inner" id="3" dimensionType="groupSizeEncoding">
1068 <field name="b" id="1" type="uint16"/>
1069 </group>
1070 </group>
1071</sbe:message>
1072</sbe:messageSchema>"#,
1073 ).unwrap();
1074 let mut ir = ir;
1075 resolve_schema(&mut ir, None).unwrap();
1076 let b = ir.tokens.iter().find(|t| t.name == "b").unwrap();
1077 assert_eq!(b.encoding.offset, Some(0));
1078
1079 Ok(())
1080 }
1081
1082 #[test]
1083 fn begin_message_without_id_skips_duplicate_check() -> Result<(), Box<dyn std::error::Error>> {
1084 let mut ir = Ir {
1087 package: "t".to_string(),
1088 id: 1,
1089 version: 0,
1090 byte_order: crate::ir::ByteOrder::LittleEndian,
1091 description: None,
1092 semantic_version: None,
1093 header_type: "messageHeader".to_string(),
1094 tokens: vec![
1095 Token {
1096 id: None,
1097 name: "A".to_string(),
1098 signal: Signal::BeginMessage,
1099 encoding: crate::ir::Encoding::default(),
1100 span: None,
1101 },
1102 Token {
1103 id: None,
1104 name: "A".to_string(),
1105 signal: Signal::EndMessage,
1106 encoding: crate::ir::Encoding::default(),
1107 span: None,
1108 },
1109 ],
1110 };
1111 assert!(resolve_schema(&mut ir, None).is_ok());
1113
1114 Ok(())
1115 }
1116
1117 #[test]
1118 fn find_matching_end_fallback_on_no_match() -> Result<(), Box<dyn std::error::Error>> {
1119 let tokens = vec![
1121 Token {
1122 id: None,
1123 name: "X".to_string(),
1124 signal: Signal::BeginComposite,
1125 encoding: crate::ir::Encoding::default(),
1126 span: None,
1127 },
1128 Token {
1129 id: None,
1130 name: "Y".to_string(),
1131 signal: Signal::BeginField,
1132 encoding: crate::ir::Encoding::default(),
1133 span: None,
1134 },
1135 ];
1136 let end = find_matching_end(&tokens, 0, Signal::BeginComposite, Signal::EndComposite);
1138 assert_eq!(end, 1); Ok(())
1141 }
1142
1143 #[test]
1144 fn get_token_block_size_catch_all_signal() -> Result<(), Box<dyn std::error::Error>> {
1145 let tokens = vec![Token {
1147 id: None,
1148 name: "X".to_string(),
1149 signal: Signal::EndField,
1150 encoding: crate::ir::Encoding::default(),
1151 span: None,
1152 }];
1153 let (size, next) = get_token_block_size(&tokens, 0);
1154 assert_eq!(size, 0);
1155 assert_eq!(next, 1);
1156
1157 Ok(())
1158 }
1159
1160 #[test]
1161 fn group_without_dimension_composite() -> Result<(), Box<dyn std::error::Error>> {
1162 let mut tokens = vec![
1164 Token {
1165 id: Some(1),
1166 name: "grp".to_string(),
1167 signal: Signal::BeginGroup,
1168 encoding: crate::ir::Encoding::default(),
1169 span: None,
1170 },
1171 Token {
1173 id: None,
1174 name: "field".to_string(),
1175 signal: Signal::BeginField,
1176 encoding: crate::ir::Encoding {
1177 primitive_type: Some(PrimitiveType::UInt32),
1178 ..crate::ir::Encoding::default()
1179 },
1180 span: None,
1181 },
1182 Token {
1183 id: None,
1184 name: "field".to_string(),
1185 signal: Signal::EndField,
1186 encoding: crate::ir::Encoding::default(),
1187 span: None,
1188 },
1189 Token {
1190 id: None,
1191 name: "grp".to_string(),
1192 signal: Signal::EndGroup,
1193 encoding: crate::ir::Encoding::default(),
1194 span: None,
1195 },
1196 ];
1197 let src: Option<miette::NamedSource<String>> = None;
1198 let result = resolve_group_offsets(&mut tokens, &src);
1199 assert!(result.is_ok());
1200 assert_eq!(tokens[1].encoding.offset, Some(0));
1201
1202 Ok(())
1203 }
1204
1205 #[test]
1206 fn vardata_without_type_composite() -> Result<(), Box<dyn std::error::Error>> {
1207 let mut tokens = vec![
1209 Token {
1210 id: Some(1),
1211 name: "data".to_string(),
1212 signal: Signal::BeginVarData,
1213 encoding: crate::ir::Encoding::default(),
1214 span: None,
1215 },
1216 Token {
1218 id: None,
1219 name: "data".to_string(),
1220 signal: Signal::EndVarData,
1221 encoding: crate::ir::Encoding::default(),
1222 span: None,
1223 },
1224 ];
1225 let src: Option<miette::NamedSource<String>> = None;
1226 let result = resolve_vardata_offsets(&mut tokens, &src);
1227 assert!(result.is_ok());
1228
1229 Ok(())
1230 }
1231}