use crate::{Component, ComponentData, Datatype, DatatypeData, Field, FieldData};
use crate::error::ParseError;
use crate::message_definition::{MessageData, MessageDefinition};
use crate::quickfix::QuickFixReader;
use crate::string::SmartString;
use fnv::FnvHashMap;
#[derive(Debug, Clone)]
pub struct Dictionary {
pub(crate) version: String,
pub(crate) data_types_by_name: FnvHashMap<SmartString, DatatypeData>,
pub(crate) fields_by_tags: FnvHashMap<u32, FieldData>,
pub(crate) field_tags_by_name: FnvHashMap<SmartString, u32>,
pub(crate) components_by_name: FnvHashMap<SmartString, ComponentData>,
pub(crate) messages_by_msgtype: FnvHashMap<SmartString, MessageData>,
pub(crate) message_msgtypes_by_name: FnvHashMap<SmartString, SmartString>,
}
impl Dictionary {
pub fn new<S: ToString>(version: S) -> Self {
Dictionary {
version: version.to_string(),
data_types_by_name: FnvHashMap::default(),
fields_by_tags: FnvHashMap::default(),
field_tags_by_name: FnvHashMap::default(),
components_by_name: FnvHashMap::default(),
messages_by_msgtype: FnvHashMap::default(),
message_msgtypes_by_name: FnvHashMap::default(),
}
}
pub fn from_quickfix_spec(input: &str) -> Result<Self, ParseError> {
let xml_document =
roxmltree::Document::parse(input).map_err(|_| ParseError::InvalidFormat)?;
QuickFixReader::new(&xml_document)
}
pub fn version(&self) -> &str {
self.version.as_str()
}
pub fn load_from_file(path: &str) -> Result<Self, ParseError> {
let spec = std::fs::read_to_string(path)
.unwrap_or_else(|_| panic!("unable to read FIX dictionary file at {path}"));
Dictionary::from_quickfix_spec(&spec)
}
#[cfg(feature = "fix40")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix40")))]
pub fn fix40() -> Self {
let spec = include_str!("resources/quickfix/FIX-4.0.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix41")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix41")))]
pub fn fix41() -> Self {
let spec = include_str!("resources/quickfix/FIX-4.1.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix42")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix42")))]
pub fn fix42() -> Self {
let spec = include_str!("resources/quickfix/FIX-4.2.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix43")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix43")))]
pub fn fix43() -> Self {
let spec = include_str!("resources/quickfix/FIX-4.3.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix44")]
pub fn fix44() -> Self {
let spec = include_str!("resources/quickfix/FIX-4.4.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix50")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix50")))]
pub fn fix50() -> Self {
let spec = include_str!("resources/quickfix/FIX-5.0.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix50sp1")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
pub fn fix50sp1() -> Self {
let spec = include_str!("resources/quickfix/FIX-5.0-SP1.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fix50sp2")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fix50sp1")))]
pub fn fix50sp2() -> Self {
let spec = include_str!("resources/quickfix/FIX-5.0-SP2.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
#[cfg(feature = "fixt11")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "fixt11")))]
pub fn fixt11() -> Self {
let spec = include_str!("resources/quickfix/FIXT-1.1.xml");
Dictionary::from_quickfix_spec(spec).unwrap()
}
pub fn common_dictionaries() -> Vec<Dictionary> {
vec![
#[cfg(feature = "fix40")]
Self::fix40(),
#[cfg(feature = "fix41")]
Self::fix41(),
#[cfg(feature = "fix42")]
Self::fix42(),
#[cfg(feature = "fix43")]
Self::fix43(),
#[cfg(feature = "fix44")]
Self::fix44(),
#[cfg(feature = "fix50")]
Self::fix50(),
#[cfg(feature = "fix50sp1")]
Self::fix50sp1(),
#[cfg(feature = "fix50sp2")]
Self::fix50sp2(),
#[cfg(feature = "fixt11")]
Self::fixt11(),
]
}
pub fn message_by_name(&self, name: &str) -> Option<MessageDefinition<'_>> {
let msg_type = self.message_msgtypes_by_name.get(name)?;
self.message_by_msgtype(msg_type)
}
pub fn message_by_msgtype(&self, msgtype: &str) -> Option<MessageDefinition<'_>> {
self.messages_by_msgtype
.get(msgtype)
.map(|data| MessageDefinition(self, data))
}
pub fn component_by_name(&self, name: &str) -> Option<Component<'_>> {
self.components_by_name
.get(name)
.map(|data| Component(self, data))
}
pub fn datatype_by_name(&self, name: &str) -> Option<Datatype<'_>> {
self.data_types_by_name
.get(name)
.map(|data| Datatype(self, data))
}
pub fn field_by_tag(&self, tag: u32) -> Option<Field<'_>> {
self.fields_by_tags
.get(&tag)
.map(|data| Field::new(self, data))
}
pub fn field_by_name(&self, name: &str) -> Option<Field<'_>> {
let tag = self.field_tags_by_name.get(name)?;
self.field_by_tag(*tag)
}
pub fn datatypes(&self) -> Vec<Datatype<'_>> {
self.data_types_by_name
.values()
.map(|data| Datatype(self, data))
.collect()
}
pub fn messages(&self) -> Vec<MessageDefinition<'_>> {
self.messages_by_msgtype
.values()
.map(|data| MessageDefinition(self, data))
.collect()
}
pub fn fields(&self) -> Vec<Field<'_>> {
self.fields_by_tags
.values()
.map(|data| Field::new(self, data))
.collect()
}
pub fn components(&self) -> Vec<Component<'_>> {
self.components_by_name
.values()
.map(|data| Component(self, data))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_from_file_success() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/resources/quickfix/FIX-4.4.xml"
);
let dict = Dictionary::load_from_file(path).unwrap();
assert_eq!(dict.version(), "FIX.4.4");
assert!(dict.message_by_name("Heartbeat").is_some());
}
#[test]
fn test_load_from_file_invalid_content() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/test_data/quickfix_specs/empty_file.xml"
);
let result = Dictionary::load_from_file(path);
assert!(result.is_err());
}
}