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::Part;
56 use crate::field_map::Field;
57 use crate::field_types::{Date, Time, Timestamp};
58 use crate::fix44;
59 use crate::message::{Config, Message};
60 use crate::parts::RepeatingGroup;
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 dict = Dictionary::fix44();
82 let parsed_message = Message::from_bytes(&config, &dict, &raw_message)
83 .into_message()
84 .unwrap();
85
86 let symbol: &str = parsed_message.get(fix44::SYMBOL)?;
87 assert_eq!(symbol, "AAPL");
88
89 let qty: u32 = parsed_message.get(fix44::ORDER_QTY).unwrap();
90 assert_eq!(qty, 60);
91
92 let body_length: usize = parsed_message.header().get(fix44::BODY_LENGTH).unwrap();
93 assert_eq!(body_length, 129);
94
95 Ok(())
96 }
97
98 #[test]
99 fn encode_message_with_repeating_group() -> anyhow::Result<()> {
100 let sending_time = Timestamp::new(
101 Date::new(2023, 11, 7).unwrap(),
102 Time::from_hmsm(11, 0, 0, 0).unwrap(),
103 );
104 let mut msg = Message::new("FIX.4.4", "8");
105 msg.set(fix44::MSG_SEQ_NUM, 1);
106 msg.set(fix44::SENDER_COMP_ID, "BROKER_B");
107 msg.set(fix44::TARGET_COMP_ID, "CLIENT_A");
108 msg.set(fix44::SENDING_TIME, sending_time);
109 msg.set(fix44::CL_ORD_ID, "ORDER_0001");
110 msg.set(fix44::EXEC_ID, "Exec12345");
111 msg.set(fix44::ORD_STATUS, "0");
112 msg.set(fix44::SYMBOL, "AAPL");
113 msg.set(fix44::SIDE, fix44::Side::Buy);
114 msg.set(fix44::ORDER_QTY, 1000);
115 msg.set(fix44::LAST_QTY, 200);
116 msg.set(fix44::LAST_PX, 150.0);
117 msg.set(fix44::LEAVES_QTY, 800);
118 msg.set(fix44::CUM_QTY, 200);
119 msg.set(fix44::AVG_PX, 150.0);
120
121 msg.set(fix44::NO_PARTY_I_DS, 2);
122
123 let mut party_1 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
124 party_1.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_A".to_vec()));
125 party_1.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
126 party_1.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"1".to_vec()));
127 party_1.store_field(Field::new(fix44::NO_PARTY_SUB_I_DS.tag(), b"2".to_vec()));
128
129 let mut subparty_1 = RepeatingGroup::new(fix44::NO_PARTY_SUB_I_DS, fix44::PARTY_SUB_ID);
130 subparty_1.store_field(Field::new(
131 fix44::PARTY_SUB_ID.tag(),
132 b"SUBPARTY_A_1".to_vec(),
133 ));
134 subparty_1.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"1".to_vec()));
135
136 let mut subparty_2 = RepeatingGroup::new(fix44::NO_PARTY_SUB_I_DS, fix44::PARTY_SUB_ID);
137 subparty_2.store_field(Field::new(
138 fix44::PARTY_SUB_ID.tag(),
139 b"SUBPARTY_A_2".to_vec(),
140 ));
141 subparty_2.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"2".to_vec()));
142
143 party_1.set_groups(vec![subparty_1, subparty_2]);
144
145 let mut party_2 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
146 party_2.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_B".to_vec()));
147 party_2.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
148 party_2.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"2".to_vec()));
149
150 msg.body.set_groups(vec![party_1, party_2]);
151 let config = Config { separator: b'|' };
152 let raw_message = msg.encode(&config)?;
153
154 let dict = Dictionary::fix44();
155 let parsed_message = Message::from_bytes(&config, &dict, &raw_message)
156 .into_message()
157 .unwrap();
158
159 let party_a = parsed_message
160 .get_group(fix44::NO_PARTY_I_DS, 0)
161 .expect("group to be found");
162 let party_a_0 = party_a
163 .get_group(fix44::NO_PARTY_SUB_I_DS.tag(), 0)
164 .expect("group to be found");
165 let sub_id_0: &str = party_a_0.get(fix44::PARTY_SUB_ID)?;
166 assert_eq!(sub_id_0, "SUBPARTY_A_1");
167
168 let party_b = parsed_message
169 .get_group(fix44::NO_PARTY_I_DS, 1)
170 .expect("group to be found");
171 let party_b_id: &str = party_b.get(fix44::PARTY_ID)?;
172 assert_eq!(party_b_id, "PARTY_B");
173
174 let party_b_role: &str = party_b.get(fix44::PARTY_ROLE)?;
175 assert_eq!(party_b_role, "2");
176
177 let checksum: &str = parsed_message.trailer().get(fix44::CHECK_SUM)?;
178 assert_eq!(checksum, "036");
179
180 let qty: usize = parsed_message.header().get(fix44::BODY_LENGTH).unwrap();
181 assert_eq!(qty, 253);
182
183 Ok(())
184 }
185}