hotfix_dictionary/
dictionary.rs1use crate::{Component, ComponentData, Datatype, DatatypeData, Field, FieldData};
2
3use crate::error::ParseError;
4use crate::message_definition::{MessageData, MessageDefinition};
5use crate::quickfix::QuickFixReader;
6use crate::string::SmartString;
7use fnv::FnvHashMap;
8
9#[derive(Debug, Clone)]
26pub struct Dictionary {
27 pub(crate) version: String,
28
29 pub(crate) data_types_by_name: FnvHashMap<SmartString, DatatypeData>,
30
31 pub(crate) fields_by_tags: FnvHashMap<u32, FieldData>,
32 pub(crate) field_tags_by_name: FnvHashMap<SmartString, u32>,
33
34 pub(crate) components_by_name: FnvHashMap<SmartString, ComponentData>,
35
36 pub(crate) messages_by_msgtype: FnvHashMap<SmartString, MessageData>,
37 pub(crate) message_msgtypes_by_name: FnvHashMap<SmartString, SmartString>,
38}
39
40impl Dictionary {
41 pub fn new<S: ToString>(version: S) -> Self {
43 Dictionary {
44 version: version.to_string(),
45 data_types_by_name: FnvHashMap::default(),
46 fields_by_tags: FnvHashMap::default(),
47 field_tags_by_name: FnvHashMap::default(),
48 components_by_name: FnvHashMap::default(),
49 messages_by_msgtype: FnvHashMap::default(),
50 message_msgtypes_by_name: FnvHashMap::default(),
51 }
52 }
53
54 pub fn from_quickfix_spec(input: &str) -> Result<Self, ParseError> {
57 let xml_document =
58 roxmltree::Document::parse(input).map_err(|_| ParseError::InvalidFormat)?;
59 QuickFixReader::new(&xml_document)
60 }
61
62 pub fn version(&self) -> &str {
72 self.version.as_str()
73 }
74
75 pub fn load_from_file(path: &str) -> Result<Self, ParseError> {
76 let spec = std::fs::read_to_string(path)
77 .unwrap_or_else(|_| panic!("unable to read FIX dictionary file at {path}"));
78 Dictionary::from_quickfix_spec(&spec)
79 }
80
81 #[cfg(feature = "fix40")]
83 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix40")))]
84 pub fn fix40() -> Self {
85 let spec = include_str!("resources/quickfix/FIX-4.0.xml");
86 Dictionary::from_quickfix_spec(spec).unwrap()
87 }
88
89 #[cfg(feature = "fix41")]
91 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix41")))]
92 pub fn fix41() -> Self {
93 let spec = include_str!("resources/quickfix/FIX-4.1.xml");
94 Dictionary::from_quickfix_spec(spec).unwrap()
95 }
96
97 #[cfg(feature = "fix42")]
99 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix42")))]
100 pub fn fix42() -> Self {
101 let spec = include_str!("resources/quickfix/FIX-4.2.xml");
102 Dictionary::from_quickfix_spec(spec).unwrap()
103 }
104
105 #[cfg(feature = "fix43")]
107 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix43")))]
108 pub fn fix43() -> Self {
109 let spec = include_str!("resources/quickfix/FIX-4.3.xml");
110 Dictionary::from_quickfix_spec(spec).unwrap()
111 }
112
113 #[cfg(feature = "fix44")]
115 pub fn fix44() -> Self {
116 let spec = include_str!("resources/quickfix/FIX-4.4.xml");
117 Dictionary::from_quickfix_spec(spec).unwrap()
118 }
119
120 #[cfg(feature = "fix50")]
122 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50")))]
123 pub fn fix50() -> Self {
124 let spec = include_str!("resources/quickfix/FIX-5.0.xml");
125 Dictionary::from_quickfix_spec(spec).unwrap()
126 }
127
128 #[cfg(feature = "fix50sp1")]
130 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
131 pub fn fix50sp1() -> Self {
132 let spec = include_str!("resources/quickfix/FIX-5.0-SP1.xml");
133 Dictionary::from_quickfix_spec(spec).unwrap()
134 }
135
136 #[cfg(feature = "fix50sp2")]
138 #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
139 pub fn fix50sp2() -> Self {
140 let spec = include_str!("resources/quickfix/FIX-5.0-SP2.xml");
141 Dictionary::from_quickfix_spec(spec).unwrap()
142 }
143
144 #[cfg(feature = "fixt11")]
146 #[cfg_attr(doc_cfg, doc(cfg(feature = "fixt11")))]
147 pub fn fixt11() -> Self {
148 let spec = include_str!("resources/quickfix/FIXT-1.1.xml");
149 Dictionary::from_quickfix_spec(spec).unwrap()
150 }
151
152 pub fn common_dictionaries() -> Vec<Dictionary> {
156 vec![
157 #[cfg(feature = "fix40")]
158 Self::fix40(),
159 #[cfg(feature = "fix41")]
160 Self::fix41(),
161 #[cfg(feature = "fix42")]
162 Self::fix42(),
163 #[cfg(feature = "fix43")]
164 Self::fix43(),
165 #[cfg(feature = "fix44")]
166 Self::fix44(),
167 #[cfg(feature = "fix50")]
168 Self::fix50(),
169 #[cfg(feature = "fix50sp1")]
170 Self::fix50sp1(),
171 #[cfg(feature = "fix50sp2")]
172 Self::fix50sp2(),
173 #[cfg(feature = "fixt11")]
174 Self::fixt11(),
175 ]
176 }
177
178 pub fn message_by_name(&self, name: &str) -> Option<MessageDefinition<'_>> {
190 let msg_type = self.message_msgtypes_by_name.get(name)?;
191 self.message_by_msgtype(msg_type)
192 }
193
194 pub fn message_by_msgtype(&self, msgtype: &str) -> Option<MessageDefinition<'_>> {
206 self.messages_by_msgtype
207 .get(msgtype)
208 .map(|data| MessageDefinition(self, data))
209 }
210
211 pub fn component_by_name(&self, name: &str) -> Option<Component<'_>> {
213 self.components_by_name
214 .get(name)
215 .map(|data| Component(self, data))
216 }
217
218 pub fn datatype_by_name(&self, name: &str) -> Option<Datatype<'_>> {
228 self.data_types_by_name
229 .get(name)
230 .map(|data| Datatype(self, data))
231 }
232
233 pub fn field_by_tag(&self, tag: u32) -> Option<Field<'_>> {
244 self.fields_by_tags
245 .get(&tag)
246 .map(|data| Field::new(self, data))
247 }
248
249 pub fn field_by_name(&self, name: &str) -> Option<Field<'_>> {
251 let tag = self.field_tags_by_name.get(name)?;
252 self.field_by_tag(*tag)
253 }
254
255 pub fn datatypes(&self) -> Vec<Datatype<'_>> {
266 self.data_types_by_name
267 .values()
268 .map(|data| Datatype(self, data))
269 .collect()
270 }
271
272 pub fn messages(&self) -> Vec<MessageDefinition<'_>> {
284 self.messages_by_msgtype
285 .values()
286 .map(|data| MessageDefinition(self, data))
287 .collect()
288 }
289
290 pub fn fields(&self) -> Vec<Field<'_>> {
293 self.fields_by_tags
294 .values()
295 .map(|data| Field::new(self, data))
296 .collect()
297 }
298
299 pub fn components(&self) -> Vec<Component<'_>> {
302 self.components_by_name
303 .values()
304 .map(|data| Component(self, data))
305 .collect()
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_load_from_file_success() {
315 let path = concat!(
316 env!("CARGO_MANIFEST_DIR"),
317 "/src/resources/quickfix/FIX-4.4.xml"
318 );
319 let dict = Dictionary::load_from_file(path).unwrap();
320 assert_eq!(dict.version(), "FIX.4.4");
321 assert!(dict.message_by_name("Heartbeat").is_some());
322 }
323
324 #[test]
325 fn test_load_from_file_invalid_content() {
326 let path = concat!(
327 env!("CARGO_MANIFEST_DIR"),
328 "/src/test_data/quickfix_specs/empty_file.xml"
329 );
330 let result = Dictionary::load_from_file(path);
331 assert!(result.is_err());
332 }
333}