hotfix_dictionary/
lib.rs

1//! Access to FIX Dictionary reference and message specifications.
2
3mod quickfix;
4
5pub use datatype::FixDatatype;
6use fnv::FnvHashMap;
7use quickfix::{ParseDictionaryError, QuickFixReader};
8use smartstring::alias::String as SmartString;
9use std::{fmt, sync::Arc};
10
11/// Type alias for FIX tags: 32-bit unsigned integers, strictly positive.
12pub type TagU32 = std::num::NonZeroU32;
13
14pub trait DataFieldLookup<F> {
15    fn field_is_data(&self, field: F) -> bool;
16}
17
18pub trait NumInGroupLookup<F> {
19    fn field_is_num_in_group(&self, field: F) -> bool;
20}
21
22impl DataFieldLookup<u32> for Dictionary {
23    fn field_is_data(&self, tag: u32) -> bool {
24        if let Some(field) = self.field_by_tag(tag) {
25            field.data_type().basetype() == FixDatatype::Data
26        } else {
27            false
28        }
29    }
30}
31
32impl NumInGroupLookup<u32> for Dictionary {
33    fn field_is_num_in_group(&self, tag: u32) -> bool {
34        if let Some(field) = self.field_by_tag(tag) {
35            field.data_type().basetype() == FixDatatype::NumInGroup
36        } else {
37            false
38        }
39    }
40}
41
42/// The expected location of a field within a FIX message (i.e. header, body, or
43/// trailer).
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45pub enum FieldLocation {
46    /// The field is located inside the "Standard Header".
47    Header,
48    /// This field is located inside the body of the FIX message.
49    Body,
50    /// This field is located inside the "Standard Trailer".
51    Trailer,
52}
53
54/// A mapping from FIX version strings to [`Dictionary`] values.
55pub type Dictionaries = FnvHashMap<String, Arc<Dictionary>>;
56
57/// Specifies business semantics for application-level entities within the FIX
58/// Protocol.
59///
60/// You can rely on [`Dictionary`] for accessing details about
61/// fields, messages, and other abstract entities as defined in the FIX
62/// specifications. Examples of such information include:
63///
64/// - The mapping of FIX field names to numeric tags (e.g. `BeginString` is 8).
65/// - Which FIX fields are mandatory and which are optional.
66/// - The data type of each and every FIX field.
67/// - What fields to expect in FIX headers.
68///
69/// N.B. The FIX Protocol mandates separation of concerns between session and
70/// application protocol only for FIX 5.0 and subsequent versions. All FIX
71/// Dictionaries for older versions will also contain information about the
72/// session layer.
73#[derive(Debug, Clone)]
74pub struct Dictionary {
75    version: String,
76
77    abbreviation_definitions: FnvHashMap<SmartString, AbbreviationData>,
78
79    data_types_by_name: FnvHashMap<SmartString, DatatypeData>,
80
81    fields_by_tags: FnvHashMap<u32, FieldData>,
82    field_tags_by_name: FnvHashMap<SmartString, u32>,
83
84    components_by_name: FnvHashMap<SmartString, ComponentData>,
85
86    messages_by_msgtype: FnvHashMap<SmartString, MessageData>,
87    message_msgtypes_by_name: FnvHashMap<SmartString, SmartString>,
88
89    //layout_items: Vec<LayoutItemData>,
90    categories_by_name: FnvHashMap<SmartString, CategoryData>,
91}
92
93#[allow(dead_code)]
94fn display_layout_item(indent: u32, item: LayoutItem, f: &mut fmt::Formatter) -> fmt::Result {
95    for _ in 0..indent {
96        write!(f, " ")?;
97    }
98    match item.kind() {
99        LayoutItemKind::Field(_) => {
100            writeln!(
101                f,
102                "<field name='{}' required='{}' />",
103                item.tag_text(),
104                item.required(),
105            )?;
106        }
107        LayoutItemKind::Group(_, _fields) => {
108            writeln!(
109                f,
110                "<group name='{}' required='{}' />",
111                item.tag_text(),
112                item.required(),
113            )?;
114            writeln!(f, "</group>")?;
115        }
116        LayoutItemKind::Component(_c) => {
117            writeln!(
118                f,
119                "<component name='{}' required='{}' />",
120                item.tag_text(),
121                item.required(),
122            )?;
123            writeln!(f, "</component>")?;
124        }
125    }
126    Ok(())
127}
128
129impl Dictionary {
130    /// Creates a new empty FIX Dictionary named `version`.
131    fn new<S: ToString>(version: S) -> Self {
132        Dictionary {
133            version: version.to_string(),
134            abbreviation_definitions: FnvHashMap::default(),
135            data_types_by_name: FnvHashMap::default(),
136            fields_by_tags: FnvHashMap::default(),
137            field_tags_by_name: FnvHashMap::default(),
138            components_by_name: FnvHashMap::default(),
139            messages_by_msgtype: FnvHashMap::default(),
140            message_msgtypes_by_name: FnvHashMap::default(),
141            categories_by_name: FnvHashMap::default(),
142        }
143    }
144
145    /// Attempts to read a QuickFIX-style specification file and convert it into
146    /// a [`Dictionary`].
147    pub fn from_quickfix_spec(input: &str) -> Result<Self, ParseDictionaryError> {
148        let xml_document =
149            roxmltree::Document::parse(input).map_err(|_| ParseDictionaryError::InvalidFormat)?;
150        QuickFixReader::new(&xml_document)
151    }
152
153    /// Returns the version string associated with this [`Dictionary`] (e.g.
154    /// `FIXT.1.1`, `FIX.4.2`).
155    ///
156    /// ```
157    /// use hotfix_dictionary::Dictionary;
158    ///
159    /// let dict = Dictionary::fix44();
160    /// assert_eq!(dict.version(), "FIX.4.4");
161    /// ```
162    pub fn version(&self) -> &str {
163        self.version.as_str()
164    }
165
166    /// Creates a new [`Dictionary`] for FIX 4.0.
167    #[cfg(feature = "fix40")]
168    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix40")))]
169    pub fn fix40() -> Self {
170        let spec = include_str!("resources/quickfix/FIX-4.0.xml");
171        Dictionary::from_quickfix_spec(spec).unwrap()
172    }
173
174    /// Creates a new [`Dictionary`] for FIX 4.1.
175    #[cfg(feature = "fix41")]
176    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix41")))]
177    pub fn fix41() -> Self {
178        let spec = include_str!("resources/quickfix/FIX-4.1.xml");
179        Dictionary::from_quickfix_spec(spec).unwrap()
180    }
181
182    /// Creates a new [`Dictionary`] for FIX 4.2.
183    #[cfg(feature = "fix42")]
184    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix42")))]
185    pub fn fix42() -> Self {
186        let spec = include_str!("resources/quickfix/FIX-4.2.xml");
187        Dictionary::from_quickfix_spec(spec).unwrap()
188    }
189
190    /// Creates a new [`Dictionary`] for FIX 4.3.
191    #[cfg(feature = "fix43")]
192    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix43")))]
193    pub fn fix43() -> Self {
194        let spec = include_str!("resources/quickfix/FIX-4.3.xml");
195        Dictionary::from_quickfix_spec(spec).unwrap()
196    }
197
198    /// Creates a new [`Dictionary`] for FIX 4.4.
199    pub fn fix44() -> Self {
200        let spec = include_str!("resources/quickfix/FIX-4.4.xml");
201        Dictionary::from_quickfix_spec(spec).unwrap()
202    }
203
204    /// Creates a new [`Dictionary`] for FIX 5.0.
205    #[cfg(feature = "fix50")]
206    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50")))]
207    pub fn fix50() -> Self {
208        let spec = include_str!("resources/quickfix/FIX-5.0.xml");
209        Dictionary::from_quickfix_spec(spec).unwrap()
210    }
211
212    /// Creates a new [`Dictionary`] for FIX 5.0 SP1.
213    #[cfg(feature = "fix50sp1")]
214    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
215    pub fn fix50sp1() -> Self {
216        let spec = include_str!("resources/quickfix/FIX-5.0-SP1.xml");
217        Dictionary::from_quickfix_spec(spec).unwrap()
218    }
219
220    /// Creates a new [`Dictionary`] for FIX 5.0 SP2.
221    #[cfg(feature = "fix50sp2")]
222    #[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
223    pub fn fix50sp2() -> Self {
224        let spec = include_str!("resources/quickfix/FIX-5.0-SP2.xml");
225        Dictionary::from_quickfix_spec(spec).unwrap()
226    }
227
228    /// Creates a new [`Dictionary`] for FIXT 1.1.
229    #[cfg(feature = "fixt11")]
230    #[cfg_attr(doc_cfg, doc(cfg(feature = "fixt11")))]
231    pub fn fixt11() -> Self {
232        let spec = include_str!("resources/quickfix/FIXT-1.1.xml");
233        Dictionary::from_quickfix_spec(spec).unwrap()
234    }
235
236    /// Returns a [`Vec`] of FIX [`Dictionary`]'s for the most common FIX
237    /// versions (that have been enabled via feature flags). This is only
238    /// intended for testing purposes.
239    pub fn common_dictionaries() -> Vec<Dictionary> {
240        vec![
241            #[cfg(feature = "fix40")]
242            Self::fix40(),
243            #[cfg(feature = "fix41")]
244            Self::fix41(),
245            #[cfg(feature = "fix42")]
246            Self::fix42(),
247            #[cfg(feature = "fix43")]
248            Self::fix43(),
249            Self::fix44(),
250            #[cfg(feature = "fix50")]
251            Self::fix50(),
252            #[cfg(feature = "fix50sp1")]
253            Self::fix50sp1(),
254            #[cfg(feature = "fix50sp2")]
255            Self::fix50sp2(),
256            #[cfg(feature = "fixt11")]
257            Self::fixt11(),
258        ]
259    }
260
261    /// Return the known abbreviation for `term` -if any- according to the
262    /// documentation of this FIX Dictionary.
263    pub fn abbreviation_for(&self, term: &str) -> Option<Abbreviation> {
264        self.abbreviation_definitions
265            .get(term)
266            .map(|data| Abbreviation(self, data))
267    }
268
269    /// Returns the [`Message`](Message) associated with `name`, if any.
270    ///
271    /// ```
272    /// use hotfix_dictionary::Dictionary;
273    ///
274    /// let dict = Dictionary::fix44();
275    ///
276    /// let msg1 = dict.message_by_name("Heartbeat").unwrap();
277    /// let msg2 = dict.message_by_msgtype("0").unwrap();
278    /// assert_eq!(msg1.name(), msg2.name());
279    /// ```
280    pub fn message_by_name(&self, name: &str) -> Option<Message> {
281        let msg_type = self.message_msgtypes_by_name.get(name)?;
282        self.message_by_msgtype(msg_type)
283    }
284
285    /// Returns the [`Message`](Message) that has the given `msgtype`, if any.
286    ///
287    /// ```
288    /// use hotfix_dictionary::Dictionary;
289    ///
290    /// let dict = Dictionary::fix44();
291    ///
292    /// let msg1 = dict.message_by_msgtype("0").unwrap();
293    /// let msg2 = dict.message_by_name("Heartbeat").unwrap();
294    /// assert_eq!(msg1.name(), msg2.name());
295    /// ```
296    pub fn message_by_msgtype(&self, msgtype: &str) -> Option<Message> {
297        self.messages_by_msgtype
298            .get(msgtype)
299            .map(|data| Message(self, data))
300    }
301
302    /// Returns the [`Component`] named `name`, if any.
303    pub fn component_by_name(&self, name: &str) -> Option<Component> {
304        self.components_by_name
305            .get(name)
306            .map(|data| Component(self, data))
307    }
308
309    /// Returns the [`Datatype`] named `name`, if any.
310    ///
311    /// ```
312    /// use hotfix_dictionary::Dictionary;
313    ///
314    /// let dict = Dictionary::fix44();
315    /// let dt = dict.datatype_by_name("String").unwrap();
316    /// assert_eq!(dt.name(), "String");
317    /// ```
318    pub fn datatype_by_name(&self, name: &str) -> Option<Datatype> {
319        self.data_types_by_name
320            .get(name)
321            .map(|data| Datatype(self, data))
322    }
323
324    /// Returns the [`Field`] associated with `tag`, if any.
325    ///
326    /// ```
327    /// use hotfix_dictionary::Dictionary;
328    ///
329    /// let dict = Dictionary::fix44();
330    /// let field1 = dict.field_by_tag(112).unwrap();
331    /// let field2 = dict.field_by_name("TestReqID").unwrap();
332    /// assert_eq!(field1.name(), field2.name());
333    /// ```
334    pub fn field_by_tag(&self, tag: u32) -> Option<Field> {
335        self.fields_by_tags.get(&tag).map(|data| Field(self, data))
336    }
337
338    /// Returns the [`Field`] named `name`, if any.
339    pub fn field_by_name(&self, name: &str) -> Option<Field> {
340        let tag = self.field_tags_by_name.get(name)?;
341        self.field_by_tag(*tag)
342    }
343
344    /// Returns the [`Category`] named `name`, if any.
345    fn category_by_name(&self, name: &str) -> Option<Category> {
346        self.categories_by_name
347            .get(name)
348            .map(|data| Category(self, data))
349    }
350
351    /// Returns a [`Vec`] of all [`Datatype`]'s in this [`Dictionary`]. The ordering
352    /// of items is not specified.
353    ///
354    /// ```
355    /// use hotfix_dictionary::Dictionary;
356    ///
357    /// let dict = Dictionary::fix44();
358    /// // FIX 4.4 defines 23 (FIXME) datatypes.
359    /// assert_eq!(dict.datatypes().len(), 23);
360    /// ```
361    pub fn datatypes(&self) -> Vec<Datatype> {
362        self.data_types_by_name
363            .values()
364            .map(|data| Datatype(self, data))
365            .collect()
366    }
367
368    /// Returns a [`Vec`] of all [`Message`]'s in this [`Dictionary`]. The ordering
369    /// of items is not specified.
370    ///
371    /// ```
372    /// use hotfix_dictionary::Dictionary;
373    ///
374    /// let dict = Dictionary::fix44();
375    /// let msgs = dict.messages();
376    /// let msg = msgs.iter().find(|m| m.name() == "MarketDataRequest");
377    /// assert_eq!(msg.unwrap().msg_type(), "V");
378    /// ```
379    pub fn messages(&self) -> Vec<Message> {
380        self.messages_by_msgtype
381            .values()
382            .map(|data| Message(self, data))
383            .collect()
384    }
385
386    /// Returns a [`Vec`] of all [`Category`]'s in this [`Dictionary`]. The ordering
387    /// of items is not specified.
388    pub fn categories(&self) -> Vec<Category> {
389        self.categories_by_name
390            .values()
391            .map(|data| Category(self, data))
392            .collect()
393    }
394
395    /// Returns a [`Vec`] of all [`Field`]'s in this [`Dictionary`]. The ordering
396    /// of items is not specified.
397    pub fn fields(&self) -> Vec<Field> {
398        self.fields_by_tags
399            .values()
400            .map(|data| Field(self, data))
401            .collect()
402    }
403
404    /// Returns a [`Vec`] of all [`Component`]'s in this [`Dictionary`]. The ordering
405    /// of items is not specified.
406    pub fn components(&self) -> Vec<Component> {
407        self.components_by_name
408            .values()
409            .map(|data| Component(self, data))
410            .collect()
411    }
412}
413
414/// Builder utilities
415impl Dictionary {
416    fn add_field(&mut self, field: FieldData) {
417        self.field_tags_by_name
418            .insert(field.name.clone(), field.tag);
419        self.fields_by_tags.insert(field.tag, field);
420    }
421
422    fn add_message(&mut self, message: MessageData) {
423        self.message_msgtypes_by_name
424            .insert(message.name.clone(), message.msg_type.clone());
425        self.messages_by_msgtype
426            .insert(message.msg_type.clone(), message);
427    }
428
429    fn add_component(&mut self, component: ComponentData) {
430        self.components_by_name
431            .insert(component.name.clone(), component);
432    }
433
434    fn add_datatype(&mut self, datatype: DatatypeData) {
435        self.data_types_by_name
436            .insert(datatype.datatype.name().into(), datatype);
437    }
438
439    fn add_category(&mut self, category: CategoryData) {
440        self.categories_by_name
441            .insert(category.name.clone().into(), category);
442    }
443}
444
445#[derive(Clone, Debug)]
446struct AbbreviationData {
447    abbreviation: SmartString,
448}
449
450/// An [`Abbreviation`] is a standardized abbreviated form for a specific word,
451/// pattern, or name. Abbreviation data is mostly meant for documentation
452/// purposes, but in general it can have other uses as well, e.g. FIXML field
453/// naming.
454#[allow(dead_code)]
455#[derive(Debug)]
456pub struct Abbreviation<'a>(&'a Dictionary, &'a AbbreviationData);
457
458impl<'a> Abbreviation<'a> {
459    /// Returns the full term (non-abbreviated) associated with `self`.
460    pub fn term(&self) -> &str {
461        self.1.abbreviation.as_str()
462    }
463}
464
465#[derive(Clone, Debug)]
466struct CategoryData {
467    /// **Primary key**. A string uniquely identifying this category.
468    name: String,
469}
470
471/// A [`Category`] is a collection of loosely related FIX messages or components
472/// all belonging to the same [`Section`].
473#[derive(Clone, Debug)]
474#[allow(dead_code)]
475pub struct Category<'a>(&'a Dictionary, &'a CategoryData);
476
477#[derive(Clone, Debug)]
478struct ComponentData {
479    /// **Primary key.** The unique integer identifier of this component
480    /// type.
481    id: usize,
482    component_type: FixmlComponentAttributes,
483    layout_items: Vec<LayoutItemData>,
484    category_name: SmartString,
485    /// The human readable name of the component.
486    name: SmartString,
487}
488
489/// A [`Component`] is an ordered collection of fields and/or other components.
490/// There are two kinds of components: (1) common blocks and (2) repeating
491/// groups. Common blocks are merely commonly reused sequences of the same
492/// fields/components
493/// which are given names for simplicity, i.e. they serve as "macros". Repeating
494/// groups, on the other hand, are components which can appear zero or more times
495/// inside FIX messages (or other components, for that matter).
496#[derive(Clone, Debug)]
497pub struct Component<'a>(&'a Dictionary, &'a ComponentData);
498
499impl<'a> Component<'a> {
500    /// Returns the unique numberic ID of `self`.
501    pub fn id(&self) -> u32 {
502        self.1.id as u32
503    }
504
505    /// Returns the name of `self`. The name of every [`Component`] is unique
506    /// across a [`Dictionary`].
507    pub fn name(&self) -> &str {
508        self.1.name.as_str()
509    }
510
511    /// Returns `true` if and only if `self` is a "group" component; `false`
512    /// otherwise.
513    pub fn is_group(&self) -> bool {
514        match self.1.component_type {
515            FixmlComponentAttributes::Block { is_repeating, .. } => is_repeating,
516            _ => false,
517        }
518    }
519
520    /// Returns the [`Category`] to which `self` belongs.
521    pub fn category(&self) -> Category {
522        self.0
523            .category_by_name(self.1.category_name.as_str())
524            .unwrap()
525    }
526
527    /// Returns an [`Iterator`] over all items that are part of `self`.
528    pub fn items(&self) -> impl Iterator<Item = LayoutItem> {
529        self.1
530            .layout_items
531            .iter()
532            .map(move |data| LayoutItem(self.0, data))
533    }
534
535    /// Checks whether `field` appears in the definition of `self` and returns
536    /// `true` if it does, `false` otherwise.
537    pub fn contains_field(&self, field: &Field) -> bool {
538        self.items().any(|layout_item| {
539            if let LayoutItemKind::Field(f) = layout_item.kind() {
540                f.tag() == field.tag()
541            } else {
542                false
543            }
544        })
545    }
546}
547
548/// Component type (FIXML-specific information).
549#[derive(Clone, Debug, PartialEq)]
550#[allow(dead_code)]
551pub enum FixmlComponentAttributes {
552    Xml,
553    Block {
554        is_repeating: bool,
555        is_implicit: bool,
556        is_optimized: bool,
557    },
558    Message,
559}
560
561#[derive(Clone, Debug, PartialEq)]
562struct DatatypeData {
563    /// **Primary key.** Identifier of the datatype.
564    datatype: FixDatatype,
565    /// Human readable description of this Datatype.
566    description: String,
567    /// A string that contains examples values for a datatype
568    examples: Vec<String>,
569    // TODO: 'XML'.
570}
571
572/// A FIX data type defined as part of a [`Dictionary`].
573#[derive(Debug)]
574#[allow(dead_code)]
575pub struct Datatype<'a>(&'a Dictionary, &'a DatatypeData);
576
577impl<'a> Datatype<'a> {
578    /// Returns the name of `self`.  This is also guaranteed to be a valid Rust
579    /// identifier.
580    pub fn name(&self) -> &str {
581        self.1.datatype.name()
582    }
583
584    /// Returns `self` as an `enum`.
585    pub fn basetype(&self) -> FixDatatype {
586        self.1.datatype
587    }
588}
589
590mod datatype {
591    use strum::IntoEnumIterator;
592    use strum_macros::{EnumIter, IntoStaticStr};
593
594    /// Sum type for all possible FIX data types ever defined across all FIX
595    /// application versions.
596    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, EnumIter, IntoStaticStr)]
597    #[repr(u8)]
598    #[non_exhaustive]
599    pub enum FixDatatype {
600        /// Single character value, can include any alphanumeric character or
601        /// punctuation except the delimiter. All char fields are case sensitive
602        /// (i.e. m != M). The following fields are based on char.
603        Char,
604        /// char field containing one of two values: 'Y' = True/Yes 'N' = False/No.
605        Boolean,
606        /// Sequence of digits with optional decimal point and sign character (ASCII
607        /// characters "-", "0" - "9" and "."); the absence of the decimal point
608        /// within the string will be interpreted as the float representation of an
609        /// integer value. All float fields must accommodate up to fifteen
610        /// significant digits. The number of decimal places used should be a factor
611        /// of business/market needs and mutual agreement between counterparties.
612        /// Note that float values may contain leading zeros (e.g. "00023.23" =
613        /// "23.23") and may contain or omit trailing zeros after the decimal point
614        /// (e.g. "23.0" = "23.0000" = "23" = "23."). Note that fields which are
615        /// derived from float may contain negative values unless explicitly
616        /// specified otherwise. The following data types are based on float.
617        Float,
618        /// float field typically representing a Price times a Qty.
619        Amt,
620        /// float field representing a price. Note the number of decimal places may
621        /// vary. For certain asset classes prices may be negative values. For
622        /// example, prices for options strategies can be negative under certain
623        /// market conditions. Refer to Volume 7: FIX Usage by Product for asset
624        /// classes that support negative price values.
625        Price,
626        /// float field representing a price offset, which can be mathematically
627        /// added to a "Price". Note the number of decimal places may vary and some
628        /// fields such as LastForwardPoints may be negative.
629        PriceOffset,
630        /// float field capable of storing either a whole number (no decimal places)
631        /// of "shares" (securities denominated in whole units) or a decimal value
632        /// containing decimal places for non-share quantity asset classes
633        /// (securities denominated in fractional units).
634        Qty,
635        /// float field representing a percentage (e.g. 0.05 represents 5% and 0.9525
636        /// represents 95.25%). Note the number of decimal places may vary.
637        Percentage,
638        /// Sequence of digits without commas or decimals and optional sign character
639        /// (ASCII characters "-" and "0" - "9" ). The sign character utilizes one
640        /// byte (i.e. positive int is "99999" while negative int is "-99999"). Note
641        /// that int values may contain leading zeros (e.g. "00023" = "23").
642        /// Examples: 723 in field 21 would be mapped int as |21=723|. -723 in field
643        /// 12 would be mapped int as |12=-723| The following data types are based on
644        /// int.
645        Int,
646        /// int field representing a day during a particular monthy (values 1 to 31).
647        DayOfMonth,
648        /// int field representing the length in bytes. Value must be positive.
649        Length,
650        /// int field representing the number of entries in a repeating group. Value
651        /// must be positive.
652        NumInGroup,
653        /// int field representing a message sequence number. Value must be positive.
654        SeqNum,
655        /// `int` field representing a field's tag number when using FIX "Tag=Value"
656        /// syntax. Value must be positive and may not contain leading zeros.
657        TagNum,
658        /// Alpha-numeric free format strings, can include any character or
659        /// punctuation except the delimiter. All String fields are case sensitive
660        /// (i.e. morstatt != Morstatt).
661        String,
662        /// string field containing raw data with no format or content restrictions.
663        /// Data fields are always immediately preceded by a length field. The length
664        /// field should specify the number of bytes of the value of the data field
665        /// (up to but not including the terminating SOH). Caution: the value of one
666        /// of these fields may contain the delimiter (SOH) character. Note that the
667        /// value specified for this field should be followed by the delimiter (SOH)
668        /// character as all fields are terminated with an "SOH".
669        Data,
670        /// string field representing month of a year. An optional day of the month
671        /// can be appended or an optional week code. Valid formats: YYYYMM YYYYMMDD
672        /// YYYYMMWW Valid values: YYYY = 0000-9999; MM = 01-12; DD = 01-31; WW = w1,
673        /// w2, w3, w4, w5.
674        MonthYear,
675        /// string field containing one or more space delimited single character
676        /// values (e.g. |18=2 A F| ).
677        MultipleCharValue,
678        /// string field representing a currency type using ISO 4217 Currency code (3
679        /// character) values (see Appendix 6-A).
680        Currency,
681        /// string field representing a market or exchange using ISO 10383 Market
682        /// Identifier Code (MIC) values (see"Appendix 6-C).
683        Exchange,
684        /// Identifier for a national language - uses ISO 639-1 standard.
685        Language,
686        /// string field represening a Date of Local Market (as oppose to UTC) in
687        /// YYYYMMDD format. This is the "normal" date field used by the FIX
688        /// Protocol. Valid values: YYYY = 0000-9999, MM = 01-12, DD = 01-31.
689        LocalMktDate,
690        /// string field containing one or more space delimited multiple character
691        /// values (e.g. |277=AV AN A| ).
692        MultipleStringValue,
693        /// string field representing Date represented in UTC (Universal Time
694        /// Coordinated, also known as "GMT") in YYYYMMDD format. This
695        /// special-purpose field is paired with UTCTimeOnly to form a proper
696        /// UTCTimestamp for bandwidth-sensitive messages. Valid values: YYYY =
697        /// 0000-9999, MM = 01-12, DD = 01-31.
698        UtcDateOnly,
699        /// string field representing Time-only represented in UTC (Universal Time
700        /// Coordinated, also known as "GMT") in either HH:MM:SS (whole seconds) or
701        /// HH:MM:SS.sss (milliseconds) format, colons, and period required. This
702        /// special-purpose field is paired with UTCDateOnly to form a proper
703        /// UTCTimestamp for bandwidth-sensitive messages. Valid values: HH = 00-23,
704        /// MM = 00-60 (60 only if UTC leap second), SS = 00-59. (without
705        /// milliseconds) HH = 00-23, MM = 00-59, SS = 00-60 (60 only if UTC leap
706        /// second), sss=000-999 (indicating milliseconds).
707        UtcTimeOnly,
708        /// string field representing Time/date combination represented in UTC
709        /// (Universal Time Coordinated, also known as "GMT") in either
710        /// YYYYMMDD-HH:MM:SS (whole seconds) or YYYYMMDD-HH:MM:SS.sss (milliseconds)
711        /// format, colons, dash, and period required. Valid values: * YYYY =
712        /// 0000-9999, MM = 01-12, DD = 01-31, HH = 00-23, MM = 00-59, SS = 00-60 (60
713        /// only if UTC leap second) (without milliseconds). * YYYY = 0000-9999, MM =
714        /// 01-12, DD = 01-31, HH = 00-23, MM = 00-59, SS = 00-60 (60 only if UTC
715        /// leap second), sss=000-999 (indicating milliseconds). Leap Seconds: Note
716        /// that UTC includes corrections for leap seconds, which are inserted to
717        /// account for slowing of the rotation of the earth. Leap second insertion
718        /// is declared by the International Earth Rotation Service (IERS) and has,
719        /// since 1972, only occurred on the night of Dec. 31 or Jun 30. The IERS
720        /// considers March 31 and September 30 as secondary dates for leap second
721        /// insertion, but has never utilized these dates. During a leap second
722        /// insertion, a UTCTimestamp field may read "19981231-23:59:59",
723        /// "19981231-23:59:60", "19990101-00:00:00". (see
724        /// <http://tycho.usno.navy.mil/leapsec.html>)
725        UtcTimestamp,
726        /// Contains an XML document raw data with no format or content restrictions.
727        /// XMLData fields are always immediately preceded by a length field. The
728        /// length field should specify the number of bytes of the value of the data
729        /// field (up to but not including the terminating SOH).
730        XmlData,
731        /// string field representing a country using ISO 3166 Country code (2
732        /// character) values (see Appendix 6-B).
733        Country,
734    }
735
736    impl FixDatatype {
737        /// Compares `name` to the set of strings commonly used by QuickFIX's custom
738        /// specification format and returns its associated
739        /// [`Datatype`](super::Datatype) if a match
740        /// was found. The query is case-insensitive.
741        ///
742        /// # Examples
743        ///
744        /// ```
745        /// use hotfix_dictionary::FixDatatype;
746        ///
747        /// assert_eq!(FixDatatype::from_quickfix_name("AMT"), Some(FixDatatype::Amt));
748        /// assert_eq!(FixDatatype::from_quickfix_name("Amt"), Some(FixDatatype::Amt));
749        /// assert_eq!(FixDatatype::from_quickfix_name("MONTHYEAR"), Some(FixDatatype::MonthYear));
750        /// assert_eq!(FixDatatype::from_quickfix_name(""), None);
751        /// ```
752        pub fn from_quickfix_name(name: &str) -> Option<Self> {
753            // https://github.com/quickfix/quickfix/blob/b6760f55ac6a46306b4e081bb13b65e6220ab02d/src/C%2B%2B/DataDictionary.cpp#L646-L680
754            Some(match name.to_ascii_uppercase().as_str() {
755                "AMT" => FixDatatype::Amt,
756                "BOOLEAN" => FixDatatype::Boolean,
757                "CHAR" => FixDatatype::Char,
758                "COUNTRY" => FixDatatype::Country,
759                "CURRENCY" => FixDatatype::Currency,
760                "DATA" => FixDatatype::Data,
761                "DATE" => FixDatatype::UtcDateOnly, // FIXME?
762                "DAYOFMONTH" => FixDatatype::DayOfMonth,
763                "EXCHANGE" => FixDatatype::Exchange,
764                "FLOAT" => FixDatatype::Float,
765                "INT" => FixDatatype::Int,
766                "LANGUAGE" => FixDatatype::Language,
767                "LENGTH" => FixDatatype::Length,
768                "LOCALMKTDATE" => FixDatatype::LocalMktDate,
769                "MONTHYEAR" => FixDatatype::MonthYear,
770                "MULTIPLECHARVALUE" | "MULTIPLEVALUESTRING" => FixDatatype::MultipleCharValue,
771                "MULTIPLESTRINGVALUE" => FixDatatype::MultipleStringValue,
772                "NUMINGROUP" => FixDatatype::NumInGroup,
773                "PERCENTAGE" => FixDatatype::Percentage,
774                "PRICE" => FixDatatype::Price,
775                "PRICEOFFSET" => FixDatatype::PriceOffset,
776                "QTY" => FixDatatype::Qty,
777                "STRING" => FixDatatype::String,
778                "TZTIMEONLY" => FixDatatype::UtcTimeOnly, // FIXME
779                "TZTIMESTAMP" => FixDatatype::UtcTimestamp, // FIXME
780                "UTCDATE" => FixDatatype::UtcDateOnly,
781                "UTCDATEONLY" => FixDatatype::UtcDateOnly,
782                "UTCTIMEONLY" => FixDatatype::UtcTimeOnly,
783                "UTCTIMESTAMP" => FixDatatype::UtcTimestamp,
784                "SEQNUM" => FixDatatype::SeqNum,
785                "TIME" => FixDatatype::UtcTimestamp,
786                "XMLDATA" => FixDatatype::XmlData,
787                _ => {
788                    return None;
789                }
790            })
791        }
792
793        /// Returns the name adopted by QuickFIX for `self`.
794        pub fn to_quickfix_name(&self) -> &str {
795            match self {
796                FixDatatype::Int => "INT",
797                FixDatatype::Length => "LENGTH",
798                FixDatatype::Char => "CHAR",
799                FixDatatype::Boolean => "BOOLEAN",
800                FixDatatype::Float => "FLOAT",
801                FixDatatype::Amt => "AMT",
802                FixDatatype::Price => "PRICE",
803                FixDatatype::PriceOffset => "PRICEOFFSET",
804                FixDatatype::Qty => "QTY",
805                FixDatatype::Percentage => "PERCENTAGE",
806                FixDatatype::DayOfMonth => "DAYOFMONTH",
807                FixDatatype::NumInGroup => "NUMINGROUP",
808                FixDatatype::Language => "LANGUAGE",
809                FixDatatype::SeqNum => "SEQNUM",
810                FixDatatype::TagNum => "TAGNUM",
811                FixDatatype::String => "STRING",
812                FixDatatype::Data => "DATA",
813                FixDatatype::MonthYear => "MONTHYEAR",
814                FixDatatype::Currency => "CURRENCY",
815                FixDatatype::Exchange => "EXCHANGE",
816                FixDatatype::LocalMktDate => "LOCALMKTDATE",
817                FixDatatype::MultipleStringValue => "MULTIPLESTRINGVALUE",
818                FixDatatype::UtcTimeOnly => "UTCTIMEONLY",
819                FixDatatype::UtcTimestamp => "UTCTIMESTAMP",
820                FixDatatype::UtcDateOnly => "UTCDATEONLY",
821                FixDatatype::Country => "COUNTRY",
822                FixDatatype::MultipleCharValue => "MULTIPLECHARVALUE",
823                FixDatatype::XmlData => "XMLDATA",
824            }
825        }
826
827        /// Returns the name of `self`, character by character identical to the name
828        /// that appears in the official guidelines. **Generally** primitive datatypes
829        /// will use `snake_case` and non-primitive ones will have `PascalCase`, but
830        /// that's not true for every [`Datatype`](super::Datatype).
831        ///
832        /// # Examples
833        ///
834        /// ```
835        /// use hotfix_dictionary::FixDatatype;
836        ///
837        /// assert_eq!(FixDatatype::Qty.name(), "Qty");
838        /// assert_eq!(FixDatatype::Float.name(), "float");
839        /// assert_eq!(FixDatatype::String.name(), "String");
840        /// ```
841        pub fn name(&self) -> &'static str {
842            // 1. Most primitive data types have `snake_case` names.
843            // 2. Most derivative data types have `PascalCase` names.
844            // 3. `data` and `String` ruin the party and mess it up.
845            //    Why, you ask? Oh, you sweet summer child. You'll learn soon enough
846            //    that nothing makes sense in FIX land.
847            match self {
848                FixDatatype::Int => "int",
849                FixDatatype::Length => "Length",
850                FixDatatype::Char => "char",
851                FixDatatype::Boolean => "Boolean",
852                FixDatatype::Float => "float",
853                FixDatatype::Amt => "Amt",
854                FixDatatype::Price => "Price",
855                FixDatatype::PriceOffset => "PriceOffset",
856                FixDatatype::Qty => "Qty",
857                FixDatatype::Percentage => "Percentage",
858                FixDatatype::DayOfMonth => "DayOfMonth",
859                FixDatatype::NumInGroup => "NumInGroup",
860                FixDatatype::Language => "Language",
861                FixDatatype::SeqNum => "SeqNum",
862                FixDatatype::TagNum => "TagNum",
863                FixDatatype::String => "String",
864                FixDatatype::Data => "data",
865                FixDatatype::MonthYear => "MonthYear",
866                FixDatatype::Currency => "Currency",
867                FixDatatype::Exchange => "Exchange",
868                FixDatatype::LocalMktDate => "LocalMktDate",
869                FixDatatype::MultipleStringValue => "MultipleStringValue",
870                FixDatatype::UtcTimeOnly => "UTCTimeOnly",
871                FixDatatype::UtcTimestamp => "UTCTimestamp",
872                FixDatatype::UtcDateOnly => "UTCDateOnly",
873                FixDatatype::Country => "Country",
874                FixDatatype::MultipleCharValue => "MultipleCharValue",
875                FixDatatype::XmlData => "XMLData",
876            }
877        }
878
879        /// Returns `true` if and only if `self` is a "base type", i.e. a primitive;
880        /// returns `false` otherwise.
881        ///
882        /// # Examples
883        ///
884        /// ```
885        /// use hotfix_dictionary::FixDatatype;
886        ///
887        /// assert_eq!(FixDatatype::Float.is_base_type(), true);
888        /// assert_eq!(FixDatatype::Price.is_base_type(), false);
889        /// ```
890        pub fn is_base_type(&self) -> bool {
891            matches!(self, Self::Char | Self::Float | Self::Int | Self::String)
892        }
893
894        /// Returns the primitive [`Datatype`](super::Datatype) from which `self` is derived. If
895        /// `self` is primitive already, returns `self` unchanged.
896        ///
897        /// # Examples
898        ///
899        /// ```
900        /// use hotfix_dictionary::FixDatatype;
901        ///
902        /// assert_eq!(FixDatatype::Float.base_type(), FixDatatype::Float);
903        /// assert_eq!(FixDatatype::Price.base_type(), FixDatatype::Float);
904        /// ```
905        pub fn base_type(&self) -> Self {
906            let dt = match self {
907                Self::Char | Self::Boolean => Self::Char,
908                Self::Float
909                | Self::Amt
910                | Self::Price
911                | Self::PriceOffset
912                | Self::Qty
913                | Self::Percentage => Self::Float,
914                Self::Int
915                | Self::DayOfMonth
916                | Self::Length
917                | Self::NumInGroup
918                | Self::SeqNum
919                | Self::TagNum => Self::Int,
920                _ => Self::String,
921            };
922            debug_assert!(dt.is_base_type());
923            dt
924        }
925
926        /// Returns an [`Iterator`] over all variants of
927        /// [`Datatype`](super::Datatype).
928        pub fn iter_all() -> impl Iterator<Item = Self> {
929            <Self as IntoEnumIterator>::iter()
930        }
931    }
932
933    #[cfg(test)]
934    mod test {
935        use super::*;
936        use std::collections::HashSet;
937
938        #[test]
939        fn iter_all_unique() {
940            let as_vec = FixDatatype::iter_all().collect::<Vec<FixDatatype>>();
941            let as_set = FixDatatype::iter_all().collect::<HashSet<FixDatatype>>();
942            assert_eq!(as_vec.len(), as_set.len());
943        }
944
945        #[test]
946        fn more_than_20_datatypes() {
947            // According to the official documentation, FIX has "about 20 data
948            // types". Including recent revisions, we should well exceed that
949            // number.
950            assert!(FixDatatype::iter_all().count() > 20);
951        }
952
953        #[test]
954        fn names_are_unique() {
955            let as_vec = FixDatatype::iter_all()
956                .map(|dt| dt.name())
957                .collect::<Vec<&str>>();
958            let as_set = FixDatatype::iter_all()
959                .map(|dt| dt.name())
960                .collect::<HashSet<&str>>();
961            assert_eq!(as_vec.len(), as_set.len());
962        }
963
964        #[test]
965        fn base_type_is_itself() {
966            for dt in FixDatatype::iter_all() {
967                if dt.is_base_type() {
968                    assert_eq!(dt.base_type(), dt);
969                } else {
970                    assert_ne!(dt.base_type(), dt);
971                }
972            }
973        }
974
975        #[test]
976        fn base_type_is_actually_base_type() {
977            for dt in FixDatatype::iter_all() {
978                assert!(dt.base_type().is_base_type());
979            }
980        }
981    }
982}
983
984/// A field is identified by a unique tag number and a name. Each field in a
985/// message is associated with a value.
986#[derive(Clone, Debug)]
987struct FieldData {
988    /// A human readable string representing the name of the field.
989    name: SmartString,
990    /// **Primary key.** A positive integer representing the unique
991    /// identifier for this field type.
992    tag: u32,
993    /// The datatype of the field.
994    data_type_name: SmartString,
995    /// The associated data field. If given, this field represents the length of
996    /// the referenced data field
997    associated_data_tag: Option<usize>,
998    value_restrictions: Option<Vec<FieldEnumData>>,
999    /// Indicates whether the field is required in an XML message.
1000    required: bool,
1001    description: Option<String>,
1002}
1003
1004#[derive(Clone, Debug)]
1005struct FieldEnumData {
1006    value: String,
1007    description: String,
1008}
1009
1010/// A limitation imposed on the value of a specific FIX [`Field`].  Also known as
1011/// "code set".
1012#[derive(Debug)]
1013#[allow(dead_code)]
1014pub struct FieldEnum<'a>(&'a Dictionary, &'a FieldEnumData);
1015
1016impl<'a> FieldEnum<'a> {
1017    /// Returns the string representation of this field variant.
1018    pub fn value(&self) -> &str {
1019        &self.1.value[..]
1020    }
1021
1022    /// Returns the documentation description for `self`.
1023    pub fn description(&self) -> &str {
1024        &self.1.description[..]
1025    }
1026}
1027
1028/// A field is the most granular message structure abstraction. It carries a
1029/// specific business meaning as described by the FIX specifications. The data
1030/// domain of a [`Field`] is either a [`Datatype`] or a "code set", i.e.
1031/// enumeration.
1032#[derive(Debug, Copy, Clone)]
1033pub struct Field<'a>(&'a Dictionary, &'a FieldData);
1034
1035impl<'a> Field<'a> {
1036    pub fn doc_url_onixs(&self, version: &str) -> String {
1037        let v = match version {
1038            "FIX.4.0" => "4.0",
1039            "FIX.4.1" => "4.1",
1040            "FIX.4.2" => "4.2",
1041            "FIX.4.3" => "4.3",
1042            "FIX.4.4" => "4.4",
1043            "FIX.5.0" => "5.0",
1044            "FIX.5.0SP1" => "5.0.SP1",
1045            "FIX.5.0SP2" => "5.0.SP2",
1046            "FIXT.1.1" => "FIXT.1.1",
1047            s => s,
1048        };
1049        format!(
1050            "https://www.onixs.biz/fix-dictionary/{}/tagNum_{}.html",
1051            v,
1052            self.1.tag.to_string().as_str()
1053        )
1054    }
1055
1056    pub fn is_num_in_group(&self) -> bool {
1057        fn nth_char_is_uppercase(s: &str, i: usize) -> bool {
1058            s.chars().nth(i).map(|c| c.is_ascii_uppercase()) == Some(true)
1059        }
1060
1061        self.fix_datatype().base_type() == FixDatatype::NumInGroup
1062            || self.name().ends_with("Len")
1063            || (self.name().starts_with("No") && nth_char_is_uppercase(self.name(), 2))
1064    }
1065
1066    /// Returns the [`FixDatatype`] of `self`.
1067    pub fn fix_datatype(&self) -> FixDatatype {
1068        self.data_type().basetype()
1069    }
1070
1071    /// Returns the name of `self`. Field names are unique across each FIX
1072    /// [`Dictionary`].
1073    pub fn name(&self) -> &str {
1074        self.1.name.as_str()
1075    }
1076
1077    /// Returns the numeric tag of `self`. Field tags are unique across each FIX
1078    /// [`Dictionary`].
1079    pub fn tag(&self) -> TagU32 {
1080        TagU32::new(self.1.tag).unwrap()
1081    }
1082
1083    /// In case this field allows any value, it returns `None`; otherwise; it
1084    /// returns an [`Iterator`] of all allowed values.
1085    pub fn enums(&self) -> Option<impl Iterator<Item = FieldEnum>> {
1086        self.1
1087            .value_restrictions
1088            .as_ref()
1089            .map(move |v| v.iter().map(move |f| FieldEnum(self.0, f)))
1090    }
1091
1092    /// Returns the [`Datatype`] of `self`.
1093    pub fn data_type(&self) -> Datatype {
1094        self.0
1095            .datatype_by_name(self.1.data_type_name.as_str())
1096            .unwrap()
1097    }
1098
1099    pub fn data_tag(&self) -> Option<TagU32> {
1100        self.1
1101            .associated_data_tag
1102            .map(|tag| TagU32::new(tag as u32).unwrap())
1103    }
1104
1105    pub fn required_in_xml_messages(&self) -> bool {
1106        self.1.required
1107    }
1108
1109    pub fn description(&self) -> Option<&str> {
1110        self.1.description.as_deref()
1111    }
1112}
1113
1114impl<'a> IsFieldDefinition for Field<'a> {
1115    fn name(&self) -> &str {
1116        self.1.name.as_str()
1117    }
1118
1119    fn tag(&self) -> TagU32 {
1120        TagU32::new(self.1.tag).expect("Invalid FIX tag (0)")
1121    }
1122
1123    fn location(&self) -> FieldLocation {
1124        FieldLocation::Body // FIXME
1125    }
1126}
1127
1128#[derive(Clone, Debug)]
1129#[allow(dead_code)]
1130enum LayoutItemKindData {
1131    Component {
1132        name: SmartString,
1133    },
1134    Group {
1135        len_field_tag: u32,
1136        items: Vec<LayoutItemData>,
1137    },
1138    Field {
1139        tag: u32,
1140    },
1141}
1142
1143#[derive(Clone, Debug)]
1144struct LayoutItemData {
1145    required: bool,
1146    kind: LayoutItemKindData,
1147}
1148
1149pub trait IsFieldDefinition {
1150    /// Returns the FIX tag associated with `self`.
1151    fn tag(&self) -> TagU32;
1152
1153    /// Returns the official, ASCII, human-readable name associated with `self`.
1154    fn name(&self) -> &str;
1155
1156    /// Returns the field location of `self`.
1157    fn location(&self) -> FieldLocation;
1158}
1159
1160fn layout_item_kind<'a>(item: &'a LayoutItemKindData, dict: &'a Dictionary) -> LayoutItemKind<'a> {
1161    match item {
1162        LayoutItemKindData::Component { name } => {
1163            LayoutItemKind::Component(dict.component_by_name(name).unwrap())
1164        }
1165        LayoutItemKindData::Group {
1166            len_field_tag,
1167            items: items_data,
1168        } => {
1169            let items = items_data
1170                .iter()
1171                .map(|item_data| LayoutItem(dict, item_data))
1172                .collect::<Vec<_>>();
1173            let len_field = dict.field_by_tag(*len_field_tag).unwrap();
1174            LayoutItemKind::Group(len_field, items)
1175        }
1176        LayoutItemKindData::Field { tag } => {
1177            LayoutItemKind::Field(dict.field_by_tag(*tag).unwrap())
1178        }
1179    }
1180}
1181
1182/// An entry in a sequence of FIX field definitions.
1183#[derive(Clone, Debug)]
1184pub struct LayoutItem<'a>(&'a Dictionary, &'a LayoutItemData);
1185
1186/// The kind of element contained in a [`Message`].
1187#[derive(Debug)]
1188pub enum LayoutItemKind<'a> {
1189    /// This component item is another component.
1190    Component(Component<'a>),
1191    /// This component item is a FIX repeating group.
1192    Group(Field<'a>, Vec<LayoutItem<'a>>),
1193    /// This component item is a FIX field.
1194    Field(Field<'a>),
1195}
1196
1197impl<'a> LayoutItem<'a> {
1198    /// Returns `true` if `self` is required in order to have a valid definition
1199    /// of its parent container, `false` otherwise.
1200    pub fn required(&self) -> bool {
1201        self.1.required
1202    }
1203
1204    /// Returns the [`LayoutItemKind`] of `self`.
1205    pub fn kind(&self) -> LayoutItemKind {
1206        layout_item_kind(&self.1.kind, self.0)
1207    }
1208
1209    /// Returns the human-readable name of `self`.
1210    pub fn tag_text(&self) -> String {
1211        match &self.1.kind {
1212            LayoutItemKindData::Component { name } => {
1213                self.0.component_by_name(name).unwrap().name().to_string()
1214            }
1215            LayoutItemKindData::Group {
1216                len_field_tag,
1217                items: _items,
1218            } => self
1219                .0
1220                .field_by_tag(*len_field_tag)
1221                .unwrap()
1222                .name()
1223                .to_string(),
1224            LayoutItemKindData::Field { tag } => {
1225                self.0.field_by_tag(*tag).unwrap().name().to_string()
1226            }
1227        }
1228    }
1229}
1230
1231type LayoutItems = Vec<LayoutItemData>;
1232
1233#[derive(Clone, Debug)]
1234struct MessageData {
1235    /// The unique integer identifier of this message type.
1236    component_id: u32,
1237    /// **Primary key**. The unique character identifier of this message
1238    /// type; used literally in FIX messages.
1239    msg_type: SmartString,
1240    /// The name of this message type.
1241    name: SmartString,
1242    layout_items: LayoutItems,
1243    /// A boolean used to indicate if the message is to be generated as part
1244    /// of FIXML.
1245    required: bool,
1246    description: String,
1247}
1248
1249/// A [`Message`] is a unit of information sent on the wire between
1250/// counterparties. Every [`Message`] is composed of fields and/or components.
1251#[derive(Debug)]
1252pub struct Message<'a>(&'a Dictionary, &'a MessageData);
1253
1254impl<'a> Message<'a> {
1255    /// Returns the human-readable name of `self`.
1256    pub fn name(&self) -> &str {
1257        self.1.name.as_str()
1258    }
1259
1260    /// Returns the message type of `self`.
1261    pub fn msg_type(&self) -> &str {
1262        self.1.msg_type.as_str()
1263    }
1264
1265    /// Returns the description associated with `self`.
1266    pub fn description(&self) -> &str {
1267        &self.1.description
1268    }
1269
1270    pub fn group_info(&self, num_in_group_tag: TagU32) -> Option<TagU32> {
1271        self.layout().find_map(|layout_item| {
1272            if let LayoutItemKind::Group(field, items) = layout_item.kind() {
1273                if field.tag() == num_in_group_tag {
1274                    if let LayoutItemKind::Field(f) = items[0].kind() {
1275                        Some(f.tag())
1276                    } else {
1277                        None
1278                    }
1279                } else {
1280                    None
1281                }
1282            } else if let LayoutItemKind::Component(_component) = layout_item.kind() {
1283                None
1284            } else {
1285                None
1286            }
1287        })
1288    }
1289
1290    /// Returns the component ID of `self`.
1291    pub fn component_id(&self) -> u32 {
1292        self.1.component_id
1293    }
1294
1295    pub fn layout(&self) -> impl Iterator<Item = LayoutItem> {
1296        self.1
1297            .layout_items
1298            .iter()
1299            .map(move |data| LayoutItem(self.0, data))
1300    }
1301
1302    pub fn fixml_required(&self) -> bool {
1303        self.1.required
1304    }
1305}
1306
1307/// A [`Section`] is a collection of many [`Component`]-s. It has no practical
1308/// effect on encoding and decoding of FIX data and it's only used for
1309/// documentation and human readability.
1310#[derive(Clone, Debug, PartialEq)]
1311pub struct Section {}
1312
1313#[cfg(test)]
1314mod test {
1315    use super::*;
1316    use std::collections::HashSet;
1317
1318    #[test]
1319    fn fix44_quickfix_is_ok() {
1320        let dict = Dictionary::fix44();
1321        let msg_heartbeat = dict.message_by_name("Heartbeat").unwrap();
1322        assert_eq!(msg_heartbeat.msg_type(), "0");
1323        assert_eq!(msg_heartbeat.name(), "Heartbeat".to_string());
1324        assert!(msg_heartbeat.layout().any(|c| {
1325            if let LayoutItemKind::Field(f) = c.kind() {
1326                f.name() == "TestReqID"
1327            } else {
1328                false
1329            }
1330        }));
1331    }
1332
1333    #[test]
1334    fn all_datatypes_are_used_at_least_once() {
1335        for dict in Dictionary::common_dictionaries().iter() {
1336            let datatypes_count = dict.datatypes().len();
1337            let mut datatypes = HashSet::new();
1338            for field in dict.fields() {
1339                datatypes.insert(field.data_type().name().to_string());
1340            }
1341            assert_eq!(datatypes_count, datatypes.len());
1342        }
1343    }
1344
1345    #[test]
1346    fn at_least_one_datatype() {
1347        for dict in Dictionary::common_dictionaries().iter() {
1348            assert!(!dict.datatypes().is_empty());
1349        }
1350    }
1351
1352    #[test]
1353    fn std_header_and_trailer_always_present() {
1354        for dict in Dictionary::common_dictionaries().iter() {
1355            let std_header = dict.component_by_name("StandardHeader");
1356            let std_trailer = dict.component_by_name("StandardTrailer");
1357            assert!(std_header.is_some() && std_trailer.is_some());
1358        }
1359    }
1360
1361    #[test]
1362    fn fix44_field_28_has_three_variants() {
1363        let dict = Dictionary::fix44();
1364        let field_28 = dict.field_by_tag(28).unwrap();
1365        assert_eq!(field_28.name(), "IOITransType");
1366        assert_eq!(field_28.enums().unwrap().count(), 3);
1367    }
1368
1369    #[test]
1370    fn fix44_field_36_has_no_variants() {
1371        let dict = Dictionary::fix44();
1372        let field_36 = dict.field_by_tag(36).unwrap();
1373        assert_eq!(field_36.name(), "NewSeqNo");
1374        assert!(field_36.enums().is_none());
1375    }
1376
1377    #[test]
1378    fn fix44_field_167_has_eucorp_variant() {
1379        let dict = Dictionary::fix44();
1380        let field_167 = dict.field_by_tag(167).unwrap();
1381        assert_eq!(field_167.name(), "SecurityType");
1382        assert!(field_167.enums().unwrap().any(|e| e.value() == "EUCORP"));
1383    }
1384
1385    const INVALID_QUICKFIX_SPECS: &[&str] = &[
1386        include_str!("test_data/quickfix_specs/empty_file.xml"),
1387        include_str!("test_data/quickfix_specs/missing_components.xml"),
1388        include_str!("test_data/quickfix_specs/missing_fields.xml"),
1389        include_str!("test_data/quickfix_specs/missing_header.xml"),
1390        include_str!("test_data/quickfix_specs/missing_messages.xml"),
1391        include_str!("test_data/quickfix_specs/missing_trailer.xml"),
1392        include_str!("test_data/quickfix_specs/root_has_no_type_attr.xml"),
1393        include_str!("test_data/quickfix_specs/root_has_no_version_attrs.xml"),
1394        include_str!("test_data/quickfix_specs/root_is_not_fix.xml"),
1395    ];
1396
1397    #[test]
1398    fn invalid_quickfix_specs() {
1399        for spec in INVALID_QUICKFIX_SPECS.iter() {
1400            let dict = Dictionary::from_quickfix_spec(spec);
1401            assert!(dict.is_err(), "{}", spec);
1402        }
1403    }
1404}