Skip to content

Commit f65b2f3

Browse files
committed
Remove legacy from_bytes function for message parsing
1 parent 1503602 commit f65b2f3

5 files changed

Lines changed: 58 additions & 79 deletions

File tree

crates/hotfix-message/src/builder.rs

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ fn extract_groups(
547547
mod tests {
548548
use crate::builder::MessageBuilder;
549549
use crate::field_types::Currency;
550-
use crate::message::{Config, Message};
550+
use crate::message::Config;
551551
use crate::parsed_message::{GarbledReason, InvalidReason, ParsedMessage};
552552
use crate::{Part, fix44};
553553
use hotfix_dictionary::{Dictionary, IsFieldDefinition, TagU32};
@@ -655,11 +655,9 @@ mod tests {
655655
#[test]
656656
fn parse_simple_message() {
657657
let raw = b"8=FIX.4.4|9=40|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=093|";
658-
let dict = Dictionary::fix44();
658+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
659659

660-
let message = Message::from_bytes(&CONFIG, &dict, raw)
661-
.into_message()
662-
.unwrap();
660+
let message = builder.build(raw).into_message().unwrap();
663661

664662
let begin: &str = message.header().get(fix44::BEGIN_STRING).unwrap();
665663
assert_eq!(begin, "FIX.4.4");
@@ -683,11 +681,10 @@ mod tests {
683681
#[test]
684682
fn repeating_group_entries() {
685683
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|";
686-
let dict = Dictionary::fix44();
684+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
685+
686+
let message = builder.build(raw).into_message().unwrap();
687687

688-
let message = Message::from_bytes(&CONFIG, &dict, raw)
689-
.into_message()
690-
.unwrap();
691688
let begin: &str = message.header().get(fix44::BEGIN_STRING).unwrap();
692689
assert_eq!(begin, "FIX.4.4");
693690

@@ -706,10 +703,9 @@ mod tests {
706703
#[test]
707704
fn nested_repeating_group_entries() {
708705
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|";
709-
let dict = Dictionary::fix44();
706+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
707+
let message = builder.build(raw).into_message().unwrap();
710708

711-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
712-
let message = parsed_message.into_message().unwrap();
713709
let party_a = message.get_group(fix44::NO_PARTY_I_DS, 0).unwrap();
714710
let party_a_0 = party_a
715711
.get_group(fix44::NO_PARTY_SUB_I_DS.tag(), 0)
@@ -731,9 +727,9 @@ mod tests {
731727
#[test]
732728
fn test_begin_string_not_the_first_tag() {
733729
let raw = b"9=40|8=FIX.4.4|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=093|";
734-
let dict = Dictionary::fix44();
730+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
731+
let parsed_message = builder.build(raw);
735732

736-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
737733
assert!(matches!(
738734
parsed_message,
739735
ParsedMessage::Garbled(GarbledReason::InvalidBeginString)
@@ -743,9 +739,9 @@ mod tests {
743739
#[test]
744740
fn test_body_length_not_the_second_tag() {
745741
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|";
746-
let dict = Dictionary::fix44();
742+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
743+
let parsed_message = builder.build(raw);
747744

748-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
749745
assert!(matches!(
750746
parsed_message,
751747
ParsedMessage::Garbled(GarbledReason::InvalidBodyLength)
@@ -755,9 +751,9 @@ mod tests {
755751
#[test]
756752
fn test_body_length_is_wrong() {
757753
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|";
758-
let dict = Dictionary::fix44();
754+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
755+
let parsed_message = builder.build(raw);
759756

760-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
761757
assert!(matches!(
762758
parsed_message,
763759
ParsedMessage::Garbled(GarbledReason::InvalidBodyLength)
@@ -767,9 +763,9 @@ mod tests {
767763
#[test]
768764
fn test_body_length_exceeds_message_length() {
769765
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|";
770-
let dict = Dictionary::fix44();
766+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
767+
let parsed_message = builder.build(raw);
771768

772-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
773769
assert!(matches!(
774770
parsed_message,
775771
ParsedMessage::Garbled(GarbledReason::InvalidBodyLength)
@@ -779,9 +775,9 @@ mod tests {
779775
#[test]
780776
fn test_msg_type_is_not_the_third_tag() {
781777
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|";
782-
let dict = Dictionary::fix44();
778+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
779+
let parsed_message = builder.build(raw);
783780

784-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
785781
assert!(matches!(
786782
parsed_message,
787783
ParsedMessage::Garbled(GarbledReason::InvalidMsgType)
@@ -791,9 +787,9 @@ mod tests {
791787
#[test]
792788
fn test_checksum_is_not_the_last_tag() {
793789
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|";
794-
let dict = Dictionary::fix44();
790+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
791+
let parsed_message = builder.build(raw);
795792

796-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
797793
assert!(matches!(
798794
parsed_message,
799795
ParsedMessage::Garbled(GarbledReason::InvalidChecksum)
@@ -803,9 +799,9 @@ mod tests {
803799
#[test]
804800
fn test_invalid_checksum() {
805801
let raw = b"8=FIX.4.4|9=40|35=D|49=AFUNDMGR|56=ABROKER|15=USD|59=0|10=000|";
806-
let dict = Dictionary::fix44();
802+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
803+
let parsed_message = builder.build(raw);
807804

808-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
809805
assert!(matches!(
810806
parsed_message,
811807
ParsedMessage::Garbled(GarbledReason::InvalidChecksum)
@@ -815,9 +811,9 @@ mod tests {
815811
#[test]
816812
fn test_invalid_field_in_body() {
817813
let raw = b"8=FIX.4.4|9=53|35=D|49=AFUNDMGR|9999=invalid|56=ABROKER|15=USD|59=0|10=229|";
818-
let dict = Dictionary::fix44();
814+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
815+
let parsed_message = builder.build(raw);
819816

820-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
821817
assert!(matches!(
822818
parsed_message,
823819
ParsedMessage::Invalid {
@@ -832,9 +828,9 @@ mod tests {
832828
// tag=384 is `NoMsgTypes`, which is supposed to have `RefMsgType` (tag=372) and `MsgDirection` (tag=385)
833829
// in our message, `RefMsgType` is missing
834830
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|";
835-
let dict = Dictionary::fix44();
831+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
832+
let parsed_message = builder.build(raw);
836833

837-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw);
838834
assert!(matches!(
839835
parsed_message,
840836
ParsedMessage::Invalid {
@@ -852,16 +848,13 @@ mod tests {
852848
fn test_parsing_nested_component_inside_group() {
853849
// an `AllocationInstruction` with `CommissionData` nested inside `AllocGrp`
854850
let raw = b"8=FIX.4.4|9=252|35=J|49=SELLSIDE|56=BUYSIDE|34=100|52=20251023-14:30:00|70=ALLOC001|71=0|626=1|854=0|55=AAPL|107=Apple Inc|167=CS|54=1|53=10000|60=20251023|75=20251023|381=250000|78=2|79=ACC001|661=1|80=5000|12=100|13=3|11=5|79=ACC002|661=1|80=5000|12=75|13=2|11=3.75|10=031|";
855-
let dict = Dictionary::fix44();
856-
857-
let parsed_message = Message::from_bytes(&CONFIG, &dict, raw)
858-
.into_message()
859-
.unwrap();
851+
let builder = MessageBuilder::new(Dictionary::fix44(), CONFIG).unwrap();
852+
let message = builder.build(raw).into_message().unwrap();
860853

861-
let alloc_1 = parsed_message.get_group(fix44::NO_ALLOCS, 0).unwrap();
854+
let alloc_1 = message.get_group(fix44::NO_ALLOCS, 0).unwrap();
862855
assert_eq!(alloc_1.get::<&str>(fix44::ALLOC_ID).unwrap(), "ALLOC001");
863856

864-
let alloc_2 = parsed_message.get_group(fix44::NO_ALLOCS, 1).unwrap();
857+
let alloc_2 = message.get_group(fix44::NO_ALLOCS, 1).unwrap();
865858
assert_eq!(alloc_2.get::<&str>(fix44::ALLOC_ID).unwrap(), "ALLOC002");
866859
}
867860
}

crates/hotfix-message/src/encoder.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ impl Encode for FieldMap {
5252

5353
#[cfg(test)]
5454
mod tests {
55-
use crate::Part;
5655
use crate::field_map::Field;
5756
use crate::field_types::{Date, Time, Timestamp};
5857
use crate::fix44;
5958
use crate::message::{Config, Message};
6059
use crate::parts::RepeatingGroup;
60+
use crate::{MessageBuilder, Part};
6161
use hotfix_dictionary::{Dictionary, IsFieldDefinition};
6262

6363
#[test]
@@ -78,10 +78,8 @@ mod tests {
7878
let config = Config { separator: b'|' };
7979
let raw_message = msg.encode(&config)?;
8080

81-
let dict = Dictionary::fix44();
82-
let parsed_message = Message::from_bytes(&config, &dict, &raw_message)
83-
.into_message()
84-
.unwrap();
81+
let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
82+
let parsed_message = builder.build(&raw_message).into_message().unwrap();
8583

8684
let symbol: &str = parsed_message.get(fix44::SYMBOL)?;
8785
assert_eq!(symbol, "AAPL");
@@ -151,10 +149,8 @@ mod tests {
151149
let config = Config { separator: b'|' };
152150
let raw_message = msg.encode(&config)?;
153151

154-
let dict = Dictionary::fix44();
155-
let parsed_message = Message::from_bytes(&config, &dict, &raw_message)
156-
.into_message()
157-
.unwrap();
152+
let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
153+
let parsed_message = builder.build(&raw_message).into_message().unwrap();
158154

159155
let party_a = parsed_message
160156
.get_group(fix44::NO_PARTY_I_DS, 0)

crates/hotfix-message/src/message.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use std::io::Write;
22

33
use crate::FieldType;
4-
use crate::builder::{MessageBuilder, SOH};
4+
use crate::builder::SOH;
55
use crate::encoder::Encode;
66
use crate::error::EncodingResult;
77
use crate::field_map::{Field, FieldMap};
8-
use crate::parsed_message::ParsedMessage;
98
use crate::parts::{Body, Header, Part, RepeatingGroup, Trailer};
109
use crate::{HardCodedFixFieldDefinition, fix44};
11-
use hotfix_dictionary::{Dictionary, FieldLocation, IsFieldDefinition};
10+
use hotfix_dictionary::{FieldLocation, IsFieldDefinition};
1211

1312
pub struct Message {
1413
pub(crate) header: Header,
@@ -37,14 +36,6 @@ impl Message {
3736
}
3837
}
3938

40-
pub fn from_bytes(config: &Config, dict: &Dictionary, data: &[u8]) -> ParsedMessage {
41-
if let Ok(parser) = MessageBuilder::new(dict.clone(), *config) {
42-
parser.build(data)
43-
} else {
44-
ParsedMessage::UnexpectedError("Failed to create message parser".to_string())
45-
}
46-
}
47-
4839
pub fn encode(&mut self, config: &Config) -> EncodingResult<Vec<u8>> {
4940
let mut buffer = Vec::new();
5041

crates/hotfix/src/session.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use anyhow::{Result, anyhow};
77
use chrono::Utc;
88
use hotfix_message::dict::Dictionary;
99
use hotfix_message::message::{Config as MessageConfig, Message};
10-
use hotfix_message::{Part, fix44};
10+
use hotfix_message::{MessageBuilder, Part, fix44};
1111
use std::cmp::Ordering;
1212
use std::pin::Pin;
1313
use tokio::select;
@@ -50,7 +50,7 @@ struct Session<M, S> {
5050
message_config: MessageConfig,
5151
config: SessionConfig,
5252
schedule: SessionSchedule,
53-
dictionary: Dictionary,
53+
message_builder: MessageBuilder,
5454
state: SessionState,
5555
application: ApplicationRef<M>,
5656
store: S,
@@ -67,14 +67,17 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
6767
let schedule_check_timer = sleep(Duration::from_secs(SCHEDULE_CHECK_INTERVAL));
6868

6969
let dictionary = Self::get_data_dictionary(&config);
70+
let message_config = MessageConfig::default();
71+
let message_builder = MessageBuilder::new(dictionary, message_config)
72+
.expect("failed to create message builder");
7073
let schedule = config.schedule.as_ref().try_into().unwrap();
7174

7275
Self {
7376
mailbox,
7477
config,
7578
schedule,
76-
message_config: MessageConfig::default(),
77-
dictionary,
79+
message_config,
80+
message_builder,
7881
state: SessionState::new_disconnected(true, "initialising"),
7982
application,
8083
store,
@@ -100,11 +103,7 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
100103
self.reset_peer_timer(None);
101104
}
102105

103-
match Message::from_bytes(
104-
&self.message_config,
105-
&self.dictionary,
106-
raw_message.as_bytes(),
107-
) {
106+
match self.message_builder.build(raw_message.as_bytes()) {
108107
ParsedMessage::Valid(message) => {
109108
self.process_message(message).await?;
110109
self.check_end_of_resend().await?;
@@ -657,10 +656,11 @@ impl<M: FixMessage, S: MessageStore> Session<M, S> {
657656
for msg in messages {
658657
let m = String::from_utf8(msg.clone()).unwrap();
659658
debug!(m, "resending message");
660-
let mut message =
661-
Message::from_bytes(&self.message_config, &self.dictionary, msg.as_slice())
662-
.into_message()
663-
.unwrap();
659+
let mut message = self
660+
.message_builder
661+
.build(msg.as_slice())
662+
.into_message()
663+
.unwrap();
664664
sequence_number = message.header().get(fix44::MSG_SEQ_NUM).unwrap();
665665
let message_type: String = message
666666
.header()

crates/hotfix/tests/common/mock_counterparty.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use hotfix::session::SessionRef;
66
use hotfix::transport::FixConnection;
77
use hotfix::transport::reader::ReaderRef;
88
use hotfix::transport::writer::{WriterMessage, WriterRef};
9+
use hotfix_message::MessageBuilder;
910
use hotfix_message::dict::Dictionary;
1011
use hotfix_message::message::{Config as MessageConfig, Message};
1112
use hotfix_message::parsed_message::ParsedMessage;
@@ -19,7 +20,7 @@ pub struct MockCounterparty<M> {
1920
sent_messages: Vec<Vec<u8>>,
2021
session_ref: SessionRef<M>,
2122
session_config: SessionConfig,
22-
dictionary: Dictionary,
23+
message_builder: MessageBuilder,
2324
message_config: MessageConfig,
2425
_connection: FixConnection,
2526
_dc_sender: oneshot::Sender<()>,
@@ -33,6 +34,8 @@ where
3334
let (writer_ref, receiver) = Self::create_writer();
3435
let (reader_ref, dc_sender) = Self::create_reader();
3536
let connection = FixConnection::new(writer_ref, reader_ref);
37+
let message_config = MessageConfig::default();
38+
let message_builder = MessageBuilder::new(Dictionary::fix44(), message_config).unwrap();
3639

3740
session_ref.register_writer(connection.get_writer()).await;
3841

@@ -42,8 +45,8 @@ where
4245
sent_messages: vec![],
4346
session_ref,
4447
session_config,
45-
dictionary: Dictionary::fix44(),
46-
message_config: MessageConfig::default(),
48+
message_builder,
49+
message_config,
4750
_connection: connection,
4851
_dc_sender: dc_sender,
4952
}
@@ -76,7 +79,7 @@ where
7679
return;
7780
}
7881

79-
let parsed = Message::from_bytes(&self.message_config, &self.dictionary, &original_raw);
82+
let parsed = self.message_builder.build(&original_raw);
8083
let mut message = match parsed {
8184
ParsedMessage::Valid(m) => m,
8285
_ => panic!("trying to resend invalid message"),
@@ -166,11 +169,7 @@ where
166169
}
167170

168171
fn parse_message(&self, raw_message: &RawFixMessage) -> Message {
169-
match Message::from_bytes(
170-
&self.message_config,
171-
&self.dictionary,
172-
raw_message.as_bytes(),
173-
) {
172+
match self.message_builder.build(raw_message.as_bytes()) {
174173
ParsedMessage::Valid(valid_message) => valid_message,
175174
_ => {
176175
panic!("only valid messages are supported in the mock counterparty")

0 commit comments

Comments
 (0)