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