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 fnv::FnvHashMap;
15use quickfix::{ParseDictionaryError, QuickFixReader};
16use smartstring::alias::String as SmartString;
17use std::sync::Arc;
18
19pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
53pub enum FieldLocation {
54 Header,
56 Body,
58 Trailer,
60}
61
62pub type Dictionaries = FnvHashMap<String, Arc<Dictionary>>;
64
65#[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 categories_by_name: FnvHashMap<SmartString, CategoryData>,
99 }
101
102impl Dictionary {
103 fn new<S: ToString>(version: S) -> Self {
105 Dictionary {
106 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 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 pub fn version(&self) -> &str {
137 self.version.as_str()
138 }
139
140 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 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 pub fn abbreviation_for(&self, term: &str) -> Option<Abbreviation> {
231 self.abbreviation_definitions.get(term).map(Abbreviation)
232 }
233
234 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 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 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 pub fn datatype_by_name(&self, name: &str) -> Option<Datatype> {
284 self.data_types_by_name.get(name).map(Datatype)
285 }
286
287 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 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 fn category_by_name(&self, name: &str) -> Option<Category> {
309 self.categories_by_name.get(name).map(Category)
310 }
311
312 pub fn datatypes(&self) -> Vec<Datatype> {
323 self.data_types_by_name.values().map(Datatype).collect()
324 }
325
326 pub fn messages(&self) -> Vec<Message> {
338 self.messages_by_msgtype
339 .values()
340 .map(|data| Message(self, data))
341 .collect()
342 }
343
344 pub fn categories(&self) -> Vec<Category> {
347 self.categories_by_name.values().map(Category).collect()
348 }
349
350 pub fn fields(&self) -> Vec<Field> {
353 self.fields_by_tags
354 .values()
355 .map(|data| Field(self, data))
356 .collect()
357 }
358
359 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#[derive(Debug)]
374pub struct Abbreviation<'a>(&'a AbbreviationData);
375
376impl<'a> Abbreviation<'a> {
377 pub fn term(&self) -> &str {
379 self.0.abbreviation.as_str()
380 }
381}
382
383#[derive(Clone, Debug)]
386pub struct Category<'a>(&'a CategoryData);
387
388impl<'a> Category<'a> {
389 pub fn name(&self) -> &str {
392 self.0.name.as_str()
393 }
394}
395
396#[derive(Clone, Debug)]
404pub struct Component<'a>(&'a ComponentData, &'a Dictionary);
405
406impl<'a> Component<'a> {
407 pub fn id(&self) -> u32 {
409 self.0.id as u32
410 }
411
412 pub fn name(&self) -> &str {
415 self.0.name.as_str()
416 }
417
418 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 pub fn category(&self) -> Category {
429 self.1
430 .category_by_name(self.0.category_name.as_str())
431 .unwrap()
432 }
433
434 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 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#[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#[derive(Debug)]
470pub struct Datatype<'a>(&'a DatatypeData);
471
472impl<'a> Datatype<'a> {
473 pub fn name(&self) -> &str {
476 self.0.datatype.name()
477 }
478
479 pub fn basetype(&self) -> FixDatatype {
481 self.0.datatype
482 }
483}
484
485#[derive(Debug)]
488pub struct FieldEnum<'a>(&'a FieldEnumData);
489
490impl<'a> FieldEnum<'a> {
491 pub fn value(&self) -> &str {
493 &self.0.value[..]
494 }
495
496 pub fn description(&self) -> &str {
498 &self.0.description[..]
499 }
500}
501
502#[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 pub fn fix_datatype(&self) -> FixDatatype {
542 self.data_type().basetype()
543 }
544
545 pub fn name(&self) -> &str {
548 self.1.name.as_str()
549 }
550
551 pub fn tag(&self) -> TagU32 {
554 TagU32::new(self.1.tag).unwrap()
555 }
556
557 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 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 }
600}
601
602pub trait IsFieldDefinition {
603 fn tag(&self) -> TagU32;
605
606 fn name(&self) -> &str;
608
609 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#[derive(Clone, Debug)]
637pub struct LayoutItem<'a>(&'a Dictionary, &'a LayoutItemData);
638
639#[derive(Debug)]
641pub enum LayoutItemKind<'a> {
642 Component(Component<'a>),
644 Group(Field<'a>, Vec<LayoutItem<'a>>),
646 Field(Field<'a>),
648}
649
650impl<'a> LayoutItem<'a> {
651 pub fn required(&self) -> bool {
654 self.1.required
655 }
656
657 pub fn kind(&self) -> LayoutItemKind {
659 layout_item_kind(&self.1.kind, self.0)
660 }
661
662 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#[derive(Debug)]
687pub struct Message<'a>(&'a Dictionary, &'a MessageData);
688
689impl<'a> Message<'a> {
690 pub fn name(&self) -> &str {
692 self.1.name.as_str()
693 }
694
695 pub fn msg_type(&self) -> &str {
697 self.1.msg_type.as_str()
698 }
699
700 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 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#[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}