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