Skip to main content

ironfix_engine/
outbound.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 14/7/26
5******************************************************************************/
6
7//! Outbound application message builder.
8
9use ironfix_core::message::MsgType;
10
11/// An outbound application message: a MsgType plus ordered body fields.
12///
13/// The engine stamps the standard header (BeginString, BodyLength, MsgType,
14/// SenderCompID, TargetCompID, MsgSeqNum, SendingTime) and the trailer when
15/// the message is sent, so the builder only carries body fields. Fields are
16/// encoded in insertion order.
17#[derive(Debug, Clone)]
18pub struct OutboundMessage {
19    /// Message type (tag 35).
20    msg_type: MsgType,
21    /// Body fields in insertion order.
22    fields: Vec<(u32, Vec<u8>)>,
23}
24
25impl OutboundMessage {
26    /// Creates a new outbound message of the given type.
27    ///
28    /// # Arguments
29    /// * `msg_type` - The message type (tag 35)
30    #[must_use]
31    pub fn new(msg_type: MsgType) -> Self {
32        Self {
33            msg_type,
34            fields: Vec::new(),
35        }
36    }
37
38    /// Returns the message type.
39    #[must_use]
40    pub fn msg_type(&self) -> &MsgType {
41        &self.msg_type
42    }
43
44    /// Appends a field with raw bytes.
45    ///
46    /// # Arguments
47    /// * `tag` - The field tag number
48    /// * `value` - The field value bytes
49    pub fn push_raw(&mut self, tag: u32, value: impl Into<Vec<u8>>) -> &mut Self {
50        self.fields.push((tag, value.into()));
51        self
52    }
53
54    /// Appends a field with a string value.
55    ///
56    /// # Arguments
57    /// * `tag` - The field tag number
58    /// * `value` - The field value
59    pub fn push_str(&mut self, tag: u32, value: &str) -> &mut Self {
60        self.push_raw(tag, value.as_bytes().to_vec())
61    }
62
63    /// Appends a field with an integer value.
64    ///
65    /// # Arguments
66    /// * `tag` - The field tag number
67    /// * `value` - The field value
68    pub fn push_int(&mut self, tag: u32, value: i64) -> &mut Self {
69        self.push_raw(tag, value.to_string().into_bytes())
70    }
71
72    /// Appends a field with an unsigned integer value.
73    ///
74    /// # Arguments
75    /// * `tag` - The field tag number
76    /// * `value` - The field value
77    pub fn push_uint(&mut self, tag: u32, value: u64) -> &mut Self {
78        self.push_raw(tag, value.to_string().into_bytes())
79    }
80
81    /// Appends a field with a single character value.
82    ///
83    /// # Arguments
84    /// * `tag` - The field tag number
85    /// * `value` - The field value
86    pub fn push_char(&mut self, tag: u32, value: char) -> &mut Self {
87        let mut buf = [0u8; 4];
88        let s = value.encode_utf8(&mut buf);
89        self.push_raw(tag, s.as_bytes().to_vec())
90    }
91
92    /// Appends a field with a boolean value (Y/N).
93    ///
94    /// # Arguments
95    /// * `tag` - The field tag number
96    /// * `value` - The field value
97    pub fn push_bool(&mut self, tag: u32, value: bool) -> &mut Self {
98        self.push_raw(tag, if value { b"Y".to_vec() } else { b"N".to_vec() })
99    }
100
101    /// Returns the body fields in insertion order.
102    #[must_use]
103    pub fn fields(&self) -> &[(u32, Vec<u8>)] {
104        &self.fields
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_outbound_message_fields_in_order() {
114        let mut msg = OutboundMessage::new(MsgType::NewOrderSingle);
115        msg.push_str(11, "ORDER-1")
116            .push_char(54, '1')
117            .push_uint(38, 100)
118            .push_int(9999, -5)
119            .push_bool(59, true);
120
121        assert_eq!(msg.msg_type(), &MsgType::NewOrderSingle);
122        let fields = msg.fields();
123        assert_eq!(fields[0], (11, b"ORDER-1".to_vec()));
124        assert_eq!(fields[1], (54, b"1".to_vec()));
125        assert_eq!(fields[2], (38, b"100".to_vec()));
126        assert_eq!(fields[3], (9999, b"-5".to_vec()));
127        assert_eq!(fields[4], (59, b"Y".to_vec()));
128    }
129}