trafix_codec/message/mod.rs
1//! Implementation of the message module.
2
3pub mod field;
4
5use bytes::Bytes;
6
7use crate::{
8 decoder, encoder,
9 message::field::{
10 Field,
11 value::{begin_string::BeginString, msg_type::MsgType},
12 },
13};
14
15/// Represents the header section of a FIX message.
16///
17/// The header always contains the protocol [`BeginString`] (tag 8)
18/// and the message type [`MsgType`] (tag 35), and may include
19/// additional session or routing fields.
20#[derive(Debug)]
21pub struct Header {
22 /// The `BeginString` identifying the FIX protocol version.
23 #[allow(dead_code)]
24 pub(crate) begin_string: BeginString,
25
26 /// The `MsgType` indicating the business purpose of the message (message type).
27 #[allow(dead_code)]
28 pub(crate) msg_type: MsgType,
29
30 /// Optional additional header fields.
31 pub(crate) fields: Vec<Field>,
32}
33
34/// Represents the body section of a FIX message.
35///
36/// The body always contains the fields forming the message business content.
37#[derive(Default, Debug)]
38pub struct Body {
39 /// Collection of fields forming this message body.
40 pub(crate) fields: Vec<Field>,
41}
42
43/// Represents a complete owned, structured FIX message composed of a header and body.
44///
45/// The header holds protocol and session metadata, while the body
46/// carries message-specific fields defined by the message type.
47#[derive(Debug)]
48pub struct Message {
49 /// The message header containing version, type, and optional routing fields.
50 header: Header,
51
52 /// The message body forming the message business content.
53 body: Body,
54}
55
56impl Message {
57 /// Creates a new [`MessageBuilder`] initialized with the required
58 /// [`BeginString`] and [`MsgType`] header fields.
59 ///
60 /// Example usage:
61 /// ```
62 /// use trafix_codec::message::{
63 /// Message,
64 /// field::{
65 /// Field,
66 /// value::{begin_string::BeginString, msg_type::MsgType},
67 /// },
68 /// };
69 ///
70 /// let builder = Message::builder(BeginString::FIX44, MsgType::Logon);
71 /// ```
72 #[must_use]
73 pub fn builder(begin_string: BeginString, msg_type: MsgType) -> MessageBuilder<false> {
74 let header = Header {
75 begin_string,
76 msg_type,
77 fields: Vec::new(),
78 };
79
80 MessageBuilder {
81 inner: Message {
82 header,
83 body: Body::default(),
84 },
85 }
86 }
87
88 /// Encodes this message into a valid, final wire-format `Bytes` buffer, auto populating fields
89 /// `BodyLength` and `Checksum`.
90 #[must_use]
91 pub fn encode(self) -> Bytes {
92 encoder::encode(&self.header, &self.body)
93 }
94
95 /// Decodes a [`Message`] from given bytes. See [`decode`] for more information.
96 ///
97 /// # Errors
98 ///
99 /// Returns [`Error`] on invalid input.
100 pub fn decode(input: impl AsRef<[u8]>) -> Result<Self, decoder::Error> {
101 decoder::decode(input)
102 }
103}
104
105/// Generic builder for constructing [`Message`] instances.
106///
107/// The builder supports chaining calls to add header or body fields.
108/// Type-state (`IS_INIT`) tracks whether at least one body field was added,
109/// allowing [`MessageBuilder::build()`] to only be available after initialization.
110pub struct MessageBuilder<const IS_INIT: bool> {
111 /// The message being constructed.
112 inner: Message,
113}
114
115impl<const IS_INIT: bool> MessageBuilder<IS_INIT> {
116 /// Adds a field to the message header.
117 #[must_use]
118 pub fn with_header(mut self, field: Field) -> Self {
119 self.inner.header.fields.push(field);
120
121 self
122 }
123
124 /// Adds a field to the message body.
125 ///
126 /// Each call appends a new [`Field`] in order of insertion.
127 /// Once at least one field has been added, the builder transitions
128 /// to an initialized state, enabling [`build`](Self::build).
129 #[must_use]
130 pub fn with_field(mut self, field: Field) -> MessageBuilder<true> {
131 self.inner.body.fields.push(field);
132
133 MessageBuilder { inner: self.inner }
134 }
135}
136
137impl MessageBuilder<true> {
138 /// Finalizes and returns the fully constructed [`Message`].
139 ///
140 /// Example usage:
141 /// ```
142 /// use trafix_codec::message::{
143 /// Message,
144 /// field::{
145 /// Field,
146 /// value::{begin_string::BeginString, msg_type::MsgType},
147 /// },
148 /// };
149 ///
150 /// let msg = Message::builder(BeginString::FIX44, MsgType::Logout)
151 /// .with_field(Field::Custom { tag: 58, value: b"Bye".to_vec() })
152 /// .build();
153 /// ```
154 #[must_use]
155 pub fn build(self) -> Message {
156 self.inner
157 }
158}
159
160#[cfg(test)]
161mod test {
162 use crate::message::{
163 Message,
164 field::{
165 Field,
166 value::{begin_string::BeginString, msg_type::MsgType},
167 },
168 };
169
170 #[test]
171 fn basic_builder() {
172 let builder = Message::builder(BeginString::FIX44, MsgType::Logon);
173
174 // header
175 assert_eq!(builder.inner.header.begin_string, BeginString::FIX44);
176 assert_eq!(builder.inner.header.msg_type, MsgType::Logon);
177
178 // body
179 assert_eq!(builder.inner.body.fields.len(), 0);
180 }
181
182 #[test]
183 fn simple_message() {
184 let builder = Message::builder(BeginString::FIX44, MsgType::Logout);
185
186 let custom_header_field = Field::Custom {
187 tag: 22,
188 value: b"custom_header_field".to_vec(),
189 };
190
191 let custom_body_field1 = Field::Custom {
192 tag: 40000,
193 value: b"custom_body_field1".to_vec(),
194 };
195
196 let custom_body_field2 = Field::Custom {
197 tag: 50000,
198 value: b"custom_body_field2".to_vec(),
199 };
200
201 let msg = builder
202 .with_header(custom_header_field.clone())
203 .with_field(custom_body_field1.clone())
204 .with_field(custom_body_field2.clone())
205 .build();
206
207 // auto-header
208 assert_eq!(msg.header.begin_string, BeginString::FIX44);
209 assert_eq!(msg.header.msg_type, MsgType::Logout);
210
211 // custom header
212 assert_eq!(msg.header.fields.clone().len(), 1);
213 assert_eq!(msg.header.fields[0], custom_header_field);
214
215 // body
216 assert_eq!(msg.body.fields.len(), 2);
217 assert_eq!(msg.body.fields[0], custom_body_field1);
218 assert_eq!(msg.body.fields[1], custom_body_field2);
219 }
220}