hotfix_dictionary/
lib.rs

1//! Access to FIX Dictionary reference and message specifications.
2
3mod builder;
4mod component;
5mod datatype;
6mod dictionary;
7mod error;
8mod field;
9mod layout;
10mod message_definition;
11mod quickfix;
12mod string;
13
14use component::{Component, ComponentData};
15use datatype::DatatypeData;
16pub use datatype::{Datatype, FixDatatype};
17pub use dictionary::Dictionary;
18pub use error::ParseError;
19pub use field::{Field, FieldEnum, FieldLocation, IsFieldDefinition};
20use field::{FieldData, FieldEnumData};
21use fnv::FnvHashMap;
22pub use layout::{LayoutItem, LayoutItemKind, display_layout_item};
23use layout::{LayoutItemData, LayoutItemKindData, LayoutItems};
24use std::sync::Arc;
25
26/// A mapping from FIX version strings to [`Dictionary`] values.
27pub type Dictionaries = FnvHashMap<String, Arc<Dictionary>>;
28
29/// Type alias for FIX tags: 32-bit unsigned integers, strictly positive.
30pub type TagU32 = std::num::NonZeroU32;
31
32#[cfg(test)]
33mod test {
34    use super::*;
35    #[cfg(feature = "fix44")]
36    use crate::layout::LayoutItemKind;
37    use std::collections::HashSet;
38
39    #[test]
40    #[cfg(feature = "fix44")]
41    fn fix44_quickfix_is_ok() {
42        let dict = Dictionary::fix44();
43        let msg_heartbeat = dict.message_by_name("Heartbeat").unwrap();
44        assert_eq!(msg_heartbeat.msg_type(), "0");
45        assert_eq!(msg_heartbeat.name(), "Heartbeat".to_string());
46        assert!(msg_heartbeat.layout().any(|c| {
47            if let LayoutItemKind::Field(f) = c.kind() {
48                f.name() == "TestReqID"
49            } else {
50                false
51            }
52        }));
53    }
54
55    #[test]
56    fn all_datatypes_are_used_at_least_once() {
57        for dict in Dictionary::common_dictionaries().iter() {
58            let datatypes_count = dict.datatypes().len();
59            let mut datatypes = HashSet::new();
60            for field in dict.fields() {
61                datatypes.insert(field.data_type().name().to_string());
62            }
63            assert_eq!(datatypes_count, datatypes.len());
64        }
65    }
66
67    #[test]
68    fn at_least_one_datatype() {
69        for dict in Dictionary::common_dictionaries().iter() {
70            assert!(!dict.datatypes().is_empty());
71        }
72    }
73
74    #[test]
75    fn std_header_and_trailer_always_present() {
76        for dict in Dictionary::common_dictionaries().iter() {
77            let std_header = dict.component_by_name("StandardHeader");
78            let std_trailer = dict.component_by_name("StandardTrailer");
79            assert!(std_header.is_some() && std_trailer.is_some());
80        }
81    }
82
83    #[test]
84    #[cfg(feature = "fix44")]
85    fn fix44_field_28_has_three_variants() {
86        let dict = Dictionary::fix44();
87        let field_28 = dict.field_by_tag(28).unwrap();
88        assert_eq!(field_28.name(), "IOITransType");
89        assert_eq!(field_28.enums().unwrap().count(), 3);
90    }
91
92    #[test]
93    #[cfg(feature = "fix44")]
94    fn fix44_field_36_has_no_variants() {
95        let dict = Dictionary::fix44();
96        let field_36 = dict.field_by_tag(36).unwrap();
97        assert_eq!(field_36.name(), "NewSeqNo");
98        assert!(field_36.enums().is_none());
99    }
100
101    #[test]
102    #[cfg(feature = "fix44")]
103    fn fix44_field_167_has_eucorp_variant() {
104        let dict = Dictionary::fix44();
105        let field_167 = dict.field_by_tag(167).unwrap();
106        assert_eq!(field_167.name(), "SecurityType");
107        assert!(field_167.enums().unwrap().any(|e| e.value() == "EUCORP"));
108    }
109
110    const INVALID_QUICKFIX_SPECS: &[&str] = &[
111        include_str!("test_data/quickfix_specs/empty_file.xml"),
112        include_str!("test_data/quickfix_specs/missing_components.xml"),
113        include_str!("test_data/quickfix_specs/missing_fields.xml"),
114        include_str!("test_data/quickfix_specs/missing_header.xml"),
115        include_str!("test_data/quickfix_specs/missing_messages.xml"),
116        include_str!("test_data/quickfix_specs/missing_trailer.xml"),
117        include_str!("test_data/quickfix_specs/root_has_no_type_attr.xml"),
118        include_str!("test_data/quickfix_specs/root_has_no_version_attrs.xml"),
119        include_str!("test_data/quickfix_specs/root_is_not_fix.xml"),
120    ];
121
122    #[test]
123    fn invalid_quickfix_specs() {
124        for spec in INVALID_QUICKFIX_SPECS.iter() {
125            let dict = Dictionary::from_quickfix_spec(spec);
126            assert!(dict.is_err(), "{}", spec);
127        }
128    }
129}