hotfix_encoding/field_types.rs
1//! A wide collection of [`FieldType`](crate::FieldType) implementors.
2//!
3//! | FIX datatype | Suggested [`FieldType`] implementors |
4//! |----------------------------|------------------------------------------------------------------------------------|
5//! | `int` | [`u32`], [`i32`], [`u64`], [`i64`], [`u128`], [`i128`]. |
6//! | `Length` | [`usize`]. |
7//! | `NumInGroup` | [`usize`]. |
8//! | `SeqNum` | [`u64`]. |
9//! | `TagNum` | [`TagU32`](crate::TagU32). |
10//! | `DayOfMonth` | [`u32`]. |
11//! | `float`, `Price`, etc. | [`f32`], [`f64`], [`struct@rust_decimal::Decimal`], [`struct@decimal::d128`]. |
12//! | `Boolean` | [`bool`]. |
13//! | `char` | [`u8`] [^1]. |
14//! | `String` | [`Vec<u8>`], `&[u8]`.[^1] |
15//! | `data` | [`Vec<u8>`], `&[u8]` (also [`String`], [`str`] for UTF-8 content). |
16//! | `MultipleCharValue` | [`MultipleChars`] [^1]. |
17//! | `MultipleValueString` | [`MultipleStrings`] [^1]. |
18//! | `Country` | [`Country`]. |
19//! | `Currency` | [`Currency`]. |
20//! | `Exchange` | [`Exchange`]. |
21//! | `month-year` | [`MonthYear`]. |
22//! | `UTCTimeOnly` | [`Time`], [`chrono::NaiveTime`]. |
23//! | `UTCDateOnly` | [`Date`], [`chrono::NaiveDate`]. |
24//! | `UTCTimestamp` | [`Timestamp`], [`chrono::NaiveDateTime`]. |
25//! | `TZTimestamp` | [`TzTimestamp`], [`chrono::DateTime<chrono::FixedOffset>`]. |
26//! | `LocalMktDate` | [`Date`], [`chrono::NaiveDate`]. |
27//! | `TZTimeOnly` | [`TzTime`], |
28//!
29//! The above table provides some useful guidelines that work for the vast
30//! majority of use cases.
31//!
32//! # Quick tour of [`FieldType`]
33//!
34//! ```
35//! use hotfix_encoding::field_access::FieldType;
36//! use hotfix_encoding::field_types::Timestamp;
37//!
38//! let bytes = b"20130422-12:30:00.000";
39//!
40//! // You can use `FieldType::deserialize` to parse data fields.
41//! let timestamp = Timestamp::deserialize(bytes).unwrap();
42//! assert_eq!(timestamp.date().year(), 2013);
43//!
44//! // `FieldType::deserialize_lossy` is like `FieldType::deserialize`, but it's
45//! // allowed to skip some format verification for the sake of speed.
46//! assert!(u32::deserialize(b"invalid integer").is_err());
47//! assert!(u32::deserialize_lossy(b"invalid integer").is_ok());
48//!
49//! let mut buffer: Vec<u8> = vec![];
50//! // Use `FieldType::serialize` to write values to buffers.
51//! 1337u32.serialize(&mut buffer);
52//! assert_eq!(&buffer[..], b"1337" as &[u8]);
53//! ```
54//!
55//! [^1]: With the exception of datatype `data`, FIX mandates a single-byte
56//! encoding (Latin alphabet No. 1 by default), while Rust strings are UTF-8,
57//! which is a multibyte encoding. These are *not* compatible. Watch out!
58
59mod checksum;
60mod date;
61mod monthyear;
62mod multiple_chars;
63mod multiple_strings;
64mod primitives;
65mod tagu32;
66mod time;
67mod timestamp;
68mod tz;
69mod tz_time;
70mod tz_timestamp;
71
72#[cfg(feature = "utils-chrono")]
73mod utils_chrono;
74#[cfg(feature = "utils-decimal")]
75mod utils_decimal;
76#[cfg(feature = "utils-rust-decimal")]
77mod utils_rust_decimal;
78
79pub use checksum::CheckSum;
80pub use date::Date;
81pub use monthyear::MonthYear;
82pub use multiple_chars::MultipleChars;
83pub use multiple_strings::MultipleStrings;
84pub use time::Time;
85pub use timestamp::Timestamp;
86pub use tz::Tz;
87pub use tz_time::TzTime;
88pub use tz_timestamp::TzTimestamp;
89
90use crate::field_access::FieldType;
91
92/// Type alias for ISO 3166-1 alpha-2 strings (two-letter country codes).
93pub type Country = [u8; 2];
94/// Type alias for ISO 4217 alpha codes (three-letter currency codes).
95pub type Currency = [u8; 3];
96/// Type alias for four-letter *Market Identifier Codes* (MICs) as defined by
97/// ISO 10383.
98pub type Exchange = [u8; 4];
99
100pub(crate) const ERR_UTF8: &str = "Invalid byte sequence; expected UTF-8 valid bytes.";
101pub(crate) const ERR_INT_INVALID: &str = "Invalid integer digits.";
102pub(crate) const ERR_TIME: &str = "Invalid time.";
103
104/// Zero-padding for integers; see [`FieldType::SerializeSettings`].
105#[derive(Debug, Copy, Clone, Default)]
106pub struct ZeroPadding(pub usize);
107
108/// Tries to [`FieldType::serialize`] an `item`, then to
109/// [`FieldType::deserialize`] it, and finally checks for equality with the
110/// initial data. [`FieldType::deserialize_lossy`] is then
111/// tested in the same manner.
112pub fn test_utility_verify_serialization_behavior<T>(item: T) -> bool
113where
114 T: for<'a> FieldType<'a> + PartialEq,
115{
116 let serialized = item.to_bytes();
117 let bytes = &serialized[..];
118 let deserialized = T::deserialize(bytes).ok().unwrap();
119 let deserialized_lossy = T::deserialize_lossy(bytes).ok().unwrap();
120 deserialized == item && deserialized_lossy == item
121}
122
123#[cfg(test)]
124mod test {
125 use super::FieldType;
126 use quickcheck_macros::quickcheck;
127
128 #[test]
129 fn serialize_bools() {
130 let mut buffer = Vec::new();
131 assert_eq!(true.serialize(&mut buffer), 1);
132 assert_eq!(false.serialize(&mut buffer), 1);
133 assert_eq!(&buffer[..], b"YN" as &[u8]);
134 }
135
136 #[quickcheck]
137 fn serialize_bytes(data: Vec<Vec<u8>>) -> bool {
138 let mut buffer = Vec::new();
139 for slice in data.iter() {
140 assert_eq!((&slice[..]).serialize(&mut buffer), slice.len());
141 }
142 buffer[..] == data.iter().flatten().copied().collect::<Vec<u8>>()[..]
143 }
144
145 #[quickcheck]
146 fn u32_serialize(n: u32) -> bool {
147 let mut buffer = Vec::new();
148 let s = FieldType::to_string(&n);
149 let bytes = s.as_bytes();
150 let len = n.serialize(&mut buffer);
151 bytes == buffer.as_slice() && len == bytes.len()
152 }
153
154 #[test]
155 fn serialize_country() {
156 let mut buffer = Vec::new();
157 assert_eq!(b"IT".serialize(&mut buffer), 2);
158 assert_eq!(&buffer[..], b"IT" as &[u8]);
159 }
160
161 #[test]
162 fn serialize_currency() {
163 let mut buffer = Vec::new();
164 assert_eq!(b"USD".serialize(&mut buffer), 3);
165 assert_eq!(&buffer[..], b"USD" as &[u8]);
166 }
167}