ironfix_tagvalue/
encoder.rs1use crate::checksum::{calculate_checksum, format_checksum};
13use bytes::{BufMut, BytesMut};
14
15pub const SOH: u8 = 0x01;
17
18#[derive(Debug)]
23pub struct Encoder {
24 body: BytesMut,
26 begin_string: &'static str,
28}
29
30impl Encoder {
31 #[must_use]
36 pub fn new(begin_string: &'static str) -> Self {
37 Self {
38 body: BytesMut::with_capacity(256),
39 begin_string,
40 }
41 }
42
43 #[must_use]
49 pub fn with_capacity(begin_string: &'static str, capacity: usize) -> Self {
50 Self {
51 body: BytesMut::with_capacity(capacity),
52 begin_string,
53 }
54 }
55
56 #[inline]
62 pub fn put_str(&mut self, tag: u32, value: &str) {
63 self.put_raw(tag, value.as_bytes());
64 }
65
66 #[inline]
72 pub fn put_int(&mut self, tag: u32, value: i64) {
73 let mut buf = itoa::Buffer::new();
74 let s = buf.format(value);
75 self.put_raw(tag, s.as_bytes());
76 }
77
78 #[inline]
84 pub fn put_uint(&mut self, tag: u32, value: u64) {
85 let mut buf = itoa::Buffer::new();
86 let s = buf.format(value);
87 self.put_raw(tag, s.as_bytes());
88 }
89
90 #[inline]
96 pub fn put_bool(&mut self, tag: u32, value: bool) {
97 self.put_raw(tag, if value { b"Y" } else { b"N" });
98 }
99
100 #[inline]
106 pub fn put_char(&mut self, tag: u32, value: char) {
107 let mut buf = [0u8; 4];
108 let s = value.encode_utf8(&mut buf);
109 self.put_raw(tag, s.as_bytes());
110 }
111
112 #[inline]
118 pub fn put_raw(&mut self, tag: u32, value: &[u8]) {
119 let mut tag_buf = itoa::Buffer::new();
120 let tag_str = tag_buf.format(tag);
121
122 self.body.put_slice(tag_str.as_bytes());
123 self.body.put_u8(b'=');
124 self.body.put_slice(value);
125 self.body.put_u8(SOH);
126 }
127
128 #[must_use]
137 pub fn finish(self) -> BytesMut {
138 let body_len = self.body.len();
139
140 let mut header = BytesMut::with_capacity(32);
142 header.put_slice(b"8=");
143 header.put_slice(self.begin_string.as_bytes());
144 header.put_u8(SOH);
145 header.put_slice(b"9=");
146
147 let mut len_buf = itoa::Buffer::new();
148 let len_str = len_buf.format(body_len);
149 header.put_slice(len_str.as_bytes());
150 header.put_u8(SOH);
151
152 let mut message = BytesMut::with_capacity(header.len() + body_len + 8);
154 message.put_slice(&header);
155 message.put_slice(&self.body);
156
157 let checksum = calculate_checksum(&message);
159 let checksum_bytes = format_checksum(checksum);
160
161 message.put_slice(b"10=");
162 message.put_slice(&checksum_bytes);
163 message.put_u8(SOH);
164
165 message
166 }
167
168 #[inline]
170 #[must_use]
171 pub fn body_len(&self) -> usize {
172 self.body.len()
173 }
174
175 #[inline]
177 pub fn clear(&mut self) {
178 self.body.clear();
179 }
180}
181
182impl Default for Encoder {
183 fn default() -> Self {
184 Self::new("FIX.4.4")
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_encoder_basic() {
194 let mut encoder = Encoder::new("FIX.4.4");
195 encoder.put_str(35, "0");
196
197 let message = encoder.finish();
198 let msg_str = String::from_utf8_lossy(&message);
199
200 assert!(msg_str.starts_with("8=FIX.4.4\x01"));
201 assert!(msg_str.contains("35=0\x01"));
202 assert!(msg_str.contains("10="));
203 }
204
205 #[test]
206 fn test_encoder_multiple_fields() {
207 let mut encoder = Encoder::new("FIX.4.4");
208 encoder.put_str(35, "D");
209 encoder.put_str(49, "SENDER");
210 encoder.put_str(56, "TARGET");
211 encoder.put_uint(34, 1);
212
213 let message = encoder.finish();
214 let msg_str = String::from_utf8_lossy(&message);
215
216 assert!(msg_str.contains("35=D\x01"));
217 assert!(msg_str.contains("49=SENDER\x01"));
218 assert!(msg_str.contains("56=TARGET\x01"));
219 assert!(msg_str.contains("34=1\x01"));
220 }
221
222 #[test]
223 fn test_encoder_bool() {
224 let mut encoder = Encoder::new("FIX.4.4");
225 encoder.put_bool(141, true);
226 encoder.put_bool(142, false);
227
228 let message = encoder.finish();
229 let msg_str = String::from_utf8_lossy(&message);
230
231 assert!(msg_str.contains("141=Y\x01"));
232 assert!(msg_str.contains("142=N\x01"));
233 }
234
235 #[test]
236 fn test_encoder_char() {
237 let mut encoder = Encoder::new("FIX.4.4");
238 encoder.put_char(54, '1');
239
240 let message = encoder.finish();
241 let msg_str = String::from_utf8_lossy(&message);
242
243 assert!(msg_str.contains("54=1\x01"));
244 }
245
246 #[test]
247 fn test_encoder_clear() {
248 let mut encoder = Encoder::new("FIX.4.4");
249 encoder.put_str(35, "0");
250 assert!(encoder.body_len() > 0);
251
252 encoder.clear();
253 assert_eq!(encoder.body_len(), 0);
254 }
255}