1mod 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
26pub type Dictionaries = FnvHashMap<String, Arc<Dictionary>>;
28
29pub 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}