1#![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
20pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
54pub enum FieldLocation {
55 Header,
57 Body,
59 Trailer,
61}
62
63pub type Dictionaries = FxHashMap<SmartString, Arc<Dictionary>>;
65
66#[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 categories_by_name: FxHashMap<SmartString, CategoryData>,
100 }
102
103impl Dictionary {
104 fn new<S: ToString>(version: S) -> Self {
106 Dictionary {
107 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 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 pub fn version(&self) -> &str {
138 self.version.as_str()
139 }
140
141 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 pub fn common_dictionaries() -> SmallVec<[Dictionary; 10]> {
207 #[allow(unused_mut)] 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 pub fn abbreviation_for(&self, term: &str) -> Option<Abbreviation> {
233 self.abbreviation_definitions.get(term).map(Abbreviation)
234 }
235
236 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 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 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 pub fn datatype_by_name(&self, name: &str) -> Option<Datatype> {
286 self.data_types_by_name.get(name).map(Datatype)
287 }
288
289 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 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 fn category_by_name(&self, name: &str) -> Option<Category> {
311 self.categories_by_name.get(name).map(Category)
312 }
313
314 pub fn datatypes(&self) -> SmallVec<[Datatype; 24]> {
325 self.data_types_by_name.values().map(Datatype).collect()
326 }
327
328 pub fn messages(&self) -> Vec<Message> {
340 self.messages_by_msgtype
341 .values()
342 .map(|data| Message(self, data))
343 .collect()
344 }
345
346 pub fn categories(&self) -> SmallVec<[Category; 8]> {
349 self.categories_by_name.values().map(Category).collect()
350 }
351
352 pub fn fields(&self) -> Vec<Field> {
355 self.fields_by_tags
356 .values()
357 .map(|data| Field(self, data))
358 .collect()
359 }
360
361 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#[derive(Debug)]
376pub struct Abbreviation<'a>(&'a AbbreviationData);
377
378impl<'a> Abbreviation<'a> {
379 pub fn term(&self) -> &str {
381 self.0.abbreviation.as_str()
382 }
383}
384
385#[derive(Clone, Debug)]
388pub struct Category<'a>(&'a CategoryData);
389
390impl<'a> Category<'a> {
391 pub fn name(&self) -> &str {
394 self.0.name.as_str()
395 }
396}
397
398#[derive(Clone, Debug)]
406pub struct Component<'a>(&'a ComponentData, &'a Dictionary);
407
408impl<'a> Component<'a> {
409 pub fn id(&self) -> u32 {
411 self.0.id as u32
412 }
413
414 pub fn name(&self) -> &str {
417 self.0.name.as_str()
418 }
419
420 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 pub fn category(&self) -> Category {
431 self.1
432 .category_by_name(self.0.category_name.as_str())
433 .unwrap()
434 }
435
436 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 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#[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#[derive(Debug)]
472pub struct Datatype<'a>(&'a DatatypeData);
473
474impl<'a> Datatype<'a> {
475 pub fn name(&self) -> &str {
478 self.0.datatype.name()
479 }
480
481 pub fn basetype(&self) -> FixDatatype {
483 self.0.datatype
484 }
485}
486
487#[derive(Debug)]
490pub struct FieldEnum<'a>(&'a FieldEnumData);
491
492impl<'a> FieldEnum<'a> {
493 pub fn value(&self) -> &str {
495 &self.0.value[..]
496 }
497
498 pub fn description(&self) -> &str {
500 &self.0.description[..]
501 }
502}
503
504#[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 pub fn fix_datatype(&self) -> FixDatatype {
545 self.data_type().basetype()
546 }
547
548 pub fn name(&self) -> &str {
551 self.1.name.as_str()
552 }
553
554 pub fn tag(&self) -> TagU32 {
557 TagU32::new(self.1.tag).unwrap()
558 }
559
560 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 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 }
603}
604
605pub trait IsFieldDefinition {
606 fn tag(&self) -> TagU32;
608
609 fn name(&self) -> &str;
611
612 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#[derive(Clone, Debug)]
640pub struct LayoutItem<'a>(&'a Dictionary, &'a LayoutItemData);
641
642#[derive(Debug)]
644pub enum LayoutItemKind<'a> {
645 Component(Component<'a>),
647 Group(Field<'a>, SmallVec<[LayoutItem<'a>; 8]>),
649 Field(Field<'a>),
651}
652
653impl<'a> LayoutItem<'a> {
654 pub fn required(&self) -> bool {
657 self.1.required
658 }
659
660 pub fn kind(&self) -> LayoutItemKind {
662 layout_item_kind(&self.1.kind, self.0)
663 }
664
665 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#[derive(Debug)]
683pub struct Message<'a>(&'a Dictionary, &'a MessageData);
684
685impl<'a> Message<'a> {
686 pub fn name(&self) -> &str {
688 self.1.name.as_str()
689 }
690
691 pub fn msg_type(&self) -> &str {
693 self.1.msg_type.as_str()
694 }
695
696 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 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#[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}