hotfix_dictionary/
lib.rs

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