use crate::error::EncodingResult;
use crate::field_map::{Field, FieldMap};
use crate::message::Config;
use hotfix_dictionary::TagU32;
use std::io::Write;
pub trait Encode {
fn write(
&self,
config: &Config,
buffer: &mut Vec<u8>,
pre_fields: &[TagU32],
) -> EncodingResult<()>;
}
impl Encode for FieldMap {
fn write(
&self,
config: &Config,
buffer: &mut Vec<u8>,
pre_fields: &[TagU32],
) -> EncodingResult<()> {
let mut write_field = |field: &Field| {
let formatted_tag = format!("{}=", field.tag.get());
buffer.write_all(formatted_tag.as_bytes())?;
buffer.write_all(&field.data)?;
buffer.push(config.separator);
if let Some(groups) = self.groups.get(&field.tag) {
for group in groups {
group.get_fields().write(config, buffer, &[])?;
}
}
Ok::<(), crate::error::EncodingError>(())
};
for pre_field_tag in pre_fields {
if let Some(field) = self.fields.get(pre_field_tag) {
write_field(field)?;
}
}
for (tag, field) in &self.fields {
if !pre_fields.contains(tag) {
write_field(field)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::field_map::Field;
use crate::field_types::{Date, Time, Timestamp};
use crate::fix44;
use crate::message::{Config, Message};
use crate::parts::RepeatingGroup;
use crate::{MessageBuilder, Part};
use hotfix_dictionary::{Dictionary, IsFieldDefinition};
#[test]
fn encode_simple_message() -> anyhow::Result<()> {
let mut msg = Message::new("FIX.4.4", "D");
msg.set(fix44::MSG_SEQ_NUM, 1);
msg.set(fix44::SENDER_COMP_ID, "CLIENT_A");
msg.set(fix44::TARGET_COMP_ID, "BROKER_B");
msg.set(fix44::SENDING_TIME, Timestamp::utc_now());
msg.set(fix44::CL_ORD_ID, "ORDER_0001");
msg.set(fix44::SYMBOL, "AAPL");
msg.set(fix44::SIDE, fix44::Side::Buy);
msg.set(fix44::TRANSACT_TIME, Timestamp::utc_now());
msg.set(fix44::ORD_TYPE, fix44::OrdType::Limit);
msg.set(fix44::PRICE, 150);
msg.set(fix44::ORDER_QTY, 60);
let config = Config::with_separator(b'|');
let raw_message = msg.encode(&config)?;
let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
let parsed_message = builder.build(&raw_message).into_message().unwrap();
let symbol: &str = parsed_message.get(fix44::SYMBOL)?;
assert_eq!(symbol, "AAPL");
let qty: u32 = parsed_message.get(fix44::ORDER_QTY).unwrap();
assert_eq!(qty, 60);
let body_length: usize = parsed_message.header().get(fix44::BODY_LENGTH).unwrap();
assert_eq!(body_length, 129);
Ok(())
}
#[test]
fn encode_message_with_repeating_group() -> anyhow::Result<()> {
let sending_time = Timestamp::new(
Date::new(2023, 11, 7).unwrap(),
Time::from_hmsm(11, 0, 0, 0).unwrap(),
);
let mut msg = Message::new("FIX.4.4", "8");
msg.set(fix44::MSG_SEQ_NUM, 1);
msg.set(fix44::SENDER_COMP_ID, "BROKER_B");
msg.set(fix44::TARGET_COMP_ID, "CLIENT_A");
msg.set(fix44::SENDING_TIME, sending_time);
msg.set(fix44::CL_ORD_ID, "ORDER_0001");
msg.set(fix44::EXEC_ID, "Exec12345");
msg.set(fix44::ORD_STATUS, "0");
msg.set(fix44::SYMBOL, "AAPL");
msg.set(fix44::SIDE, fix44::Side::Buy);
msg.set(fix44::ORDER_QTY, 1000);
msg.set(fix44::LAST_QTY, 200);
msg.set(fix44::LAST_PX, 150.0);
msg.set(fix44::LEAVES_QTY, 800);
msg.set(fix44::CUM_QTY, 200);
msg.set(fix44::AVG_PX, 150.0);
msg.set(fix44::NO_PARTY_I_DS, 2);
let mut party_1 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
party_1.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_A".to_vec()));
party_1.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
party_1.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"1".to_vec()));
party_1.store_field(Field::new(fix44::NO_PARTY_SUB_I_DS.tag(), b"2".to_vec()));
let mut subparty_1 = RepeatingGroup::new(fix44::NO_PARTY_SUB_I_DS, fix44::PARTY_SUB_ID);
subparty_1.store_field(Field::new(
fix44::PARTY_SUB_ID.tag(),
b"SUBPARTY_A_1".to_vec(),
));
subparty_1.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"1".to_vec()));
let mut subparty_2 = RepeatingGroup::new(fix44::NO_PARTY_SUB_I_DS, fix44::PARTY_SUB_ID);
subparty_2.store_field(Field::new(
fix44::PARTY_SUB_ID.tag(),
b"SUBPARTY_A_2".to_vec(),
));
subparty_2.store_field(Field::new(fix44::PARTY_SUB_ID_TYPE.tag(), b"2".to_vec()));
party_1.set_groups(vec![subparty_1, subparty_2])?;
let mut party_2 = RepeatingGroup::new(fix44::NO_PARTY_I_DS, fix44::PARTY_ID);
party_2.store_field(Field::new(fix44::PARTY_ID.tag(), b"PARTY_B".to_vec()));
party_2.store_field(Field::new(fix44::PARTY_ID_SOURCE.tag(), b"D".to_vec()));
party_2.store_field(Field::new(fix44::PARTY_ROLE.tag(), b"2".to_vec()));
msg.body.set_groups(vec![party_1, party_2])?;
let config = Config::with_separator(b'|');
let raw_message = msg.encode(&config)?;
let builder = MessageBuilder::new(Dictionary::fix44(), config)?;
let parsed_message = builder.build(&raw_message).into_message().unwrap();
let party_a = parsed_message
.get_group(fix44::NO_PARTY_I_DS, 0)
.expect("group to be found");
let party_a_0 = party_a
.get_group(fix44::NO_PARTY_SUB_I_DS.tag(), 0)
.expect("group to be found");
let sub_id_0: &str = party_a_0.get(fix44::PARTY_SUB_ID)?;
assert_eq!(sub_id_0, "SUBPARTY_A_1");
let party_b = parsed_message
.get_group(fix44::NO_PARTY_I_DS, 1)
.expect("group to be found");
let party_b_id: &str = party_b.get(fix44::PARTY_ID)?;
assert_eq!(party_b_id, "PARTY_B");
let party_b_role: &str = party_b.get(fix44::PARTY_ROLE)?;
assert_eq!(party_b_role, "2");
let checksum: &str = parsed_message.trailer().get(fix44::CHECK_SUM)?;
assert_eq!(checksum, "036");
let qty: usize = parsed_message.header().get(fix44::BODY_LENGTH).unwrap();
assert_eq!(qty, 253);
Ok(())
}
}