1use crate::Part;
2use crate::error::{MessageIntegrityError, ParserError, ParserResult};
3use crate::field_map::Field;
4use crate::field_types::CheckSum;
5use crate::message::{Config, Message};
6use crate::parsed_message::{GarbledReason, InvalidReason, ParsedMessage};
7use crate::parts::{Body, Header, RepeatingGroup, Trailer};
8use crate::tags::{BEGIN_STRING, BODY_LENGTH, CHECK_SUM, MSG_TYPE};
9use hotfix_dictionary::{Dictionary, LayoutItem, LayoutItemKind, TagU32};
10use std::collections::{HashMap, HashSet};
11
12pub const SOH: u8 = 0x1;
13
14const CHECKSUM_LENGTH: usize = 7;
24
25pub struct MessageBuilder {
26 dict: Dictionary,
27 header_tags: HashSet<TagU32>,
28 trailer_tags: HashSet<TagU32>,
29 message_specification: HashMap<String, MessageSpecification>,
30 config: Config,
31}
32
33impl MessageBuilder {
34 pub fn new(dict: Dictionary, config: Config) -> ParserResult<Self> {
35 let header_tags = Self::get_tags_for_component(&dict, "StandardHeader")?;
36 let trailer_tags = Self::get_tags_for_component(&dict, "StandardTrailer")?;
37 let message_definitions = build_message_specifications(&dict)?;
38
39 let parser = Self {
40 dict,
41 header_tags,
42 trailer_tags,
43 message_specification: message_definitions,
44 config,
45 };
46
47 Ok(parser)
48 }
49
50 pub fn build(&self, data: &[u8]) -> ParsedMessage {
51 let mut parser = Parser {
52 position: 0,
53 raw_data: data,
54 config: &self.config,
55 };
56 let (mut header, mut trailer) = match self.verify_integrity(&mut parser) {
57 Ok((header, trailer)) => (header, trailer),
58 Err(err) => return err.into(),
59 };
60
61 let next = match self.build_header(&mut header, &mut parser) {
62 Ok(next_field) => next_field,
63 Err(err) => {
64 return parser_error_to_parsed_message(err, header);
65 }
66 };
67
68 #[allow(clippy::expect_used)]
69 let msg_type = header.get::<&str>(MSG_TYPE).expect("we know this is valid at this point as we have already verified the integrity of the header");
70 let (body, next) = match self.build_body(msg_type, &mut parser, next) {
71 Ok((body, field)) => (body, field),
72 Err(err) => {
73 return parser_error_to_parsed_message(err, header);
74 }
75 };
76
77 self.build_trailer(&mut trailer, &mut parser, next);
78
79 let msg = Message {
80 header,
81 body,
82 trailer,
83 };
84
85 ParsedMessage::Valid(msg)
86 }
87
88 fn verify_integrity(
89 &self,
90 parser: &mut Parser,
91 ) -> Result<(Header, Trailer), MessageIntegrityError> {
92 let mut header = Header::default();
93
94 let begin_string_field = self.parse_begin_string(parser)?;
96 header.fields.insert(begin_string_field);
97
98 let body_length_field = self.parse_body_length(parser)?;
100 header.fields.insert(body_length_field);
101
102 let body_length = if let Ok(body_length) = header.get::<usize>(BODY_LENGTH) {
104 let expected_length = parser.position + body_length + CHECKSUM_LENGTH;
105 if parser.raw_data.len() != expected_length {
106 return Err(MessageIntegrityError::InvalidBodyLength);
107 }
108 body_length
109 } else {
110 return Err(MessageIntegrityError::InvalidBodyLength);
112 };
113
114 let mut trailer = Trailer::default();
116 let checksum_field = parser.parse_checksum(parser.position + body_length)?;
117 trailer.fields.insert(checksum_field);
118
119 if let Ok(checksum) = trailer.get::<CheckSum>(CHECK_SUM) {
120 let computed_checksum =
121 CheckSum::compute(&parser.raw_data[0..parser.position + body_length]);
122 if computed_checksum != checksum {
123 return Err(MessageIntegrityError::InvalidCheckSum);
124 }
125 }
126
127 let msg_type_field = self.parse_message_type(parser)?;
129 header.fields.insert(msg_type_field);
130
131 Ok((header, trailer))
132 }
133
134 fn parse_begin_string(&self, parser: &mut Parser) -> Result<Field, MessageIntegrityError> {
135 if let Some(begin_string) = parser.next_field()
136 && begin_string.tag.get() == BEGIN_STRING.tag
137 {
138 Ok(begin_string)
139 } else {
140 Err(MessageIntegrityError::InvalidBeginString)
141 }
142 }
143
144 fn parse_body_length(&self, parser: &mut Parser) -> Result<Field, MessageIntegrityError> {
145 if let Some(body_length) = parser.next_field()
146 && body_length.tag.get() == BODY_LENGTH.tag
147 {
148 Ok(body_length)
149 } else {
150 Err(MessageIntegrityError::InvalidBodyLength)
151 }
152 }
153
154 fn parse_message_type(&self, parser: &mut Parser) -> Result<Field, MessageIntegrityError> {
155 if let Some(msg_type) = parser.next_field()
156 && msg_type.tag.get() == MSG_TYPE.tag
157 {
158 Ok(msg_type)
159 } else {
160 Err(MessageIntegrityError::InvalidMsgType)
161 }
162 }
163
164 fn build_header(&self, header: &mut Header, parser: &mut Parser) -> ParserResult<Field> {
165 loop {
168 let field = parser.next_field().ok_or(ParserError::Malformed(
169 "message ended within header".to_string(),
170 ))?;
171
172 if self.is_header_tag(field.tag) {
173 header.fields.insert(field);
174 } else {
175 #[allow(clippy::expect_used)]
178 let msg_type = header
179 .get::<&str>(MSG_TYPE)
180 .expect("this never fails as we've verified the integrity of the header");
181 if self.dict.message_by_msgtype(msg_type).is_none() {
182 return Err(ParserError::InvalidMsgType(msg_type.to_string()));
183 }
184
185 return Ok(field);
186 }
187 }
188 }
189
190 fn build_body(
191 &self,
192 msg_type: &str,
193 parser: &mut Parser,
194 next_field: Field,
195 ) -> ParserResult<(Body, Field)> {
196 let message_def = self.get_message_def(msg_type)?;
197 let mut body = Body::default();
198 let mut field = next_field;
199
200 while message_def.contains_tag(field.tag) {
201 let tag = field.tag;
202 body.store_field(field);
203
204 let field_def = self.get_dict_field_by_tag(tag.get())?;
206 match message_def.get_group(tag) {
207 Some(group_def) => {
208 let (groups, next) = Self::parse_groups(parser, group_def, field_def.tag())?;
209 #[allow(clippy::expect_used)]
210 body.set_groups(groups)
211 .expect("groups are guaranteed to be valid at this point");
212 field = next;
213 }
214 None => {
215 field = parser.next_field().ok_or(ParserError::Malformed(
216 "message ended within the body".to_string(),
217 ))?;
218 }
219 }
220 }
221
222 if !self.is_trailer_tag(field.tag) {
223 return Err(ParserError::InvalidField(field.tag.get()));
224 }
225
226 Ok((body, field))
227 }
228
229 fn build_trailer(&self, trailer: &mut Trailer, parser: &mut Parser, next_field: Field) {
230 let mut field = Some(next_field);
231 while let Some(f) = field {
232 if f.tag.get() == CHECK_SUM.tag {
233 break;
234 }
235 trailer.store_field(f);
236 field = parser.next_field();
237 }
238 }
239
240 fn parse_groups(
241 parser: &mut Parser,
242 group_def: &GroupSpecification,
243 start_tag: TagU32,
244 ) -> ParserResult<(Vec<RepeatingGroup>, Field)> {
245 let mut groups = vec![];
246
247 let mut field = parser.next_field().ok_or(ParserError::Malformed(
248 "missing delimiter field".to_string(),
249 ))?;
250 loop {
251 let mut group = RepeatingGroup::new_with_tags(start_tag, group_def.delimiter_tag());
252
253 for field_def in group_def.fields().iter() {
255 let is_required =
256 field_def.is_required || field_def.tag == group_def.delimiter_tag();
257 let current_tag = field.tag;
258 if field_def.tag == current_tag {
259 group.store_field(field);
261 field = if let Some(nested_group_def) = group_def.get_nested_group(current_tag)
262 {
263 let (groups, next) =
264 Self::parse_groups(parser, nested_group_def, current_tag)?;
265 #[allow(clippy::expect_used)]
266 group
267 .set_groups(groups)
268 .expect("groups are guaranteed to be valid at this point");
269 next
270 } else {
271 parser
272 .next_field()
273 .ok_or(ParserError::Malformed("incomplete group".to_string()))?
274 }
275 } else if !is_required {
276 } else {
278 let err = if group_def.contains_tag(field.tag) {
280 ParserError::InvalidGroupFieldOrder {
281 tag: field.tag.get(),
282 group_tag: group_def.number_of_entries_tag().get(),
283 }
284 } else {
285 ParserError::InvalidField(field.tag.get())
286 };
287 return Err(err);
288 }
289 }
290
291 groups.push(group);
294
295 if !group_def.contains_tag(field.tag) {
296 return Ok((groups, field));
297 }
298 }
299 }
300
301 fn get_dict_field_by_tag(&self, tag: u32) -> ParserResult<hotfix_dictionary::Field<'_>> {
302 self.dict
303 .field_by_tag(tag)
304 .ok_or(ParserError::InvalidField(tag))
305 }
306
307 fn is_header_tag(&self, tag: TagU32) -> bool {
308 self.header_tags.contains(&tag)
309 }
310
311 fn is_trailer_tag(&self, tag: TagU32) -> bool {
312 self.trailer_tags.contains(&tag)
313 }
314
315 fn get_message_def(&self, msg_type: &str) -> ParserResult<&MessageSpecification> {
316 match self.message_specification.get(msg_type) {
317 Some(message_def) => Ok(message_def),
318 None => Err(ParserError::InvalidMsgType(msg_type.to_string())),
319 }
320 }
321
322 fn get_tags_for_component(
323 dict: &Dictionary,
324 component_name: &str,
325 ) -> ParserResult<HashSet<TagU32>> {
326 let mut tags = HashSet::new();
327 let component = dict
328 .component_by_name(component_name)
329 .ok_or_else(|| ParserError::InvalidComponent(component_name.to_string()))?;
330 for item in component.items() {
331 if let LayoutItemKind::Field(field) = item.kind() {
332 tags.insert(field.tag());
333 }
334 }
335
336 Ok(tags)
337 }
338}
339
340struct Parser<'a> {
341 position: usize,
342 raw_data: &'a [u8],
343 config: &'a Config,
344}
345
346impl<'a> Parser<'a> {
347 fn next_field(&mut self) -> Option<Field> {
348 let (field, end_position) = self.parse_field_at(self.position)?;
349 self.position = end_position + 1;
350
351 Some(field)
352 }
353
354 fn parse_field_at(&self, position: usize) -> Option<(Field, usize)> {
355 let mut iter = self.raw_data[position..].iter();
356 let equal_sign_position = position + iter.position(|c| *c == b'=')?;
357 let bytes_until_separator = iter.position(|c| *c == self.config.separator)?;
358 let separator_position = equal_sign_position + bytes_until_separator + 1;
359
360 let tag = tag_from_bytes(&self.raw_data[position..equal_sign_position])?;
361 let data = self.raw_data[equal_sign_position + 1..separator_position].to_vec();
362 let field = Field::new(tag, data);
363
364 Some((field, separator_position))
365 }
366
367 fn parse_checksum(&self, checksum_start: usize) -> Result<Field, MessageIntegrityError> {
368 if let Some((checksum, _)) = self.parse_field_at(checksum_start)
369 && checksum.tag.get() == CHECK_SUM.tag
370 {
371 Ok(checksum)
372 } else {
373 Err(MessageIntegrityError::InvalidCheckSum)
374 }
375 }
376}
377
378fn tag_from_bytes(bytes: &[u8]) -> Option<TagU32> {
379 let mut tag = 0u32;
380 for byte in bytes.iter().copied() {
381 tag = tag * 10 + (byte as u32 - b'0' as u32);
382 }
383
384 TagU32::new(tag)
385}
386
387fn parser_error_to_parsed_message(err: ParserError, header: Header) -> ParsedMessage {
388 match err {
389 ParserError::IOError(_) => ParsedMessage::Garbled(GarbledReason::Malformed),
390 ParserError::InvalidField(tag) => ParsedMessage::Invalid {
391 reason: InvalidReason::InvalidField(tag),
392 message: Message::with_header(header),
393 },
394 ParserError::InvalidGroup(tag) => ParsedMessage::Invalid {
395 reason: InvalidReason::InvalidGroup(tag),
396 message: Message::with_header(header),
397 },
398 ParserError::InvalidGroupFieldOrder { tag, group_tag } => ParsedMessage::Invalid {
399 reason: InvalidReason::InvalidOrderInGroup { tag, group_tag },
400 message: Message::with_header(header),
401 },
402 ParserError::InvalidComponent(tag) => ParsedMessage::Invalid {
403 reason: InvalidReason::InvalidComponent(tag),
404 message: Message::with_header(header),
405 },
406 ParserError::InvalidMsgType(msg_type) => ParsedMessage::Invalid {
407 reason: InvalidReason::InvalidMsgType(msg_type),
408 message: Message::with_header(header),
409 },
410 ParserError::Malformed(_) => ParsedMessage::Garbled(GarbledReason::Malformed),
411 }
412}
413
414struct FieldSpecification {
415 pub(crate) tag: TagU32,
416 pub(crate) is_required: bool,
417}
418
419struct GroupSpecification {
420 number_of_entries_tag: TagU32,
421 fields: Vec<FieldSpecification>,
422 nested_groups: HashMap<TagU32, GroupSpecification>,
423}
424
425impl GroupSpecification {
426 pub fn fields(&self) -> &[FieldSpecification] {
427 self.fields.as_slice()
428 }
429 pub fn number_of_entries_tag(&self) -> TagU32 {
430 self.number_of_entries_tag
431 }
432
433 pub fn delimiter_tag(&self) -> TagU32 {
434 #[allow(clippy::expect_used)]
435 self.fields
436 .first()
437 .expect("groups always have at least one field")
438 .tag
439 }
440
441 pub fn contains_tag(&self, tag: TagU32) -> bool {
442 self.fields.iter().any(|f| f.tag == tag)
443 }
444
445 pub fn get_nested_group(&self, tag: TagU32) -> Option<&GroupSpecification> {
446 self.nested_groups.get(&tag)
447 }
448}
449
450struct MessageSpecification {
451 fields: Vec<FieldSpecification>,
452 groups: HashMap<TagU32, GroupSpecification>,
453}
454
455impl MessageSpecification {
456 pub fn contains_tag(&self, tag: TagU32) -> bool {
457 self.fields.iter().any(|f| f.tag == tag)
458 }
459
460 pub fn get_group(&self, tag: TagU32) -> Option<&GroupSpecification> {
461 self.groups.get(&tag)
462 }
463}
464
465fn build_message_specifications(
466 dict: &Dictionary,
467) -> ParserResult<HashMap<String, MessageSpecification>> {
468 let mut definitions = HashMap::new();
469
470 for message in dict.messages() {
471 let fields = message
472 .layout()
473 .flat_map(|item| extract_fields(dict, item))
474 .flatten()
475 .collect();
476
477 let mut groups = HashMap::new();
478 for item in message.layout() {
479 groups.extend(extract_groups(dict, item)?);
480 }
481
482 let message_def = MessageSpecification { fields, groups };
483 definitions.insert(message.msg_type().to_string(), message_def);
484 }
485
486 Ok(definitions)
487}
488
489fn extract_fields(dict: &Dictionary, item: LayoutItem) -> ParserResult<Vec<FieldSpecification>> {
490 let is_required = item.required();
491 let fields = match item.kind() {
492 LayoutItemKind::Component(c) => {
493 let component = dict
494 .component_by_name(c.name())
495 .ok_or_else(|| ParserError::InvalidComponent(c.name().to_string()))?;
496 component
497 .items()
498 .flat_map(|i| extract_fields(dict, i))
499 .flatten()
500 .collect()
501 }
502 LayoutItemKind::Field(field) => vec![FieldSpecification {
503 tag: field.tag(),
504 is_required,
505 }],
506 LayoutItemKind::Group(field, _) => vec![FieldSpecification {
507 tag: field.tag(),
508 is_required,
509 }],
510 };
511
512 Ok(fields)
513}
514
515fn extract_groups(
516 dict: &Dictionary,
517 item: LayoutItem,
518) -> ParserResult<HashMap<TagU32, GroupSpecification>> {
519 let mut groups = HashMap::new();
520 match item.kind() {
521 LayoutItemKind::Component(c) => {
522 let component = dict
523 .component_by_name(c.name())
524 .ok_or_else(|| ParserError::InvalidComponent(c.name().to_string()))?;
525 for i in component.items() {
526 groups.extend(extract_groups(dict, i)?);
527 }
528 }
529 LayoutItemKind::Group(field, items) => {
530 let mut nested_groups = HashMap::new();
531 for i in items.iter() {
532 nested_groups.extend(extract_groups(dict, i.clone())?);
533 }
534 groups.insert(
535 field.tag(),
536 GroupSpecification {
537 number_of_entries_tag: field.tag(),
538 fields: items
539 .iter()
540 .flat_map(|i| extract_fields(dict, i.clone()))
541 .flatten()
542 .collect(),
543 nested_groups,
544 },
545 );
546 }
547 _ => {}
548 };
549
550 Ok(groups)
551}
552
553#[cfg(test)]
554mod tests {
555 use crate::builder::MessageBuilder;
556 use crate::field_types::Currency;
557 use crate::message::Config;
558 use crate::parsed_message::{GarbledReason, InvalidReason, ParsedMessage};
559 use crate::{Part, fix44};
560 use hotfix_dictionary::{Dictionary, IsFieldDefinition, TagU32};
561
562 const CONFIG: Config = Config::with_separator(b'|');
563
564 #[test]
565 fn test_specification_top_level_fields() {
566 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
567 let message_def = builder.get_message_def("J").unwrap();
568
569 assert!(message_def.contains_tag(fix44::SYMBOL.tag()));
571
572 assert!(message_def.contains_tag(fix44::NO_ORDERS.tag()));
574
575 assert!(!message_def.contains_tag(fix44::ORDER_QTY.tag()));
577 }
578
579 #[test]
580 fn test_specification_top_level_groups() {
581 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
582 let message_def = builder.get_message_def("J").unwrap();
583
584 let expected_group_fields = vec![
587 fix44::NO_ORDERS,
588 fix44::NO_ALLOCS,
589 fix44::NO_EXECS,
590 fix44::NO_STIPULATIONS,
591 fix44::NO_PARTY_I_DS,
592 fix44::NO_SECURITY_ALT_ID,
593 fix44::NO_LEGS,
594 fix44::NO_UNDERLYINGS,
595 fix44::NO_EVENTS,
596 fix44::NO_INSTR_ATTRIB,
597 ];
598 assert_eq!(message_def.groups.len(), expected_group_fields.len());
599 for field in expected_group_fields {
600 assert!(
601 message_def
602 .get_group(TagU32::new(field.tag).unwrap())
603 .is_some()
604 );
605 }
606
607 assert!(
609 message_def
610 .get_group(fix44::NO_NESTED2_PARTY_I_DS.tag())
611 .is_none()
612 );
613 }
614
615 #[test]
616 fn test_specification_nested_groups() {
617 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
618 let message_def = builder.get_message_def("J").unwrap();
619
620 let order_alloc_group = message_def.get_group(fix44::NO_ORDERS.tag()).unwrap();
622 assert_eq!(order_alloc_group.nested_groups.len(), 1);
623 let nested_parties_2_group = order_alloc_group
624 .get_nested_group(fix44::NO_NESTED2_PARTY_I_DS.tag())
625 .expect("nested parties group to exist");
626
627 assert_eq!(nested_parties_2_group.nested_groups.len(), 1);
629 let subgroup = nested_parties_2_group
630 .get_nested_group(fix44::NO_NESTED2_PARTY_SUB_I_DS.tag())
631 .expect("parties subgroup to exist");
632 assert!(subgroup.nested_groups.is_empty());
633 }
634
635 #[test]
636 fn test_specification_field_order_in_nested_group() {
637 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
638 let message_def = builder.get_message_def("J").unwrap();
639
640 let order_alloc_group = message_def.get_group(fix44::NO_ORDERS.tag()).unwrap();
642 assert_eq!(order_alloc_group.nested_groups.len(), 1);
643 let nested_parties_2_group = order_alloc_group
644 .get_nested_group(fix44::NO_NESTED2_PARTY_I_DS.tag())
645 .expect("nested parties group to exist");
646
647 let mut fields = nested_parties_2_group.fields.iter();
648 let expected_fields = vec![
649 (fix44::NESTED2_PARTY_ID, false),
650 (fix44::NESTED2_PARTY_ID_SOURCE, false),
651 (fix44::NESTED2_PARTY_ROLE, false),
652 (fix44::NO_NESTED2_PARTY_SUB_I_DS, false),
653 ];
654
655 for (field_definition, is_required) in expected_fields {
656 let next = fields.next().unwrap();
657 assert_eq!(next.tag.get(), field_definition.tag);
658 assert_eq!(next.is_required, is_required);
659 }
660 }
661
662 #[test]
663 fn parse_simple_message() {
664 let raw = b"8=FIX.4.4|9=40|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=093|";
665 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
666
667 let message = builder.build(raw).into_message().unwrap();
668
669 let begin: &str = message.header().get(fix44::BEGIN_STRING).unwrap();
670 assert_eq!(begin, "FIX.4.4");
671
672 let body_length: u32 = message.header().get(fix44::BODY_LENGTH).unwrap();
673 assert_eq!(body_length, 40);
674
675 let message_type: &str = message.header().get(fix44::MSG_TYPE).unwrap();
676 assert_eq!(message_type, "D");
677
678 let currency: &Currency = message.get(fix44::CURRENCY).unwrap();
679 assert_eq!(currency, b"USD");
680
681 let time_in_force: &str = message.get(fix44::TIME_IN_FORCE).unwrap();
682 assert_eq!(time_in_force, "0");
683
684 let checksum: &str = message.trailer().get(fix44::CHECK_SUM).unwrap();
685 assert_eq!(checksum, "093");
686 }
687
688 #[test]
689 fn repeating_group_entries() {
690 let raw = b"8=FIX.4.4|9=191|35=8|49=SENDER|56=TARGET|34=123|52=20231103-12:00:00|11=12345|17=ABC123|150=2|39=1|55=XYZ|54=1|38=200|44=10|32=100|31=10|14=100|6=10|151=100|136=2|137=100|138=EUR|139=7|137=160|138=GBP|139=7|10=140|";
691 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
692
693 let message = builder.build(raw).into_message().unwrap();
694
695 let begin: &str = message.header().get(fix44::BEGIN_STRING).unwrap();
696 assert_eq!(begin, "FIX.4.4");
697
698 let fee1 = message.get_group(fix44::NO_MISC_FEES, 0).unwrap();
699 let amt: f64 = fee1.get(fix44::MISC_FEE_AMT).unwrap();
700 assert_eq!(amt, 100.0);
701
702 let fee2 = message.get_group(fix44::NO_MISC_FEES, 1).unwrap();
703 let fee_type: &str = fee2.get(fix44::MISC_FEE_TYPE).unwrap();
704 assert_eq!(fee_type, "7");
705
706 let checksum: &str = message.trailer().get(fix44::CHECK_SUM).unwrap();
707 assert_eq!(checksum, "140");
708 }
709
710 #[test]
711 fn nested_repeating_group_entries() {
712 let raw = b"8=FIX.4.4|9=247|35=8|34=2|49=Broker|52=20231103-09:30:00|56=Client|11=Order12345|17=Exec12345|150=0|39=0|55=APPL|54=1|38=100|32=50|31=150.00|151=50|14=50|6=150.00|453=2|448=PARTYA|447=D|452=1|802=2|523=SUBPARTYA1|803=1|523=SUBPARTYA2|803=2|448=PARTYB|447=D|452=2|10=129|";
713 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
714 let message = builder.build(raw).into_message().unwrap();
715
716 let party_a = message.get_group(fix44::NO_PARTY_I_DS, 0).unwrap();
717 let party_a_0 = party_a
718 .get_group(fix44::NO_PARTY_SUB_I_DS.tag(), 0)
719 .unwrap();
720 let sub_id_0: &str = party_a_0.get(fix44::PARTY_SUB_ID).unwrap();
721 assert_eq!(sub_id_0, "SUBPARTYA1");
722
723 let party_b = message.get_group(fix44::NO_PARTY_I_DS, 1).unwrap();
724 let party_b_id: &str = party_b.get(fix44::PARTY_ID).unwrap();
725 assert_eq!(party_b_id, "PARTYB");
726
727 let party_b_role: u32 = party_b.get(fix44::PARTY_ROLE).unwrap();
728 assert_eq!(party_b_role, 2);
729
730 let checksum: &str = message.trailer().get(fix44::CHECK_SUM).unwrap();
731 assert_eq!(checksum, "129");
732 }
733
734 #[test]
735 fn test_begin_string_not_the_first_tag() {
736 let raw = b"9=40|8=FIX.4.4|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=093|";
737 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
738 let parsed_message = builder.build(raw);
739
740 assert!(matches!(
741 parsed_message,
742 ParsedMessage::Garbled(GarbledReason::InvalidBeginString)
743 ));
744 }
745
746 #[test]
747 fn test_body_length_not_the_second_tag() {
748 let raw = b"8=FIX.4.4|49=SENDER|9=191|35=8|56=TARGET|34=123|52=20231103-12:00:00|11=12345|17=ABC123|150=2|39=1|55=XYZ|54=1|38=200|44=10|32=100|31=10|14=100|6=10|151=100|136=2|137=100|138=EUR|139=7|137=160|138=GBP|139=7|10=140|";
749 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
750 let parsed_message = builder.build(raw);
751
752 assert!(matches!(
753 parsed_message,
754 ParsedMessage::Garbled(GarbledReason::InvalidBodyLength)
755 ));
756 }
757
758 #[test]
759 fn test_body_length_is_wrong() {
760 let raw = b"8=FIX.4.4|9=192|35=8|49=SENDER|56=TARGET|34=123|52=20231103-12:00:00|11=12345|17=ABC123|150=2|39=1|55=XYZ|54=1|38=200|44=10|32=100|31=10|14=100|6=10|151=100|136=2|137=100|138=EUR|139=7|137=160|138=GBP|139=7|10=140|";
761 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
762 let parsed_message = builder.build(raw);
763
764 assert!(matches!(
765 parsed_message,
766 ParsedMessage::Garbled(GarbledReason::InvalidBodyLength)
767 ));
768 }
769
770 #[test]
771 fn test_body_length_exceeds_message_length() {
772 let raw = b"8=FIX.4.4|9=500|35=8|49=SENDER|56=TARGET|34=123|52=20231103-12:00:00|11=12345|17=ABC123|150=2|39=1|55=XYZ|54=1|38=200|44=10|32=100|31=10|14=100|6=10|151=100|136=2|137=100|138=EUR|139=7|137=160|138=GBP|139=7|10=140|";
773 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
774 let parsed_message = builder.build(raw);
775
776 assert!(matches!(
777 parsed_message,
778 ParsedMessage::Garbled(GarbledReason::InvalidBodyLength)
779 ));
780 }
781
782 #[test]
783 fn test_msg_type_is_not_the_third_tag() {
784 let raw = b"8=FIX.4.4|9=191|49=SENDER|35=8|56=TARGET|34=123|52=20231103-12:00:00|11=12345|17=ABC123|150=2|39=1|55=XYZ|54=1|38=200|44=10|32=100|31=10|14=100|6=10|151=100|136=2|137=100|138=EUR|139=7|137=160|138=GBP|139=7|10=140|";
785 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
786 let parsed_message = builder.build(raw);
787
788 assert!(matches!(
789 parsed_message,
790 ParsedMessage::Garbled(GarbledReason::InvalidMsgType)
791 ));
792 }
793
794 #[test]
795 fn test_checksum_is_not_the_last_tag() {
796 let raw = b"8=FIX.4.4|9=191|35=8|49=SENDER|56=TARGET|34=123|52=20231103-12:00:00|11=12345|17=ABC123|150=2|39=1|55=XYZ|54=1|38=200|44=10|32=100|31=10|14=100|6=10|151=100|136=2|137=100|138=EUR|139=7|137=160|138=GBP|10=140|139=7|";
797 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
798 let parsed_message = builder.build(raw);
799
800 assert!(matches!(
801 parsed_message,
802 ParsedMessage::Garbled(GarbledReason::InvalidChecksum)
803 ));
804 }
805
806 #[test]
807 fn test_invalid_checksum() {
808 let raw = b"8=FIX.4.4|9=40|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=000|";
809 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
810 let parsed_message = builder.build(raw);
811
812 assert!(matches!(
813 parsed_message,
814 ParsedMessage::Garbled(GarbledReason::InvalidChecksum)
815 ));
816 }
817
818 #[test]
819 fn test_invalid_field_in_body() {
820 let raw = b"8=FIX.4.4|9=53|35=D|49=AFUNDMGR|9999=invalid|56=ABROKER|15=USD|59=0|10=229|";
821 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
822 let parsed_message = builder.build(raw);
823
824 assert!(matches!(
825 parsed_message,
826 ParsedMessage::Invalid {
827 reason: InvalidReason::InvalidField(_),
828 ..
829 }
830 ));
831 }
832
833 #[test]
834 fn test_invalid_group_in_body() {
835 let raw = b"8=FIX.4.4|9=75|35=A|49=SENDER|56=TARGET|34=1|52=20231103-12:00:00|98=0|108=30|384=1|385=R|10=050|";
838 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
839 let parsed_message = builder.build(raw);
840
841 assert!(matches!(
842 parsed_message,
843 ParsedMessage::Invalid {
844 reason: InvalidReason::InvalidOrderInGroup {
845 tag: 385,
846 group_tag: 384
847 },
848 ..
849 }
850 ));
851 }
852
853 #[test]
854 fn test_parsing_nested_component_inside_group() {
855 let raw_instrument = "55=AAPL|107=Apple Inc|167=CS";
857 let raw_alloc_group =
858 "78=2|79=ACC001|661=1|80=5000|12=100|13=3|79=ACC002|661=1|80=5000|12=75|13=2";
859 let raw = format!(
860 "8=FIX.4.4|9=222|35=J|49=SELLSIDE|56=BUYSIDE|34=100|52=20251023-14:30:00|70=ALLOC001|71=0|626=1|857=0|54=1|{raw_instrument}|53=10000|6=125|75=20251023|{raw_alloc_group}|10=068|"
861 );
862 let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
863 let message = builder.build(raw.as_bytes()).into_message().unwrap();
864
865 let alloc_1 = message.get_group(fix44::NO_ALLOCS, 0).unwrap();
866 assert_eq!(alloc_1.get::<&str>(fix44::ALLOC_ACCOUNT).unwrap(), "ACC001");
867 assert_eq!(alloc_1.get::<f64>(fix44::COMMISSION).unwrap(), 100.0);
868 assert_eq!(alloc_1.get::<&str>(fix44::COMM_TYPE).unwrap(), "3");
869
870 let alloc_2 = message.get_group(fix44::NO_ALLOCS, 1).unwrap();
871 assert_eq!(alloc_2.get::<&str>(fix44::ALLOC_ACCOUNT).unwrap(), "ACC002");
872 assert_eq!(alloc_2.get::<f64>(fix44::COMMISSION).unwrap(), 75.0);
873 assert_eq!(alloc_2.get::<&str>(fix44::COMM_TYPE).unwrap(), "2");
874 }
875}