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