1use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc};
4
5use crate::error::ConversionError;
6use crate::message::Tag;
7
8pub trait FixEncode {
10 fn encode(&self, buf: &mut Vec<u8>);
11}
12
13pub trait FixDecode: Sized {
15 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError>;
16}
17
18fn invalid(tag: Tag, bytes: &[u8]) -> ConversionError {
19 ConversionError::InvalidValue { tag, value: String::from_utf8_lossy(bytes).into_owned() }
20}
21
22impl FixEncode for &str {
23 fn encode(&self, buf: &mut Vec<u8>) {
24 buf.extend_from_slice(self.as_bytes());
25 }
26}
27
28impl FixEncode for String {
29 fn encode(&self, buf: &mut Vec<u8>) {
30 buf.extend_from_slice(self.as_bytes());
31 }
32}
33
34impl FixEncode for &[u8] {
35 fn encode(&self, buf: &mut Vec<u8>) {
36 buf.extend_from_slice(self);
37 }
38}
39
40impl FixEncode for Vec<u8> {
41 fn encode(&self, buf: &mut Vec<u8>) {
42 buf.extend_from_slice(self);
43 }
44}
45
46impl FixEncode for char {
47 fn encode(&self, buf: &mut Vec<u8>) {
48 let mut b = [0u8; 4];
49 buf.extend_from_slice(self.encode_utf8(&mut b).as_bytes());
50 }
51}
52
53impl FixEncode for bool {
54 fn encode(&self, buf: &mut Vec<u8>) {
55 buf.push(if *self { b'Y' } else { b'N' });
56 }
57}
58
59macro_rules! impl_int {
60 ($($t:ty),*) => {$(
61 impl FixEncode for $t {
62 fn encode(&self, buf: &mut Vec<u8>) {
63 buf.extend_from_slice(itoa_buf(*self as i64).as_bytes());
64 }
65 }
66 impl FixDecode for $t {
67 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
68 let s = std::str::from_utf8(bytes).map_err(|_| invalid(tag, bytes))?;
69 s.parse::<$t>().map_err(|_| invalid(tag, bytes))
70 }
71 }
72 )*};
73}
74impl_int!(i32, i64, u32, u64, usize);
75
76fn itoa_buf(v: i64) -> String {
77 v.to_string()
78}
79
80impl FixEncode for f64 {
81 fn encode(&self, buf: &mut Vec<u8>) {
82 let s = format!("{}", self);
84 debug_assert!(!s.contains('e') && !s.contains('E'));
85 buf.extend_from_slice(s.as_bytes());
86 }
87}
88
89impl FixDecode for f64 {
90 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
91 let s = std::str::from_utf8(bytes).map_err(|_| invalid(tag, bytes))?;
92 if s.bytes().any(|b| !matches!(b, b'0'..=b'9' | b'.' | b'-')) {
95 return Err(invalid(tag, bytes));
96 }
97 s.parse::<f64>().map_err(|_| invalid(tag, bytes))
98 }
99}
100
101#[cfg(feature = "decimal")]
104impl FixEncode for rust_decimal::Decimal {
105 fn encode(&self, buf: &mut Vec<u8>) {
106 use std::fmt::Write;
108 let mut s = String::new();
109 let _ = write!(s, "{self}");
110 buf.extend_from_slice(s.as_bytes());
111 }
112}
113
114#[cfg(feature = "decimal")]
115impl FixDecode for rust_decimal::Decimal {
116 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
117 let s = std::str::from_utf8(bytes).map_err(|_| invalid(tag, bytes))?;
118 if s.bytes().any(|b| !matches!(b, b'0'..=b'9' | b'.' | b'-')) {
120 return Err(invalid(tag, bytes));
121 }
122 s.parse::<rust_decimal::Decimal>().map_err(|_| invalid(tag, bytes))
123 }
124}
125
126impl FixDecode for String {
127 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
128 String::from_utf8(bytes.to_vec()).map_err(|_| invalid(tag, bytes))
129 }
130}
131
132impl FixDecode for Vec<u8> {
133 fn decode(_tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
134 Ok(bytes.to_vec())
135 }
136}
137
138impl FixDecode for bool {
139 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
140 match bytes {
141 b"Y" => Ok(true),
142 b"N" => Ok(false),
143 _ => Err(invalid(tag, bytes)),
144 }
145 }
146}
147
148impl FixDecode for char {
149 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
150 let s = std::str::from_utf8(bytes).map_err(|_| invalid(tag, bytes))?;
151 let mut chars = s.chars();
152 match (chars.next(), chars.next()) {
153 (Some(c), None) => Ok(c),
154 _ => Err(invalid(tag, bytes)),
155 }
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
161pub enum TimestampPrecision {
162 Seconds,
163 #[default]
164 Millis,
165 Micros,
166 Nanos,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct UtcTimestamp {
172 pub time: DateTime<Utc>,
173 pub precision: TimestampPrecision,
174}
175
176impl UtcTimestamp {
177 pub fn now() -> Self {
178 Self { time: Utc::now(), precision: TimestampPrecision::default() }
179 }
180
181 pub fn new(time: DateTime<Utc>, precision: TimestampPrecision) -> Self {
182 Self { time, precision }
183 }
184}
185
186impl FixEncode for UtcTimestamp {
187 fn encode(&self, buf: &mut Vec<u8>) {
188 let fmt = match self.precision {
189 TimestampPrecision::Seconds => "%Y%m%d-%H:%M:%S",
190 TimestampPrecision::Millis => "%Y%m%d-%H:%M:%S%.3f",
191 TimestampPrecision::Micros => "%Y%m%d-%H:%M:%S%.6f",
192 TimestampPrecision::Nanos => "%Y%m%d-%H:%M:%S%.9f",
193 };
194 buf.extend_from_slice(self.time.format(fmt).to_string().as_bytes());
195 }
196}
197
198impl FixDecode for UtcTimestamp {
199 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
200 let s = std::str::from_utf8(bytes).map_err(|_| invalid(tag, bytes))?;
201 let (fmt, precision) = match s.len() {
202 17 => ("%Y%m%d-%H:%M:%S", TimestampPrecision::Seconds),
203 21 => ("%Y%m%d-%H:%M:%S%.3f", TimestampPrecision::Millis),
204 24 => ("%Y%m%d-%H:%M:%S%.6f", TimestampPrecision::Micros),
205 27 => ("%Y%m%d-%H:%M:%S%.9f", TimestampPrecision::Nanos),
206 _ => return Err(invalid(tag, bytes)),
207 };
208 let naive = NaiveDateTime::parse_from_str(s, fmt).map_err(|_| invalid(tag, bytes))?;
209 Ok(Self { time: Utc.from_utc_datetime(&naive), precision })
210 }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub struct FixDate(pub NaiveDate);
216
217impl FixEncode for FixDate {
218 fn encode(&self, buf: &mut Vec<u8>) {
219 buf.extend_from_slice(self.0.format("%Y%m%d").to_string().as_bytes());
220 }
221}
222
223impl FixDecode for FixDate {
224 fn decode(tag: Tag, bytes: &[u8]) -> Result<Self, ConversionError> {
225 let s = std::str::from_utf8(bytes).map_err(|_| invalid(tag, bytes))?;
226 NaiveDate::parse_from_str(s, "%Y%m%d").map(FixDate).map_err(|_| invalid(tag, bytes))
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 fn enc(v: impl FixEncode) -> Vec<u8> {
235 let mut buf = Vec::new();
236 v.encode(&mut buf);
237 buf
238 }
239
240 #[test]
241 fn roundtrip_scalars() {
242 assert_eq!(enc(42i64), b"42");
243 assert_eq!(enc(-7i32), b"-7");
244 assert_eq!(enc(true), b"Y");
245 assert_eq!(enc(false), b"N");
246 assert_eq!(enc(1.5f64), b"1.5");
247 assert_eq!(enc("ABC"), b"ABC");
248 assert_eq!(i64::decode(1, b"42").unwrap(), 42);
249 assert!(i64::decode(1, b"4x2").is_err());
250 assert!(bool::decode(1, b"X").is_err());
251 assert!(f64::decode(1, b"1e5").is_err());
252 assert_eq!(f64::decode(1, b"-1.25").unwrap(), -1.25);
253 }
254
255 #[cfg(feature = "decimal")]
256 #[test]
257 fn decimal_exactness_and_scale() {
258 use rust_decimal::Decimal;
259 let a: Decimal = "0.1".parse().unwrap();
261 let b: Decimal = "0.2".parse().unwrap();
262 assert_eq!(a + b, "0.3".parse::<Decimal>().unwrap());
263
264 let d = Decimal::decode(44, b"1.50").unwrap();
266 assert_eq!(enc(d), b"1.50");
267 let d = Decimal::decode(44, b"123456789.012345678").unwrap();
269 assert_eq!(enc(d), b"123456789.012345678");
270 assert_eq!(enc(Decimal::decode(38, b"100").unwrap()), b"100");
272 assert_eq!(enc(Decimal::decode(44, b"-1.25").unwrap()), b"-1.25");
273 assert!(Decimal::decode(44, b"1e5").is_err());
275 assert!(Decimal::decode(44, b"+1.5").is_err());
276 }
277
278 #[test]
279 fn roundtrip_timestamp() {
280 let ts = UtcTimestamp::decode(52, b"20140515-19:49:56.659").unwrap();
281 assert_eq!(ts.precision, TimestampPrecision::Millis);
282 assert_eq!(enc(ts), b"20140515-19:49:56.659");
283
284 let ts = UtcTimestamp::decode(52, b"20140515-19:49:56").unwrap();
285 assert_eq!(ts.precision, TimestampPrecision::Seconds);
286 assert_eq!(enc(ts), b"20140515-19:49:56");
287
288 assert!(UtcTimestamp::decode(52, b"2014-05-15").is_err());
289 }
290}