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}