1use crate::error::EncodingResult;
2use crate::field_map::{Field, FieldMap};
3use crate::message::Config;
4use hotfix_dictionary::TagU32;
5use std::io::Write;
6
7pub trait Encode {
8 fn write(
9 &self,
10 config: &Config,
11 buffer: &mut Vec<u8>,
12 pre_fields: &[TagU32],
13 ) -> EncodingResult<()>;
14}
15
16impl Encode for FieldMap {
17 fn write(
18 &self,
19 config: &Config,
20 buffer: &mut Vec<u8>,
21 pre_fields: &[TagU32],
22 ) -> EncodingResult<()> {
23 let mut write_field = |field: &Field| {
24 let formatted_tag = format!("{}=", field.tag.get());
25 buffer.write_all(formatted_tag.as_bytes())?;
26 buffer.write_all(&field.data)?;
27 buffer.push(config.separator);
28
29 if let Some(groups) = self.groups.get(&field.tag) {
30 for group in groups {
31 group.get_fields().write(config, buffer, &[])?;
32 }
33 }
34 Ok::<(), crate::error::EncodingError>(())
35 };
36
37 for pre_field_tag in pre_fields {
38 if let Some(field) = self.fields.get(pre_field_tag) {
39 write_field(field)?;
40 }
41 }
42
43 for (tag, field) in &self.fields {
44 if !pre_fields.contains(tag) {
45 write_field(field)?;
46 }
47 }
48
49 Ok(())
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::field_map::Field;
56 use crate::field_types::{Date, Time, Timestamp};
57 use crate::fix44;
58 use crate::message::{Config, Message};
59 use crate::parts::RepeatingGroup;
60 use crate::{MessageBuilder, Part};
61 use hotfix_dictionary::{Dictionary, IsFieldDefinition};
62
63 #[test]
64 fn encode_simple_message() -> anyhow::Result<()> {
65 let mut msg = Message::new("FIX.4.4", "D");
66 msg.set(fix44::MSG_SEQ_NUM, 1);
67 msg.set(fix44::SENDER_COMP_ID, "CLIENT_A");
68 msg.set(fix44::TARGET_COMP_ID, "BROKER_B");
69 msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
70 msg.set(fix44::CL_ORD_ID, "ORDER_0001");
71 msg.set(fix44::SYMBOL, "AAPL");
72 msg.set(fix44::SIDE, fix44::Side::Buy);
73 msg.set(fix44::TRANSACT_TIME, Timestamp::utc_now());
74 msg.set(fix44::ORD_TYPE, fix44::OrdType::Limit);
75 msg.set(fix44::PRICE, 150);
76 msg.set(fix44::ORDER_QTY, 60);
77
78 let config = Config { separator: b'|' };
79 let raw_message = msg.encode(&config)?;
80
81 let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
82 let parsed_message = builder.build(&raw_message).into_message().unwrap();
83
84 let symbol: &str = parsed_message.get(fix44::SYMBOL)?;
85 assert_eq!(symbol, "AAPL");
86
87 let qty: u32 = parsed_message.get(fix44::ORDER_QTY).unwrap();
88 assert_eq!(qty, 60);
89
90 let body_length: usize = parsed_message.header().get(fix44::BODY_LENGTH).unwrap();
91 assert_eq!(body_length, 129);
92
93 Ok(())
94 }
95
96 #[test]
97 fn encode_message_with_repeating_group() -> anyhow::Result<()> {
98 let sending_time = Timestamp::new(
99 Date::new(2023, 11, 7).unwrap(),
100 Time::from_hmsm(11, 0, 0, 0).unwrap(),
101 );
102 let mut msg = Message::new("FIX.4.4", "8");
103 msg.set(fix44::MSG_SEQ_NUM, 1);
104 msg.set(fix44::SENDER_COMP_ID, "BROKER_B");
105 msg.set(fix44::TARGET_COMP_ID, "CLIENT_A");
106 msg.set(fix44::SENDING_TIME, sending_time);
107 msg.set(fix44::CL_ORD_ID, "ORDER_0001");
108 msg.set(fix44::EXEC_ID, "Exec12345");
109 msg.set(fix44::ORD_STATUS, "0");
110 msg.set(fix44::SYMBOL, "AAPL");
111 msg.set(fix44::SIDE, fix44::Side::Buy);
112 msg.set(fix44::ORDER_QTY, 1000);
113 msg.set(fix44::LAST_QTY, 200);
114 msg.set(fix44::LAST_PX, 150.0);
115 msg.set(fix44::LEAVES_QTY, 800);
116 msg.set(fix44::CUM_QTY, 200);
117 msg.set(fix44::AVG_PX, 150.0);
118
119 msg.set(fix44::NO_PARTY_I_DS, 2);
120
121 let mut party_1 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
122 party_1.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_A".to_vec()));
123 party_1.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
124 party_1.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"1".to_vec()));
125 party_1.store_field(Field::new(fix44::NO_PARTY_SUB_I_DS.tag(), b"2".to_vec()));
126
127 let mut subparty_1 = RepeatingGroup::new(fix44::NO_PARTY_SUB_I_DS, fix44::PARTY_SUB_ID);
128 subparty_1.store_field(Field::new(
129 fix44::PARTY_SUB_ID.tag(),
130 b"SUBPARTY_A_1".to_vec(),
131 ));
132 subparty_1.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"1".to_vec()));
133
134 let mut subparty_2 = RepeatingGroup::new(fix44::NO_PARTY_SUB_I_DS, fix44::PARTY_SUB_ID);
135 subparty_2.store_field(Field::new(
136 fix44::PARTY_SUB_ID.tag(),
137 b"SUBPARTY_A_2".to_vec(),
138 ));
139 subparty_2.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"2".to_vec()));
140
141 party_1.set_groups(vec![subparty_1, subparty_2]);
142
143 let mut party_2 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
144 party_2.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_B".to_vec()));
145 party_2.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
146 party_2.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"2".to_vec()));
147
148 msg.body.set_groups(vec![party_1, party_2]);
149 let config = Config { separator: b'|' };
150 let raw_message = msg.encode(&config)?;
151
152 let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
153 let parsed_message = builder.build(&raw_message).into_message().unwrap();
154
155 let party_a = parsed_message
156 .get_group(fix44::NO_PARTY_I_DS, 0)
157 .expect("group to be found");
158 let party_a_0 = party_a
159 .get_group(fix44::NO_PARTY_SUB_I_DS.tag(), 0)
160 .expect("group to be found");
161 let sub_id_0: &str = party_a_0.get(fix44::PARTY_SUB_ID)?;
162 assert_eq!(sub_id_0, "SUBPARTY_A_1");
163
164 let party_b = parsed_message
165 .get_group(fix44::NO_PARTY_I_DS, 1)
166 .expect("group to be found");
167 let party_b_id: &str = party_b.get(fix44::PARTY_ID)?;
168 assert_eq!(party_b_id, "PARTY_B");
169
170 let party_b_role: &str = party_b.get(fix44::PARTY_ROLE)?;
171 assert_eq!(party_b_role, "2");
172
173 let checksum: &str = parsed_message.trailer().get(fix44::CHECK_SUM)?;
174 assert_eq!(checksum, "036");
175
176 let qty: usize = parsed_message.header().get(fix44::BODY_LENGTH).unwrap();
177 assert_eq!(qty, 253);
178
179 Ok(())
180 }
181}