hotfix_dictionary/
dictionary.rs

1use crate::{Component, ComponentData, Datatype, DatatypeData, Field, FieldData};
2
3use crate::message_definition::{MessageData, MessageDefinition};
4use crate::quickfix::{ParseDictionaryError, QuickFixReader};
5use crate::string::SmartString;
6use fnv::FnvHashMap;
7
8/// Specifies business semantics for application-level entities within the FIX
9/// Protocol.
10///
11/// You can rely on [`Dictionary`] for accessing details about
12/// fields, messages, and other abstract entities as defined in the FIX
13/// specifications. Examples of such information include:
14///
15/// - The mapping of FIX field names to numeric tags (e.g. `BeginString` is 8).
16/// - Which FIX fields are mandatory and which are optional.
17/// - The data type of each and every FIX field.
18/// - What fields to expect in FIX headers.
19///
20/// N.B. The FIX Protocol mandates separation of concerns between session and
21/// application protocol only for FIX 5.0 and subsequent versions. All FIX
22/// Dictionaries for older versions will also contain information about the
23/// session layer.
24#[derive(Debug, Clone)]
25pub struct Dictionary {
26    pub(crate) version: String,
27
28    pub(crate) data_types_by_name: FnvHashMap<SmartString, DatatypeData>,
29
30    pub(crate) fields_by_tags: FnvHashMap<u32, FieldData>,
31    pub(crate) field_tags_by_name: FnvHashMap<SmartString, u32>,
32
33    pub(crate) components_by_name: FnvHashMap<SmartString, ComponentData>,
34
35    pub(crate) messages_by_msgtype: FnvHashMap<SmartString, MessageData>,
36    pub(crate) message_msgtypes_by_name: FnvHashMap<SmartString, SmartString>,
37}
38
39impl Dictionary {
40    /// Creates a new empty FIX Dictionary named `version`.
41    pub fn new<S: ToString>(version: S) -> Self {
42        Dictionary {
43            version: version.to_string(),
44            data_types_by_name: FnvHashMap::default(),
45            fields_by_tags: FnvHashMap::default(),
46            field_tags_by_name: FnvHashMap::default(),
47            components_by_name: FnvHashMap::default(),
48            messages_by_msgtype: FnvHashMap::default(),
49            message_msgtypes_by_name: FnvHashMap::default(),
50        }
51    }
52
53    /// Attempts to read a QuickFIX-style specification file and convert it into
54    /// a [`Dictionary`].
55    pub fn from_quickfix_spec(input: &str) -> Result<Self, ParseDictionaryError> {
56        let xml_document =
57            roxmltree::Document::parse(input).map_err(|_| ParseDictionaryError::InvalidFormat)?;
58        QuickFixReader::new(&xml_document)
59    }
60
61    /// Returns the version string associated with this [`Dictionary`] (e.g.
62    /// `FIXT.1.1`, `FIX.4.2`).
63    ///
64    /// ```
65    /// use hotfix_dictionary::Dictionary;
66    ///
67    /// let dict = Dictionary::fix44();
68    /// assert_eq!(dict.version(), "FIX.4.4");
69    /// ```
70    pub fn version(&self) -> &str {
71        self.version.as_str()
72    }
73
74    pub fn load_from_file(path: &str) -> Result<Self, ParseDictionaryError> {
75        let spec = std::fs::read_to_string(path)
76            .unwrap_or_else(|_| panic!("unable to read FIX dictionary file at {path}"));
77        Dictionary::from_quickfix_spec(&spec)
78    }
79
80    /// Creates a new [`Dictionary`] for FIX 4.0.
81    #[cfg(feature = "fix40")]
82    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix40")))]
83    pub fn fix40() -> Self {
84        let spec = include_str!("resources/quickfix/FIX-4.0.xml");
85        Dictionary::from_quickfix_spec(spec).unwrap()
86    }
87
88    /// Creates a new [`Dictionary`] for FIX 4.1.
89    #[cfg(feature = "fix41")]
90    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix41")))]
91    pub fn fix41() -> Self {
92        let spec = include_str!("resources/quickfix/FIX-4.1.xml");
93        Dictionary::from_quickfix_spec(spec).unwrap()
94    }
95
96    /// Creates a new [`Dictionary`] for FIX 4.2.
97    #[cfg(feature = "fix42")]
98    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix42")))]
99    pub fn fix42() -> Self {
100        let spec = include_str!("resources/quickfix/FIX-4.2.xml");
101        Dictionary::from_quickfix_spec(spec).unwrap()
102    }
103
104    /// Creates a new [`Dictionary`] for FIX 4.3.
105    #[cfg(feature = "fix43")]
106    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix43")))]
107    pub fn fix43() -> Self {
108        let spec = include_str!("resources/quickfix/FIX-4.3.xml");
109        Dictionary::from_quickfix_spec(spec).unwrap()
110    }
111
112    /// Creates a new [`Dictionary`] for FIX 4.4.
113    #[cfg(feature = "fix44")]
114    pub fn fix44() -> Self {
115        let spec = include_str!("resources/quickfix/FIX-4.4.xml");
116        Dictionary::from_quickfix_spec(spec).unwrap()
117    }
118
119    /// Creates a new [`Dictionary`] for FIX 5.0.
120    #[cfg(feature = "fix50")]
121    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50")))]
122    pub fn fix50() -> Self {
123        let spec = include_str!("resources/quickfix/FIX-5.0.xml");
124        Dictionary::from_quickfix_spec(spec).unwrap()
125    }
126
127    /// Creates a new [`Dictionary`] for FIX 5.0 SP1.
128    #[cfg(feature = "fix50sp1")]
129    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
130    pub fn fix50sp1() -> Self {
131        let spec = include_str!("resources/quickfix/FIX-5.0-SP1.xml");
132        Dictionary::from_quickfix_spec(spec).unwrap()
133    }
134
135    /// Creates a new [`Dictionary`] for FIX 5.0 SP2.
136    #[cfg(feature = "fix50sp2")]
137    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
138    pub fn fix50sp2() -> Self {
139        let spec = include_str!("resources/quickfix/FIX-5.0-SP2.xml");
140        Dictionary::from_quickfix_spec(spec).unwrap()
141    }
142
143    /// Creates a new [`Dictionary`] for FIXT 1.1.
144    #[cfg(feature = "fixt11")]
145    #[cfg_attr(doc_cfg, doc(cfg(feature = "fixt11")))]
146    pub fn fixt11() -> Self {
147        let spec = include_str!("resources/quickfix/FIXT-1.1.xml");
148        Dictionary::from_quickfix_spec(spec).unwrap()
149    }
150
151    /// Returns a [`Vec`] of FIX [`Dictionary`]'s for the most common FIX
152    /// versions (that have been enabled via feature flags). This is only
153    /// intended for testing purposes.
154    pub fn common_dictionaries() -> Vec<Dictionary> {
155        vec![
156            #[cfg(feature = "fix40")]
157            Self::fix40(),
158            #[cfg(feature = "fix41")]
159            Self::fix41(),
160            #[cfg(feature = "fix42")]
161            Self::fix42(),
162            #[cfg(feature = "fix43")]
163            Self::fix43(),
164            #[cfg(feature = "fix44")]
165            Self::fix44(),
166            #[cfg(feature = "fix50")]
167            Self::fix50(),
168            #[cfg(feature = "fix50sp1")]
169            Self::fix50sp1(),
170            #[cfg(feature = "fix50sp2")]
171            Self::fix50sp2(),
172            #[cfg(feature = "fixt11")]
173            Self::fixt11(),
174        ]
175    }
176
177    /// Returns the [`Message`] associated with `name`, if any.
178    ///
179    /// ```
180    /// use hotfix_dictionary::Dictionary;
181    ///
182    /// let dict = Dictionary::fix44();
183    ///
184    /// let msg1 = dict.message_by_name("Heartbeat").unwrap();
185    /// let msg2 = dict.message_by_msgtype("0").unwrap();
186    /// assert_eq!(msg1.name(), msg2.name());
187    /// ```
188    pub fn message_by_name(&self, name: &str) -> Option<MessageDefinition<'_>> {
189        let msg_type = self.message_msgtypes_by_name.get(name)?;
190        self.message_by_msgtype(msg_type)
191    }
192
193    /// Returns the [`Message`] that has the given `msgtype`, if any.
194    ///
195    /// ```
196    /// use hotfix_dictionary::Dictionary;
197    ///
198    /// let dict = Dictionary::fix44();
199    ///
200    /// let msg1 = dict.message_by_msgtype("0").unwrap();
201    /// let msg2 = dict.message_by_name("Heartbeat").unwrap();
202    /// assert_eq!(msg1.name(), msg2.name());
203    /// ```
204    pub fn message_by_msgtype(&self, msgtype: &str) -> Option<MessageDefinition<'_>> {
205        self.messages_by_msgtype
206            .get(msgtype)
207            .map(|data| MessageDefinition(self, data))
208    }
209
210    /// Returns the [`Component`] named `name`, if any.
211    pub fn component_by_name(&self, name: &str) -> Option<Component<'_>> {
212        self.components_by_name
213            .get(name)
214            .map(|data| Component(self, data))
215    }
216
217    /// Returns the [`Datatype`] named `name`, if any.
218    ///
219    /// ```
220    /// use hotfix_dictionary::Dictionary;
221    ///
222    /// let dict = Dictionary::fix44();
223    /// let dt = dict.datatype_by_name("String").unwrap();
224    /// assert_eq!(dt.name(), "String");
225    /// ```
226    pub fn datatype_by_name(&self, name: &str) -> Option<Datatype<'_>> {
227        self.data_types_by_name
228            .get(name)
229            .map(|data| Datatype(self, data))
230    }
231
232    /// Returns the [`Field`] associated with `tag`, if any.
233    ///
234    /// ```
235    /// use hotfix_dictionary::Dictionary;
236    ///
237    /// let dict = Dictionary::fix44();
238    /// let field1 = dict.field_by_tag(112).unwrap();
239    /// let field2 = dict.field_by_name("TestReqID").unwrap();
240    /// assert_eq!(field1.name(), field2.name());
241    /// ```
242    pub fn field_by_tag(&self, tag: u32) -> Option<Field<'_>> {
243        self.fields_by_tags
244            .get(&tag)
245            .map(|data| Field::new(self, data))
246    }
247
248    /// Returns the [`Field`] named `name`, if any.
249    pub fn field_by_name(&self, name: &str) -> Option<Field<'_>> {
250        let tag = self.field_tags_by_name.get(name)?;
251        self.field_by_tag(*tag)
252    }
253
254    /// Returns a [`Vec`] of all [`Datatype`]'s in this [`Dictionary`]. The ordering
255    /// of items is not specified.
256    ///
257    /// ```
258    /// use hotfix_dictionary::Dictionary;
259    ///
260    /// let dict = Dictionary::fix44();
261    /// // FIX 4.4 defines 23 (FIXME) datatypes.
262    /// assert_eq!(dict.datatypes().len(), 23);
263    /// ```
264    pub fn datatypes(&self) -> Vec<Datatype<'_>> {
265        self.data_types_by_name
266            .values()
267            .map(|data| Datatype(self, data))
268            .collect()
269    }
270
271    /// Returns a [`Vec`] of all [`Message`]'s in this [`Dictionary`]. The ordering
272    /// of items is not specified.
273    ///
274    /// ```
275    /// use hotfix_dictionary::Dictionary;
276    ///
277    /// let dict = Dictionary::fix44();
278    /// let msgs = dict.messages();
279    /// let msg = msgs.iter().find(|m| m.name() == "MarketDataRequest");
280    /// assert_eq!(msg.unwrap().msg_type(), "V");
281    /// ```
282    pub fn messages(&self) -> Vec<MessageDefinition<'_>> {
283        self.messages_by_msgtype
284            .values()
285            .map(|data| MessageDefinition(self, data))
286            .collect()
287    }
288
289    /// Returns a [`Vec`] of all [`Field`]'s in this [`Dictionary`]. The ordering
290    /// of items is not specified.
291    pub fn fields(&self) -> Vec<Field<'_>> {
292        self.fields_by_tags
293            .values()
294            .map(|data| Field::new(self, data))
295            .collect()
296    }
297
298    /// Returns a [`Vec`] of all [`Component`]'s in this [`Dictionary`]. The ordering
299    /// of items is not specified.
300    pub fn components(&self) -> Vec<Component<'_>> {
301        self.components_by_name
302            .values()
303            .map(|data| Component(self, data))
304            .collect()
305    }
306}