rustyfix_dictionary/
lib.rs

1//! Access to FIX Dictionary reference and message specifications.
2#![cfg_attr(doc_cfg, feature(doc_cfg))]
3#![allow(unexpected_cfgs)]
4
5pub mod builder;
6mod fix_datatype;
7mod quickfix;
8
9use builder::{
10    AbbreviationData, CategoryData, ComponentData, DatatypeData, FieldData, FieldEnumData,
11    LayoutItemData, LayoutItemKindData, MessageData,
12};
13pub use fix_datatype::FixDatatype;
14use fnv::FnvHashMap;
15use quickfix::{ParseDictionaryError, QuickFixReader};
16use smartstring::alias::String as SmartString;
17use std::sync::Arc;
18
19/// Type alias for FIX tags: 32-bit unsigned integers, strictly positive.
20pub type TagU32 = std::num::NonZeroU32;
21
22pub trait DataFieldLookup<F> {
23    fn field_is_data(&self, field: F) -> bool;
24}
25
26pub trait NumInGroupLookup<F> {
27    fn field_is_num_in_group(&self, field: F) -> bool;
28}
29
30impl DataFieldLookup<u32> for Dictionary {
31    fn field_is_data(&self, tag: u32) -> bool {
32        if let Some(field) = self.field_by_tag(tag) {
33            field.data_type().basetype() == FixDatatype::Data
34        } else {
35            false
36        }
37    }
38}
39
40impl NumInGroupLookup<u32> for Dictionary {
41    fn field_is_num_in_group(&self, tag: u32) -> bool {
42        if let Some(field) = self.field_by_tag(tag) {
43            field.data_type().basetype() == FixDatatype::NumInGroup
44        } else {
45            false
46        }
47    }
48}
49
50/// The expected location of a field within a FIX message (i.e. header, body, or
51/// trailer).
52#[derive(Debug, Copy, Clone, PartialEq, Eq)]
53pub enum FieldLocation {
54    /// The field is located inside the "Standard Header".
55    Header,
56    /// This field is located inside the body of the FIX message.
57    Body,
58    /// This field is located inside the "Standard Trailer".
59    Trailer,
60}
61
62/// A mapping from FIX version strings to [`Dictionary`] values.
63pub type Dictionaries = FnvHashMap<String, Arc<Dictionary>>;
64
65/// Specifies business semantics for application-level entities within the FIX
66/// Protocol.
67///
68/// You can rely on [`Dictionary`] for accessing details about
69/// fields, messages, and other abstract entities as defined in the FIX
70/// specifications. Examples of such information include:
71///
72/// - The mapping of FIX field names to numeric tags (e.g. `BeginString` is 8).
73/// - Which FIX fields are mandatory and which are optional.
74/// - The data type of each and every FIX field.
75/// - What fields to expect in FIX headers.
76///
77/// N.B. The FIX Protocol mandates separation of concerns between session and
78/// application protocol only for FIX 5.0 and subsequent versions. All FIX
79/// Dictionaries for older versions will also contain information about the
80/// session layer.
81#[derive(Debug, Clone)]
82pub struct Dictionary {
83    version: String,
84
85    abbreviation_definitions: FnvHashMap<SmartString, AbbreviationData>,
86
87    data_types_by_name: FnvHashMap<SmartString, DatatypeData>,
88
89    fields_by_tags: FnvHashMap<u32, FieldData>,
90    field_tags_by_name: FnvHashMap<SmartString, u32>,
91
92    components_by_name: FnvHashMap<SmartString, ComponentData>,
93
94    messages_by_msgtype: FnvHashMap<SmartString, MessageData>,
95    message_msgtypes_by_name: FnvHashMap<SmartString, SmartString>,
96
97    //layout_items: Vec<LayoutItemData>,
98    categories_by_name: FnvHashMap<SmartString, CategoryData>,
99    // header: Vec<FieldData>,
100}
101
102impl Dictionary {
103    /// Creates a new empty FIX Dictionary named `version`.
104    fn new<S: ToString>(version: S) -> Self {
105        Dictionary {
106            // header: Vec::new(), // FIXME
107            version: version.to_string(),
108            abbreviation_definitions: FnvHashMap::default(),
109            data_types_by_name: FnvHashMap::default(),
110            fields_by_tags: FnvHashMap::default(),
111            field_tags_by_name: FnvHashMap::default(),
112            components_by_name: FnvHashMap::default(),
113            messages_by_msgtype: FnvHashMap::default(),
114            message_msgtypes_by_name: FnvHashMap::default(),
115            categories_by_name: FnvHashMap::default(),
116        }
117    }
118
119    /// Attempts to read a QuickFIX-style specification file and convert it into
120    /// a [`Dictionary`].
121    pub fn from_quickfix_spec(input: &str) -> Result<Self, ParseDictionaryError> {
122        let xml_document =
123            roxmltree::Document::parse(input).map_err(|_| ParseDictionaryError::InvalidFormat)?;
124        QuickFixReader::from_xml(&xml_document)
125    }
126
127    /// Returns the version string associated with this [`Dictionary`] (e.g.
128    /// `FIXT.1.1`, `FIX.4.2`).
129    ///
130    /// ```
131    /// use rustyfix_dictionary::Dictionary;
132    ///
133    /// let dict = Dictionary::fix44();
134    /// assert_eq!(dict.version(), "FIX.4.4");
135    /// ```
136    pub fn version(&self) -> &str {
137        self.version.as_str()
138    }
139
140    /// Creates a new [`Dictionary`] for FIX 4.0.
141    #[cfg(feature = "fix40")]
142    pub fn fix40() -> Self {
143        let spec = include_str!("resources/quickfix/FIX-4.0.xml");
144        Dictionary::from_quickfix_spec(spec).unwrap()
145    }
146
147    /// Creates a new [`Dictionary`] for FIX 4.1.
148    #[cfg(feature = "fix41")]
149    pub fn fix41() -> Self {
150        let spec = include_str!("resources/quickfix/FIX-4.1.xml");
151        Dictionary::from_quickfix_spec(spec).unwrap()
152    }
153
154    /// Creates a new [`Dictionary`] for FIX 4.2.
155    #[cfg(feature = "fix42")]
156    pub fn fix42() -> Self {
157        let spec = include_str!("resources/quickfix/FIX-4.2.xml");
158        Dictionary::from_quickfix_spec(spec).unwrap()
159    }
160
161    /// Creates a new [`Dictionary`] for FIX 4.3.
162    #[cfg(feature = "fix43")]
163    pub fn fix43() -> Self {
164        let spec = include_str!("resources/quickfix/FIX-4.3.xml");
165        Dictionary::from_quickfix_spec(spec).unwrap()
166    }
167
168    /// Creates a new [`Dictionary`] for FIX 4.4.
169    pub fn fix44() -> Self {
170        let spec = include_str!("resources/quickfix/FIX-4.4.xml");
171        Dictionary::from_quickfix_spec(spec).unwrap()
172    }
173
174    /// Creates a new [`Dictionary`] for FIX 5.0.
175    #[cfg(feature = "fix50")]
176    pub fn fix50() -> Self {
177        let spec = include_str!("resources/quickfix/FIX-5.0.xml");
178        Dictionary::from_quickfix_spec(spec).unwrap()
179    }
180
181    /// Creates a new [`Dictionary`] for FIX 5.0 SP1.
182    #[cfg(feature = "fix50sp1")]
183    pub fn fix50sp1() -> Self {
184        let spec = include_str!("resources/quickfix/FIX-5.0-SP1.xml");
185        Dictionary::from_quickfix_spec(spec).unwrap()
186    }
187
188    /// Creates a new [`Dictionary`] for FIX 5.0 SP2.
189    #[cfg(feature = "fix50sp2")]
190    pub fn fix50sp2() -> Self {
191        let spec = include_str!("resources/quickfix/FIX-5.0-SP2.xml");
192        Dictionary::from_quickfix_spec(spec).unwrap()
193    }
194
195    /// Creates a new [`Dictionary`] for FIXT 1.1.
196    #[cfg(feature = "fixt11")]
197    pub fn fixt11() -> Self {
198        let spec = include_str!("resources/quickfix/FIXT-1.1.xml");
199        Dictionary::from_quickfix_spec(spec).unwrap()
200    }
201
202    /// Returns a [`Vec`] of FIX [`Dictionary`]'s for the most common FIX
203    /// versions (all that have been enabled via feature flags). This is only
204    /// intended for testing purposes.
205    pub fn common_dictionaries() -> Vec<Dictionary> {
206        vec![
207            #[cfg(feature = "fix40")]
208            Self::fix40(),
209            #[cfg(feature = "fix41")]
210            Self::fix41(),
211            #[cfg(feature = "fix42")]
212            Self::fix42(),
213            #[cfg(feature = "fix43")]
214            Self::fix43(),
215            #[cfg(feature = "fix44")]
216            Self::fix44(),
217            #[cfg(feature = "fix50")]
218            Self::fix50(),
219            #[cfg(feature = "fix50sp1")]
220            Self::fix50sp1(),
221            #[cfg(feature = "fix50sp2")]
222            Self::fix50sp2(),
223            #[cfg(feature = "fixt11")]
224            Self::fixt11(),
225        ]
226    }
227
228    /// Return the known abbreviation for `term` -if any- according to the
229    /// documentation of this FIX Dictionary.
230    pub fn abbreviation_for(&self, term: &str) -> Option<Abbreviation> {
231        self.abbreviation_definitions.get(term).map(Abbreviation)
232    }
233
234    /// Returns the [`Message`](Message) associated with `name`, if any.
235    ///
236    /// ```
237    /// use rustyfix_dictionary::Dictionary;
238    ///
239    /// let dict = Dictionary::fix44();
240    ///
241    /// let msg1 = dict.message_by_name("Heartbeat").unwrap();
242    /// let msg2 = dict.message_by_msgtype("0").unwrap();
243    /// assert_eq!(msg1.name(), msg2.name());
244    /// ```
245    pub fn message_by_name(&self, name: &str) -> Option<Message> {
246        let msg_type = self.message_msgtypes_by_name.get(name)?;
247        self.message_by_msgtype(msg_type)
248    }
249
250    /// Returns the [`Message`](Message) that has the given `msgtype`, if any.
251    ///
252    /// ```
253    /// use rustyfix_dictionary::Dictionary;
254    ///
255    /// let dict = Dictionary::fix44();
256    ///
257    /// let msg1 = dict.message_by_msgtype("0").unwrap();
258    /// let msg2 = dict.message_by_name("Heartbeat").unwrap();
259    /// assert_eq!(msg1.name(), msg2.name());
260    /// ```
261    pub fn message_by_msgtype(&self, msgtype: &str) -> Option<Message> {
262        self.messages_by_msgtype
263            .get(msgtype)
264            .map(|data| Message(self, data))
265    }
266
267    /// Returns the [`Component`] named `name`, if any.
268    pub fn component_by_name(&self, name: &str) -> Option<Component> {
269        self.components_by_name
270            .get(name)
271            .map(|data| Component(data, self))
272    }
273
274    /// Returns the [`Datatype`] named `name`, if any.
275    ///
276    /// ```
277    /// use rustyfix_dictionary::Dictionary;
278    ///
279    /// let dict = Dictionary::fix44();
280    /// let dt = dict.datatype_by_name("String").unwrap();
281    /// assert_eq!(dt.name(), "String");
282    /// ```
283    pub fn datatype_by_name(&self, name: &str) -> Option<Datatype> {
284        self.data_types_by_name.get(name).map(Datatype)
285    }
286
287    /// Returns the [`Field`] associated with `tag`, if any.
288    ///
289    /// ```
290    /// use rustyfix_dictionary::Dictionary;
291    ///
292    /// let dict = Dictionary::fix44();
293    /// let field1 = dict.field_by_tag(112).unwrap();
294    /// let field2 = dict.field_by_name("TestReqID").unwrap();
295    /// assert_eq!(field1.name(), field2.name());
296    /// ```
297    pub fn field_by_tag(&self, tag: u32) -> Option<Field> {
298        self.fields_by_tags.get(&tag).map(|data| Field(self, data))
299    }
300
301    /// Returns the [`Field`] named `name`, if any.
302    pub fn field_by_name(&self, name: &str) -> Option<Field> {
303        let tag = self.field_tags_by_name.get(name)?;
304        self.field_by_tag(*tag)
305    }
306
307    /// Returns the [`Category`] named `name`, if any.
308    fn category_by_name(&self, name: &str) -> Option<Category> {
309        self.categories_by_name.get(name).map(Category)
310    }
311
312    /// Returns a [`Vec`] of all [`Datatype`]'s in this [`Dictionary`]. The ordering
313    /// of items is not specified.
314    ///
315    /// ```
316    /// use rustyfix_dictionary::Dictionary;
317    ///
318    /// let dict = Dictionary::fix44();
319    /// // FIX 4.4 defines 23 (FIXME) datatypes.
320    /// assert_eq!(dict.datatypes().len(), 23);
321    /// ```
322    pub fn datatypes(&self) -> Vec<Datatype> {
323        self.data_types_by_name.values().map(Datatype).collect()
324    }
325
326    /// Returns a [`Vec`] of all [`Message`]'s in this [`Dictionary`]. The ordering
327    /// of items is not specified.
328    ///
329    /// ```
330    /// use rustyfix_dictionary::Dictionary;
331    ///
332    /// let dict = Dictionary::fix44();
333    /// let msgs = dict.messages();
334    /// let msg = msgs.iter().find(|m| m.name() == "MarketDataRequest");
335    /// assert_eq!(msg.unwrap().msg_type(), "V");
336    /// ```
337    pub fn messages(&self) -> Vec<Message> {
338        self.messages_by_msgtype
339            .values()
340            .map(|data| Message(self, data))
341            .collect()
342    }
343
344    /// Returns a [`Vec`] of all [`Category`]'s in this [`Dictionary`]. The ordering
345    /// of items is not specified.
346    pub fn categories(&self) -> Vec<Category> {
347        self.categories_by_name.values().map(Category).collect()
348    }
349
350    /// Returns a [`Vec`] of all [`Field`]'s in this [`Dictionary`]. The ordering
351    /// of items is not specified.
352    pub fn fields(&self) -> Vec<Field> {
353        self.fields_by_tags
354            .values()
355            .map(|data| Field(self, data))
356            .collect()
357    }
358
359    /// Returns a [`Vec`] of all [`Component`]'s in this [`Dictionary`]. The ordering
360    /// of items is not specified.
361    pub fn components(&self) -> Vec<Component> {
362        self.components_by_name
363            .values()
364            .map(|data| Component(data, self))
365            .collect()
366    }
367}
368
369/// An [`Abbreviation`] is a standardized abbreviated form for a specific word,
370/// pattern, or name. Abbreviation data is mostly meant for documentation
371/// purposes, but in general it can have other uses as well, e.g. FIXML field
372/// naming.
373#[derive(Debug)]
374pub struct Abbreviation<'a>(&'a AbbreviationData);
375
376impl<'a> Abbreviation<'a> {
377    /// Returns the full term (non-abbreviated) associated with `self`.
378    pub fn term(&self) -> &str {
379        self.0.abbreviation.as_str()
380    }
381}
382
383/// A [`Category`] is a collection of loosely related FIX messages or components
384/// all belonging to the same [`Section`].
385#[derive(Clone, Debug)]
386pub struct Category<'a>(&'a CategoryData);
387
388impl<'a> Category<'a> {
389    /// Returns the name of `self`. The name of every [`Category`] is unique
390    /// across a [`Dictionary`].
391    pub fn name(&self) -> &str {
392        self.0.name.as_str()
393    }
394}
395
396/// A [`Component`] is an ordered collection of fields and/or other components.
397/// There are two kinds of components: (1) common blocks and (2) repeating
398/// groups. Common blocks are merely commonly reused sequences of the same
399/// fields/components
400/// which are given names for simplicity, i.e. they serve as "macros". Repeating
401/// groups, on the other hand, are components which can appear zero or more times
402/// inside FIX messages (or other components, for that matter).
403#[derive(Clone, Debug)]
404pub struct Component<'a>(&'a ComponentData, &'a Dictionary);
405
406impl<'a> Component<'a> {
407    /// Returns the unique numberic ID of `self`.
408    pub fn id(&self) -> u32 {
409        self.0.id as u32
410    }
411
412    /// Returns the name of `self`. The name of every [`Component`] is unique
413    /// across a [`Dictionary`].
414    pub fn name(&self) -> &str {
415        self.0.name.as_str()
416    }
417
418    /// Returns `true` if and only if `self` is a "group" component; `false`
419    /// otherwise.
420    pub fn is_group(&self) -> bool {
421        match self.0.component_type {
422            FixmlComponentAttributes::Block { is_repeating, .. } => is_repeating,
423            _ => false,
424        }
425    }
426
427    /// Returns the [`Category`] to which `self` belongs.
428    pub fn category(&self) -> Category {
429        self.1
430            .category_by_name(self.0.category_name.as_str())
431            .unwrap()
432    }
433
434    /// Returns an [`Iterator`] over all items that are part of `self`.
435    pub fn items(&self) -> impl Iterator<Item = LayoutItem> {
436        self.0
437            .layout_items
438            .iter()
439            .map(move |data| LayoutItem(self.1, data))
440    }
441
442    /// Checks whether `field` appears in the definition of `self` and returns
443    /// `true` if it does, `false` otherwise.
444    pub fn contains_field(&self, field: &Field) -> bool {
445        self.items().any(|layout_item| {
446            if let LayoutItemKind::Field(f) = layout_item.kind() {
447                f.tag() == field.tag()
448            } else {
449                false
450            }
451        })
452    }
453}
454
455/// Component type (FIXML-specific information).
456#[derive(Clone, Debug, PartialEq)]
457#[allow(dead_code)]
458pub enum FixmlComponentAttributes {
459    Xml,
460    Block {
461        is_repeating: bool,
462        is_implicit: bool,
463        is_optimized: bool,
464    },
465    Message,
466}
467
468/// A FIX data type defined as part of a [`Dictionary`].
469#[derive(Debug)]
470pub struct Datatype<'a>(&'a DatatypeData);
471
472impl<'a> Datatype<'a> {
473    /// Returns the name of `self`.  This is also guaranteed to be a valid Rust
474    /// identifier.
475    pub fn name(&self) -> &str {
476        self.0.datatype.name()
477    }
478
479    /// Returns `self` as an `enum`.
480    pub fn basetype(&self) -> FixDatatype {
481        self.0.datatype
482    }
483}
484
485/// A limitation imposed on the value of a specific FIX [`Field`].  Also known as
486/// "code set".
487#[derive(Debug)]
488pub struct FieldEnum<'a>(&'a FieldEnumData);
489
490impl<'a> FieldEnum<'a> {
491    /// Returns the string representation of this field variant.
492    pub fn value(&self) -> &str {
493        &self.0.value[..]
494    }
495
496    /// Returns the documentation description for `self`.
497    pub fn description(&self) -> &str {
498        &self.0.description[..]
499    }
500}
501
502/// A field is the most granular message structure abstraction. It carries a
503/// specific business meaning as described by the FIX specifications. The data
504/// domain of a [`Field`] is either a [`Datatype`] or a "code set", i.e.
505/// enumeration.
506#[derive(Debug, Copy, Clone)]
507pub struct Field<'a>(&'a Dictionary, &'a FieldData);
508
509impl<'a> Field<'a> {
510    pub fn doc_url_onixs(&self, version: &str) -> String {
511        let v = match version {
512            "FIX.4.0" => "4.0",
513            "FIX.4.1" => "4.1",
514            "FIX.4.2" => "4.2",
515            "FIX.4.3" => "4.3",
516            "FIX.4.4" => "4.4",
517            "FIX.5.0" => "5.0",
518            "FIX.5.0SP1" => "5.0.SP1",
519            "FIX.5.0SP2" => "5.0.SP2",
520            "FIXT.1.1" => "FIXT.1.1",
521            s => s,
522        };
523        format!(
524            "https://www.onixs.biz/fix-dictionary/{}/tagNum_{}.html",
525            v,
526            self.1.tag.to_string().as_str()
527        )
528    }
529
530    pub fn is_num_in_group(&self) -> bool {
531        fn nth_char_is_uppercase(s: &str, i: usize) -> bool {
532            s.chars().nth(i).map(|c| c.is_ascii_uppercase()) == Some(true)
533        }
534
535        self.fix_datatype().base_type() == FixDatatype::NumInGroup
536            || self.name().ends_with("Len")
537            || (self.name().starts_with("No") && nth_char_is_uppercase(self.name(), 2))
538    }
539
540    /// Returns the [`FixDatatype`] of `self`.
541    pub fn fix_datatype(&self) -> FixDatatype {
542        self.data_type().basetype()
543    }
544
545    /// Returns the name of `self`. Field names are unique across each FIX
546    /// [`Dictionary`].
547    pub fn name(&self) -> &str {
548        self.1.name.as_str()
549    }
550
551    /// Returns the numeric tag of `self`. Field tags are unique across each FIX
552    /// [`Dictionary`].
553    pub fn tag(&self) -> TagU32 {
554        TagU32::new(self.1.tag).unwrap()
555    }
556
557    /// In case this field allows any value, it returns `None`; otherwise; it
558    /// returns an [`Iterator`] of all allowed values.
559    pub fn enums(&self) -> Option<impl Iterator<Item = FieldEnum>> {
560        self.1
561            .value_restrictions
562            .as_ref()
563            .map(move |v| v.iter().map(FieldEnum))
564    }
565
566    /// Returns the [`Datatype`] of `self`.
567    pub fn data_type(&self) -> Datatype {
568        self.0
569            .datatype_by_name(self.1.data_type_name.as_str())
570            .unwrap()
571    }
572
573    pub fn data_tag(&self) -> Option<TagU32> {
574        self.1
575            .associated_data_tag
576            .map(|tag| TagU32::new(tag as u32).unwrap())
577    }
578
579    pub fn required_in_xml_messages(&self) -> bool {
580        self.1.required
581    }
582
583    pub fn description(&self) -> Option<&str> {
584        self.1.description.as_deref()
585    }
586}
587
588impl<'a> IsFieldDefinition for Field<'a> {
589    fn name(&self) -> &str {
590        self.1.name.as_str()
591    }
592
593    fn tag(&self) -> TagU32 {
594        TagU32::new(self.1.tag).expect("Invalid FIX tag (0)")
595    }
596
597    fn location(&self) -> FieldLocation {
598        FieldLocation::Body // FIXME
599    }
600}
601
602pub trait IsFieldDefinition {
603    /// Returns the FIX tag associated with `self`.
604    fn tag(&self) -> TagU32;
605
606    /// Returns the official, ASCII, human-readable name associated with `self`.
607    fn name(&self) -> &str;
608
609    /// Returns the field location of `self`.
610    fn location(&self) -> FieldLocation;
611}
612
613fn layout_item_kind<'a>(item: &'a LayoutItemKindData, dict: &'a Dictionary) -> LayoutItemKind<'a> {
614    match item {
615        LayoutItemKindData::Component { name } => {
616            LayoutItemKind::Component(dict.component_by_name(name).unwrap())
617        }
618        LayoutItemKindData::Group {
619            len_field_tag,
620            items: items_data,
621        } => {
622            let items = items_data
623                .iter()
624                .map(|item_data| LayoutItem(dict, item_data))
625                .collect::<Vec<_>>();
626            let len_field = dict.field_by_tag(*len_field_tag).unwrap();
627            LayoutItemKind::Group(len_field, items)
628        }
629        LayoutItemKindData::Field { tag } => {
630            LayoutItemKind::Field(dict.field_by_tag(*tag).unwrap())
631        }
632    }
633}
634
635/// An entry in a sequence of FIX field definitions.
636#[derive(Clone, Debug)]
637pub struct LayoutItem<'a>(&'a Dictionary, &'a LayoutItemData);
638
639/// The kind of element contained in a [`Message`].
640#[derive(Debug)]
641pub enum LayoutItemKind<'a> {
642    /// This component item is another component.
643    Component(Component<'a>),
644    /// This component item is a FIX repeating group.
645    Group(Field<'a>, Vec<LayoutItem<'a>>),
646    /// This component item is a FIX field.
647    Field(Field<'a>),
648}
649
650impl<'a> LayoutItem<'a> {
651    /// Returns `true` if `self` is required in order to have a valid definition
652    /// of its parent container, `false` otherwise.
653    pub fn required(&self) -> bool {
654        self.1.required
655    }
656
657    /// Returns the [`LayoutItemKind`] of `self`.
658    pub fn kind(&self) -> LayoutItemKind {
659        layout_item_kind(&self.1.kind, self.0)
660    }
661
662    /// Returns the human-readable name of `self`.
663    pub fn tag_text(&self) -> String {
664        match &self.1.kind {
665            LayoutItemKindData::Component { name } => {
666                self.0.component_by_name(name).unwrap().name().to_string()
667            }
668            LayoutItemKindData::Group {
669                len_field_tag,
670                items: _items,
671            } => self
672                .0
673                .field_by_tag(*len_field_tag)
674                .unwrap()
675                .name()
676                .to_string(),
677            LayoutItemKindData::Field { tag } => {
678                self.0.field_by_tag(*tag).unwrap().name().to_string()
679            }
680        }
681    }
682}
683
684/// A [`Message`] is a unit of information sent on the wire between
685/// counterparties. Every [`Message`] is composed of fields and/or components.
686#[derive(Debug)]
687pub struct Message<'a>(&'a Dictionary, &'a MessageData);
688
689impl<'a> Message<'a> {
690    /// Returns the human-readable name of `self`.
691    pub fn name(&self) -> &str {
692        self.1.name.as_str()
693    }
694
695    /// Returns the message type of `self`.
696    pub fn msg_type(&self) -> &str {
697        self.1.msg_type.as_str()
698    }
699
700    /// Returns the description associated with `self`.
701    pub fn description(&self) -> &str {
702        &self.1.description
703    }
704
705    pub fn group_info(&self, num_in_group_tag: TagU32) -> Option<TagU32> {
706        self.layout().find_map(|layout_item| {
707            if let LayoutItemKind::Group(field, items) = layout_item.kind() {
708                if field.tag() == num_in_group_tag {
709                    if let LayoutItemKind::Field(f) = items[0].kind() {
710                        Some(f.tag())
711                    } else {
712                        None
713                    }
714                } else {
715                    None
716                }
717            } else if let LayoutItemKind::Component(_component) = layout_item.kind() {
718                None
719            } else {
720                None
721            }
722        })
723    }
724
725    /// Returns the component ID of `self`.
726    pub fn component_id(&self) -> u32 {
727        self.1.component_id
728    }
729
730    pub fn layout(&self) -> impl Iterator<Item = LayoutItem> {
731        self.1
732            .layout_items
733            .iter()
734            .map(move |data| LayoutItem(self.0, data))
735    }
736
737    pub fn fixml_required(&self) -> bool {
738        self.1.required
739    }
740}
741
742/// A [`Section`] is a collection of many [`Component`]-s. It has no practical
743/// effect on encoding and decoding of FIX data and it's only used for
744/// documentation and human readability.
745#[derive(Clone, Debug, PartialEq)]
746pub struct Section {}
747
748#[cfg(test)]
749mod test {
750    use super::*;
751    use std::collections::HashSet;
752
753    #[test]
754    fn fix44_quickfix_is_ok() {
755        let dict = Dictionary::fix44();
756        let msg_heartbeat = dict.message_by_name("Heartbeat").unwrap();
757        assert_eq!(msg_heartbeat.msg_type(), "0");
758        assert_eq!(msg_heartbeat.name(), "Heartbeat".to_string());
759        assert!(msg_heartbeat.layout().any(|c| {
760            if let LayoutItemKind::Field(f) = c.kind() {
761                f.name() == "TestReqID"
762            } else {
763                false
764            }
765        }));
766    }
767
768    #[test]
769    fn all_datatypes_are_used_at_least_once() {
770        for dict in Dictionary::common_dictionaries().iter() {
771            let datatypes_count = dict.datatypes().len();
772            let mut datatypes = HashSet::new();
773            for field in dict.fields() {
774                datatypes.insert(field.data_type().name().to_string());
775            }
776            assert_eq!(datatypes_count, datatypes.len());
777        }
778    }
779
780    #[test]
781    fn at_least_one_datatype() {
782        for dict in Dictionary::common_dictionaries().iter() {
783            assert!(!dict.datatypes().is_empty());
784        }
785    }
786
787    #[test]
788    fn std_header_and_trailer_always_present() {
789        for dict in Dictionary::common_dictionaries().iter() {
790            let std_header = dict.component_by_name("StandardHeader");
791            let std_trailer = dict.component_by_name("StandardTrailer");
792            assert!(std_header.is_some() && std_trailer.is_some());
793        }
794    }
795
796    #[test]
797    fn fix44_field_28_has_three_variants() {
798        let dict = Dictionary::fix44();
799        let field_28 = dict.field_by_tag(28).unwrap();
800        assert_eq!(field_28.name(), "IOITransType");
801        assert_eq!(field_28.enums().unwrap().count(), 3);
802    }
803
804    #[test]
805    fn fix44_field_36_has_no_variants() {
806        let dict = Dictionary::fix44();
807        let field_36 = dict.field_by_tag(36).unwrap();
808        assert_eq!(field_36.name(), "NewSeqNo");
809        assert!(field_36.enums().is_none());
810    }
811
812    #[test]
813    fn fix44_field_167_has_eucorp_variant() {
814        let dict = Dictionary::fix44();
815        let field_167 = dict.field_by_tag(167).unwrap();
816        assert_eq!(field_167.name(), "SecurityType");
817        assert!(field_167.enums().unwrap().any(|e| e.value() == "EUCORP"));
818    }
819
820    const INVALID_QUICKFIX_SPECS: &[&str] = &[
821        include_str!("test_data/quickfix_specs/empty_file.xml"),
822        include_str!("test_data/quickfix_specs/missing_components.xml"),
823        include_str!("test_data/quickfix_specs/missing_fields.xml"),
824        include_str!("test_data/quickfix_specs/missing_header.xml"),
825        include_str!("test_data/quickfix_specs/missing_messages.xml"),
826        include_str!("test_data/quickfix_specs/missing_trailer.xml"),
827        include_str!("test_data/quickfix_specs/root_has_no_type_attr.xml"),
828        include_str!("test_data/quickfix_specs/root_has_no_version_attrs.xml"),
829        include_str!("test_data/quickfix_specs/root_is_not_fix.xml"),
830    ];
831
832    #[test]
833    fn invalid_quickfix_specs() {
834        for spec in INVALID_QUICKFIX_SPECS.iter() {
835            let dict = Dictionary::from_quickfix_spec(spec);
836            assert!(dict.is_err(), "{}", spec);
837        }
838    }
839}