#![allow(dead_code)]
use self::symbol_table::{Key, KeyRef, SymbolTable, SymbolTableIndex};
use super::TagU16;
use fnv::FnvHashMap;
use quickfix::{ParseDictionaryError, QuickFixReader};
use std::fmt;
use std::sync::Arc;
pub use datatype::FixDatatype;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FieldLocation {
Header,
Body,
Trailer,
}
type InternalId = u32;
#[derive(Clone, Debug)]
pub struct Dictionary {
inner: Arc<DictionaryData>,
}
#[derive(Clone, Debug)]
struct DictionaryData {
version: String,
symbol_table: SymbolTable,
abbreviations: Vec<AbbreviationData>,
data_types: Vec<DatatypeData>,
fields: Vec<FieldData>,
components: Vec<ComponentData>,
messages: Vec<MessageData>,
categories: Vec<CategoryData>,
header: Vec<FieldData>,
}
impl fmt::Display for Dictionary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "<fix type='FIX' version='{}'>", self.inner.version)?;
{
writeln!(f, " <header>")?;
let std_header = self.component_by_name("StandardHeader").unwrap();
for item in std_header.items() {
display_layout_item(2, item, f)?;
}
writeln!(f, " </header>")?;
}
{
writeln!(f, " <messages>")?;
for message in self.iter_messages() {
writeln!(
f,
" <message name='{}' msgtype='{}' msgcat='{}'>",
message.name(),
message.msg_type(),
"TODO"
)?;
for item in message.layout() {
display_layout_item(2, item, f)?;
}
writeln!(f, " </message>")?;
}
writeln!(f, " </messages>")?;
}
{
writeln!(f, " <header>")?;
let std_header = self.component_by_name("StandardTrailer").unwrap();
for item in std_header.items() {
display_layout_item(2, item, f)?;
}
writeln!(f, " </header>")?;
}
Ok(())
}
}
fn display_layout_item(indent: u32, item: LayoutItem, f: &mut fmt::Formatter) -> fmt::Result {
for _ in 0..indent {
write!(f, " ")?;
}
match item.kind() {
LayoutItemKind::Field(_) => {
writeln!(
f,
"<field name='{}' required='{}' />",
item.tag_text(),
item.required(),
)?;
}
LayoutItemKind::Group(_, _fields) => {
writeln!(
f,
"<group name='{}' required='{}' />",
item.tag_text(),
item.required(),
)?;
writeln!(f, "</group>")?;
}
LayoutItemKind::Component(_c) => {
writeln!(
f,
"<component name='{}' required='{}' />",
item.tag_text(),
item.required(),
)?;
writeln!(f, "</component>")?;
}
}
Ok(())
}
impl DictionaryData {
fn symbol(&self, pkey: KeyRef) -> Option<&u32> {
self.symbol_table.get(&pkey as &dyn SymbolTableIndex)
}
}
impl Dictionary {
fn new<S: ToString>(version: S) -> Self {
Dictionary {
inner: Arc::new(DictionaryData {
version: version.to_string(),
symbol_table: FnvHashMap::default(),
abbreviations: Vec::new(),
data_types: Vec::new(),
fields: Vec::new(),
components: Vec::new(),
messages: Vec::new(),
categories: Vec::new(),
header: Vec::new(),
}),
}
}
pub fn from_quickfix_spec<S: AsRef<str>>(input: S) -> Result<Self, ParseDictionaryError> {
let xml_document = roxmltree::Document::parse(input.as_ref())
.map_err(|_| ParseDictionaryError::InvalidFormat)?;
QuickFixReader::new(&xml_document)
}
pub fn empty() -> Self {
Self::new("FIX.???")
}
pub fn get_version(&self) -> &str {
self.inner.version.as_str()
}
#[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()
}
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()
}
#[cfg(test)]
pub fn all() -> Vec<Dictionary> {
vec![
#[cfg(feature = "fix40")]
Self::fix40(),
#[cfg(feature = "fix41")]
Self::fix41(),
#[cfg(feature = "fix42")]
Self::fix42(),
#[cfg(feature = "fix43")]
Self::fix43(),
Self::fix44(),
#[cfg(feature = "fix50")]
Self::fix50(),
#[cfg(feature = "fix50sp1")]
Self::fix50sp1(),
#[cfg(feature = "fix50sp2")]
Self::fix50sp2(),
#[cfg(feature = "fixt11")]
Self::fixt11(),
]
}
fn symbol(&self, pkey: KeyRef) -> Option<&u32> {
self.inner.symbol(pkey)
}
pub fn abbreviation_for<S: AsRef<str>>(&self, term: S) -> Option<Abbreviation> {
self.symbol(KeyRef::Abbreviation(term.as_ref()))
.map(|iid| self.inner.abbreviations.get(*iid as usize).unwrap())
.map(move |data| Abbreviation(self, data))
}
pub fn message_by_name<S: AsRef<str>>(&self, name: S) -> Option<Message> {
self.symbol(KeyRef::MessageByName(name.as_ref()))
.map(|iid| self.inner.messages.get(*iid as usize).unwrap())
.map(|data| Message(self, data))
}
pub fn message_by_msgtype<S: AsRef<str>>(&self, msgtype: S) -> Option<Message> {
self.symbol(KeyRef::MessageByMsgType(msgtype.as_ref()))
.map(|iid| self.inner.messages.get(*iid as usize).unwrap())
.map(|data| Message(self, data))
}
pub fn component_by_name<S: AsRef<str>>(&self, name: S) -> Option<Component> {
self.symbol(KeyRef::ComponentByName(name.as_ref()))
.map(|iid| self.inner.components.get(*iid as usize).unwrap())
.map(|data| Component(self, data))
}
pub fn datatype_by_name<S: AsRef<str>>(&self, name: S) -> Option<Datatype> {
self.symbol(KeyRef::DatatypeByName(name.as_ref()))
.map(|iid| self.inner.data_types.get(*iid as usize).unwrap())
.map(|data| Datatype(self, data))
}
pub fn field_by_tag(&self, tag: u32) -> Option<Field> {
self.symbol(KeyRef::FieldByTag(tag))
.map(|iid| self.inner.fields.get(*iid as usize).unwrap())
.map(|data| Field(self, data))
}
pub fn field_by_name<S: AsRef<str>>(&self, name: S) -> Option<Field> {
self.symbol(KeyRef::FieldByName(name.as_ref()))
.map(|iid| self.inner.fields.get(*iid as usize).unwrap())
.map(|data| Field(self, data))
}
pub fn iter_datatypes(&self) -> impl Iterator<Item = Datatype> {
self.inner
.data_types
.iter()
.map(move |data| Datatype(self, data))
}
pub fn iter_messages(&self) -> impl Iterator<Item = Message> {
self.inner
.messages
.iter()
.map(move |data| Message(&self, data))
}
pub fn iter_categories(&self) -> impl Iterator<Item = Category> {
self.inner
.categories
.iter()
.map(move |data| Category(&self, data))
}
pub fn iter_fields(&self) -> impl Iterator<Item = Field> {
self.inner.fields.iter().map(move |data| Field(&self, data))
}
pub fn iter_components(&self) -> impl Iterator<Item = Component> {
self.inner
.components
.iter()
.map(move |data| Component(&self, data))
}
}
struct DictionaryBuilder {
version: String,
symbol_table: FnvHashMap<Key, InternalId>,
abbreviations: Vec<AbbreviationData>,
data_types: Vec<DatatypeData>,
fields: Vec<FieldData>,
components: Vec<ComponentData>,
messages: Vec<MessageData>,
categories: Vec<CategoryData>,
header: Vec<FieldData>,
}
impl DictionaryBuilder {
pub fn new(version: String) -> Self {
Self {
version,
symbol_table: FnvHashMap::default(),
abbreviations: Vec::new(),
data_types: Vec::new(),
fields: Vec::new(),
components: Vec::new(),
messages: Vec::new(),
categories: Vec::new(),
header: Vec::new(),
}
}
pub fn symbol(&self, pkey: KeyRef) -> Option<&InternalId> {
self.symbol_table.get(&pkey as &dyn SymbolTableIndex)
}
pub fn add_field(&mut self, field: FieldData) -> InternalId {
let iid = self.fields.len() as InternalId;
self.symbol_table
.insert(Key::FieldByName(field.name.clone()), iid);
self.symbol_table
.insert(Key::FieldByTag(field.tag as u32), iid);
self.fields.push(field);
iid
}
pub fn add_message(&mut self, message: MessageData) -> InternalId {
let iid = self.messages.len() as InternalId;
self.symbol_table
.insert(Key::MessageByName(message.name.clone()), iid);
self.symbol_table
.insert(Key::MessageByMsgType(message.msg_type.to_string()), iid);
self.messages.push(message);
iid
}
pub fn add_component(&mut self, component: ComponentData) -> InternalId {
let iid = self.components.len() as InternalId;
self.symbol_table
.insert(Key::ComponentByName(component.name.to_string()), iid);
self.components.push(component);
iid
}
pub fn build(self) -> Dictionary {
Dictionary {
inner: Arc::new(DictionaryData {
version: self.version,
symbol_table: self.symbol_table,
abbreviations: self.abbreviations,
data_types: self.data_types,
fields: self.fields,
components: self.components,
messages: self.messages,
categories: self.categories,
header: self.header,
}),
}
}
}
#[derive(Clone, Debug)]
struct AbbreviationData {
abbreviation: String,
is_last: bool,
}
#[derive(Debug)]
pub struct Abbreviation<'a>(&'a Dictionary, &'a AbbreviationData);
impl<'a> Abbreviation<'a> {
pub fn term(&self) -> &str {
self.1.abbreviation.as_str()
}
}
#[derive(Clone, Debug)]
struct CategoryData {
name: String,
fixml_filename: String,
}
#[derive(Clone, Debug)]
pub struct Category<'a>(&'a Dictionary, &'a CategoryData);
#[derive(Clone, Debug)]
struct ComponentData {
id: usize,
component_type: FixmlComponentAttributes,
layout_items: Vec<LayoutItemData>,
category_iid: InternalId,
name: String,
abbr_name: Option<String>,
}
#[derive(Clone, Debug)]
pub struct Component<'a>(&'a Dictionary, &'a ComponentData);
impl<'a> Component<'a> {
pub fn id(&self) -> u32 {
self.1.id as u32
}
pub fn name(&self) -> &str {
self.1.name.as_str()
}
pub fn is_group(&self) -> bool {
match self.1.component_type {
FixmlComponentAttributes::Block { is_repeating, .. } => is_repeating,
_ => false,
}
}
pub fn category(&self) -> Category {
let data = self
.0
.inner
.categories
.get(self.1.category_iid as usize)
.unwrap();
Category(self.0, data)
}
pub fn items(&self) -> impl Iterator<Item = LayoutItem> {
self.1
.layout_items
.iter()
.map(move |data| LayoutItem(self.0, data))
}
pub fn contains_field(&self, field: &Field) -> bool {
self.items().any(|layout_item| {
if let LayoutItemKind::Field(f) = layout_item.kind() {
f.tag() == field.tag()
} else {
false
}
})
}
}
#[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub enum FixmlComponentAttributes {
Xml,
Block {
is_repeating: bool,
is_implicit: bool,
is_optimized: bool,
},
Message,
}
#[derive(Clone, Debug, PartialEq)]
struct DatatypeData {
datatype: FixDatatype,
description: String,
examples: Vec<String>,
}
#[derive(Debug)]
pub struct Datatype<'a>(&'a Dictionary, &'a DatatypeData);
impl<'a> Datatype<'a> {
pub fn name(&self) -> &str {
self.1.datatype.name()
}
pub fn basetype(&self) -> FixDatatype {
self.1.datatype
}
}
mod datatype {
use strum::IntoEnumIterator;
use strum_macros::{EnumIter, IntoStaticStr};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, EnumIter, IntoStaticStr)]
#[repr(u8)]
#[non_exhaustive]
pub enum FixDatatype {
Char,
Boolean,
Float,
Amt,
Price,
PriceOffset,
Qty,
Percentage,
Int,
DayOfMonth,
Length,
NumInGroup,
SeqNum,
TagNum,
String,
Data,
MonthYear,
MultipleCharValue,
Currency,
Exchange,
Language,
LocalMktDate,
MultipleStringValue,
UtcDateOnly,
UtcTimeOnly,
UtcTimestamp,
XmlData,
Country,
}
impl FixDatatype {
pub fn from_quickfix_name(name: &str) -> Option<Self> {
Some(match name.to_ascii_uppercase().as_str() {
"AMT" => FixDatatype::Amt,
"BOOLEAN" => FixDatatype::Boolean,
"CHAR" => FixDatatype::Char,
"COUNTRY" => FixDatatype::Country,
"CURRENCY" => FixDatatype::Currency,
"DATA" => FixDatatype::Data,
"DATE" => FixDatatype::UtcDateOnly, "DAYOFMONTH" => FixDatatype::DayOfMonth,
"EXCHANGE" => FixDatatype::Exchange,
"FLOAT" => FixDatatype::Float,
"INT" => FixDatatype::Int,
"LANGUAGE" => FixDatatype::Language,
"LENGTH" => FixDatatype::Length,
"LOCALMKTDATE" => FixDatatype::LocalMktDate,
"MONTHYEAR" => FixDatatype::MonthYear,
"MULTIPLECHARVALUE" | "MULTIPLEVALUESTRING" => FixDatatype::MultipleCharValue,
"MULTIPLESTRINGVALUE" => FixDatatype::MultipleStringValue,
"NUMINGROUP" => FixDatatype::NumInGroup,
"PERCENTAGE" => FixDatatype::Percentage,
"PRICE" => FixDatatype::Price,
"PRICEOFFSET" => FixDatatype::PriceOffset,
"QTY" => FixDatatype::Qty,
"STRING" => FixDatatype::String,
"TZTIMEONLY" => FixDatatype::UtcTimeOnly, "TZTIMESTAMP" => FixDatatype::UtcTimestamp, "UTCDATE" => FixDatatype::UtcDateOnly,
"UTCDATEONLY" => FixDatatype::UtcDateOnly,
"UTCTIMEONLY" => FixDatatype::UtcTimeOnly,
"UTCTIMESTAMP" => FixDatatype::UtcTimestamp,
"SEQNUM" => FixDatatype::SeqNum,
"TIME" => FixDatatype::UtcTimestamp,
"XMLDATA" => FixDatatype::XmlData,
_ => {
return None;
}
})
}
pub fn to_quickfix_name(&self) -> &str {
match self {
FixDatatype::Int => "INT",
FixDatatype::Length => "LENGTH",
FixDatatype::Char => "CHAR",
FixDatatype::Boolean => "BOOLEAN",
FixDatatype::Float => "FLOAT",
FixDatatype::Amt => "AMT",
FixDatatype::Price => "PRICE",
FixDatatype::PriceOffset => "PRICEOFFSET",
FixDatatype::Qty => "QTY",
FixDatatype::Percentage => "PERCENTAGE",
FixDatatype::DayOfMonth => "DAYOFMONTH",
FixDatatype::NumInGroup => "NUMINGROUP",
FixDatatype::Language => "LANGUAGE",
FixDatatype::SeqNum => "SEQNUM",
FixDatatype::TagNum => "TAGNUM",
FixDatatype::String => "STRING",
FixDatatype::Data => "DATA",
FixDatatype::MonthYear => "MONTHYEAR",
FixDatatype::Currency => "CURRENCY",
FixDatatype::Exchange => "EXCHANGE",
FixDatatype::LocalMktDate => "LOCALMKTDATE",
FixDatatype::MultipleStringValue => "MULTIPLESTRINGVALUE",
FixDatatype::UtcTimeOnly => "UTCTIMEONLY",
FixDatatype::UtcTimestamp => "UTCTIMESTAMP",
FixDatatype::UtcDateOnly => "UTCDATEONLY",
FixDatatype::Country => "COUNTRY",
FixDatatype::MultipleCharValue => "MULTIPLECHARVALUE",
FixDatatype::XmlData => "XMLDATA",
}
}
pub fn name(&self) -> &'static str {
match self {
FixDatatype::Int => "int",
FixDatatype::Length => "Length",
FixDatatype::Char => "char",
FixDatatype::Boolean => "Boolean",
FixDatatype::Float => "float",
FixDatatype::Amt => "Amt",
FixDatatype::Price => "Price",
FixDatatype::PriceOffset => "PriceOffset",
FixDatatype::Qty => "Qty",
FixDatatype::Percentage => "Percentage",
FixDatatype::DayOfMonth => "DayOfMonth",
FixDatatype::NumInGroup => "NumInGroup",
FixDatatype::Language => "Language",
FixDatatype::SeqNum => "SeqNum",
FixDatatype::TagNum => "TagNum",
FixDatatype::String => "String",
FixDatatype::Data => "data",
FixDatatype::MonthYear => "MonthYear",
FixDatatype::Currency => "Currency",
FixDatatype::Exchange => "Exchange",
FixDatatype::LocalMktDate => "LocalMktDate",
FixDatatype::MultipleStringValue => "MultipleStringValue",
FixDatatype::UtcTimeOnly => "UTCTimeOnly",
FixDatatype::UtcTimestamp => "UTCTimestamp",
FixDatatype::UtcDateOnly => "UTCDateOnly",
FixDatatype::Country => "Country",
FixDatatype::MultipleCharValue => "MultipleCharValue",
FixDatatype::XmlData => "XMLData",
}
}
pub fn is_base_type(&self) -> bool {
match self {
Self::Char | Self::Float | Self::Int | Self::String => true,
_ => false,
}
}
pub fn base_type(&self) -> Self {
let dt = match self {
Self::Char | Self::Boolean => Self::Char,
Self::Float
| Self::Amt
| Self::Price
| Self::PriceOffset
| Self::Qty
| Self::Percentage => Self::Float,
Self::Int
| Self::DayOfMonth
| Self::Length
| Self::NumInGroup
| Self::SeqNum
| Self::TagNum => Self::Int,
_ => Self::String,
};
debug_assert!(dt.is_base_type());
dt
}
pub fn iter_all() -> impl Iterator<Item = Self> {
<Self as IntoEnumIterator>::iter()
}
}
#[cfg(test)]
mod test {
use super::*;
use std::collections::HashSet;
#[test]
fn iter_all_unique() {
let as_vec = FixDatatype::iter_all().collect::<Vec<FixDatatype>>();
let as_set = FixDatatype::iter_all().collect::<HashSet<FixDatatype>>();
assert_eq!(as_vec.len(), as_set.len());
}
#[test]
fn more_than_20_datatypes() {
assert!(FixDatatype::iter_all().count() > 20);
}
#[test]
fn names_are_unique() {
let as_vec = FixDatatype::iter_all()
.map(|dt| dt.name())
.collect::<Vec<&str>>();
let as_set = FixDatatype::iter_all()
.map(|dt| dt.name())
.collect::<HashSet<&str>>();
assert_eq!(as_vec.len(), as_set.len());
}
#[test]
fn base_type_is_itself() {
for dt in FixDatatype::iter_all() {
if dt.is_base_type() {
assert_eq!(dt.base_type(), dt);
} else {
assert_ne!(dt.base_type(), dt);
}
}
}
#[test]
fn base_type_is_actually_base_type() {
for dt in FixDatatype::iter_all() {
assert!(dt.base_type().is_base_type());
}
}
}
}
#[derive(Clone, Debug)]
struct FieldData {
name: String,
tag: u32,
data_type_iid: InternalId,
associated_data_tag: Option<usize>,
value_restrictions: Option<Vec<FieldEnumData>>,
abbr_name: Option<String>,
base_category_id: Option<usize>,
base_category_abbr_name: Option<String>,
required: bool,
description: Option<String>,
}
#[derive(Clone, Debug)]
struct FieldEnumData {
value: String,
description: String,
}
#[derive(Debug)]
pub struct FieldEnum<'a>(&'a Dictionary, &'a FieldEnumData);
impl<'a> FieldEnum<'a> {
pub fn value(&self) -> &str {
&self.1.value[..]
}
pub fn description(&self) -> &str {
&self.1.description[..]
}
}
#[derive(Debug, Copy, Clone)]
pub struct Field<'a>(&'a Dictionary, &'a FieldData);
impl<'a> Field<'a> {
pub fn doc_url_onixs(&self, version: &str) -> String {
let v = match version {
"FIX.4.0" => "4.0",
"FIX.4.1" => "4.1",
"FIX.4.2" => "4.2",
"FIX.4.3" => "4.3",
"FIX.4.4" => "4.4",
"FIX.5.0" => "5.0",
"FIX.5.0SP1" => "5.0.SP1",
"FIX.5.0SP2" => "5.0.SP2",
"FIXT.1.1" => "FIXT.1.1",
s => s,
};
format!(
"https://www.onixs.biz/fix-dictionary/{}/tagNum_{}.html",
v,
self.1.tag.to_string().as_str()
)
}
pub fn fix_datatype(&self) -> FixDatatype {
self.data_type().basetype()
}
pub fn name(&self) -> &str {
self.1.name.as_str()
}
pub fn tag(&self) -> TagU16 {
TagU16::new(self.1.tag as u16).unwrap()
}
pub fn enums(&self) -> Option<impl Iterator<Item = FieldEnum>> {
self.1
.value_restrictions
.as_ref()
.map(move |v| v.iter().map(move |f| FieldEnum(self.0, f)))
}
pub fn data_type(&self) -> Datatype {
let data = self
.0
.inner
.data_types
.get(self.1.data_type_iid as usize)
.unwrap();
Datatype(self.0, data)
}
}
impl<'a> IsFieldDefinition for Field<'a> {
fn name(&self) -> &str {
self.1.name.as_str()
}
fn tag(&self) -> TagU16 {
TagU16::new(self.1.tag as u16).expect("Invalid FIX tag (0)")
}
fn location(&self) -> FieldLocation {
FieldLocation::Body }
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
enum LayoutItemKindData {
Component {
iid: InternalId,
},
Group {
len_field_iid: u32,
items: Vec<LayoutItemData>,
},
Field {
iid: InternalId,
},
}
#[derive(Clone, Debug)]
struct LayoutItemData {
required: bool,
kind: LayoutItemKindData,
}
pub trait IsFieldDefinition {
fn tag(&self) -> TagU16;
fn name(&self) -> &str;
fn location(&self) -> FieldLocation;
}
fn layout_item_kind<'a>(item: &'a LayoutItemKindData, dict: &'a Dictionary) -> LayoutItemKind<'a> {
match item {
LayoutItemKindData::Component { iid } => LayoutItemKind::Component(Component(
dict,
dict.inner.components.get(*iid as usize).unwrap(),
)),
LayoutItemKindData::Group {
len_field_iid,
items: items_data,
} => {
let items = items_data
.iter()
.map(|item_data| LayoutItem(dict, item_data))
.collect::<Vec<_>>();
let len_field_data = &dict.inner.fields[*len_field_iid as usize];
let len_field = Field(dict, len_field_data);
LayoutItemKind::Group(len_field, items)
}
LayoutItemKindData::Field { iid } => {
LayoutItemKind::Field(Field(dict, dict.inner.fields.get(*iid as usize).unwrap()))
}
}
}
#[derive(Clone, Debug)]
pub struct LayoutItem<'a>(&'a Dictionary, &'a LayoutItemData);
#[derive(Debug)]
pub enum LayoutItemKind<'a> {
Component(Component<'a>),
Group(Field<'a>, Vec<LayoutItem<'a>>),
Field(Field<'a>),
}
impl<'a> LayoutItem<'a> {
pub fn required(&self) -> bool {
self.1.required
}
pub fn kind(&self) -> LayoutItemKind {
layout_item_kind(&self.1.kind, self.0)
}
pub fn tag_text(&self) -> &str {
match &self.1.kind {
LayoutItemKindData::Component { iid } => self
.0
.inner
.components
.get(*iid as usize)
.unwrap()
.name
.as_str(),
LayoutItemKindData::Group {
len_field_iid,
items: _items,
} => self
.0
.inner
.fields
.get(*len_field_iid as usize)
.unwrap()
.name
.as_str(),
LayoutItemKindData::Field { iid } => self
.0
.inner
.fields
.get(*iid as usize)
.unwrap()
.name
.as_str(),
}
}
}
type LayoutItems = Vec<LayoutItemData>;
#[derive(Clone, Debug)]
struct MessageData {
component_id: u32,
msg_type: String,
name: String,
category_iid: InternalId,
section_id: String,
layout_items: LayoutItems,
abbr_name: Option<String>,
required: bool,
description: String,
elaboration: Option<String>,
}
#[derive(Debug)]
pub struct Message<'a>(&'a Dictionary, &'a MessageData);
impl<'a> Message<'a> {
pub fn name(&self) -> &str {
self.1.name.as_str()
}
pub fn msg_type(&self) -> &str {
self.1.msg_type.as_str()
}
pub fn description(&self) -> &str {
&self.1.description
}
pub fn group_info(&self, num_in_group_tag: TagU16) -> Option<TagU16> {
self.layout().find_map(|layout_item| {
if let LayoutItemKind::Group(field, items) = layout_item.kind() {
if field.tag() == num_in_group_tag {
if let LayoutItemKind::Field(f) = items[0].kind() {
Some(f.tag())
} else {
None
}
} else {
None
}
} else if let LayoutItemKind::Component(_component) = layout_item.kind() {
None
} else {
None
}
})
}
pub fn component_id(&self) -> u32 {
self.1.component_id
}
pub fn layout(&self) -> impl Iterator<Item = LayoutItem> {
self.1
.layout_items
.iter()
.map(move |data| LayoutItem(self.0, data))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Section {}
mod symbol_table {
use super::InternalId;
use fnv::FnvHashMap;
use std::borrow::Borrow;
use std::hash::Hash;
pub type SymbolTable = FnvHashMap<Key, InternalId>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Key {
#[allow(dead_code)]
Abbreviation(String),
CategoryByName(String),
ComponentByName(String),
DatatypeByName(String),
FieldByTag(u32),
FieldByName(String),
MessageByName(String),
MessageByMsgType(String),
}
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
pub enum KeyRef<'a> {
Abbreviation(&'a str),
CategoryByName(&'a str),
ComponentByName(&'a str),
DatatypeByName(&'a str),
FieldByTag(u32),
FieldByName(&'a str),
MessageByName(&'a str),
MessageByMsgType(&'a str),
}
impl Key {
fn as_ref(&self) -> KeyRef {
match self {
Key::Abbreviation(s) => KeyRef::Abbreviation(s.as_str()),
Key::CategoryByName(s) => KeyRef::CategoryByName(s.as_str()),
Key::ComponentByName(s) => KeyRef::ComponentByName(s.as_str()),
Key::DatatypeByName(s) => KeyRef::DatatypeByName(s.as_str()),
Key::FieldByTag(t) => KeyRef::FieldByTag(*t),
Key::FieldByName(s) => KeyRef::FieldByName(s.as_str()),
Key::MessageByName(s) => KeyRef::MessageByName(s.as_str()),
Key::MessageByMsgType(s) => KeyRef::MessageByMsgType(s.as_str()),
}
}
}
pub trait SymbolTableIndex {
fn to_key(&self) -> KeyRef;
}
impl SymbolTableIndex for Key {
fn to_key(&self) -> KeyRef {
self.as_ref()
}
}
impl<'a> SymbolTableIndex for KeyRef<'a> {
fn to_key(&self) -> KeyRef {
*self
}
}
impl<'a> Hash for dyn SymbolTableIndex + 'a {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_key().hash(state);
}
}
impl<'a> Borrow<dyn SymbolTableIndex + 'a> for Key {
fn borrow(&self) -> &(dyn SymbolTableIndex + 'a) {
self
}
}
impl<'a> Eq for dyn SymbolTableIndex + 'a {}
impl<'a> PartialEq for dyn SymbolTableIndex + 'a {
fn eq(&self, other: &dyn SymbolTableIndex) -> bool {
self.to_key() == other.to_key()
}
}
}
mod quickfix {
use super::*;
pub struct QuickFixReader<'a> {
node_with_header: roxmltree::Node<'a, 'a>,
node_with_trailer: roxmltree::Node<'a, 'a>,
node_with_components: roxmltree::Node<'a, 'a>,
node_with_messages: roxmltree::Node<'a, 'a>,
node_with_fields: roxmltree::Node<'a, 'a>,
builder: DictionaryBuilder,
}
impl<'a> QuickFixReader<'a> {
pub fn new(xml_document: &'a roxmltree::Document<'a>) -> ParseResult<Dictionary> {
let mut reader = Self::empty(&xml_document)?;
for child in reader.node_with_fields.children() {
if child.is_element() {
import_field(&mut reader.builder, child)?;
}
}
for child in reader.node_with_components.children() {
if child.is_element() {
let name = child
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string();
import_component(&mut reader.builder, child, name)?;
}
}
for child in reader.node_with_messages.children() {
if child.is_element() {
import_message(&mut reader.builder, child)?;
}
}
import_component(
&mut reader.builder,
reader.node_with_header,
"StandardHeader",
)?;
import_component(
&mut reader.builder,
reader.node_with_trailer,
"StandardTrailer",
)?;
Ok(reader.builder.build())
}
fn empty(xml_document: &'a roxmltree::Document<'a>) -> ParseResult<Self> {
let root = xml_document.root_element();
let find_tagged_child = |tag: &str| {
root.children()
.find(|n| n.has_tag_name(tag))
.ok_or_else(|| {
ParseDictionaryError::InvalidData(format!("<{}> tag not found", tag))
})
};
let version_type = root
.attribute("type")
.ok_or(ParseDictionaryError::InvalidData(
"No version attribute.".to_string(),
))?;
let version_major =
root.attribute("major")
.ok_or(ParseDictionaryError::InvalidData(
"No major version attribute.".to_string(),
))?;
let version_minor =
root.attribute("minor")
.ok_or(ParseDictionaryError::InvalidData(
"No minor version attribute.".to_string(),
))?;
let version_sp = root.attribute("servicepack").unwrap_or("0");
let version = format!(
"{}.{}.{}{}",
version_type,
version_major,
version_minor,
if version_sp != "0" {
format!("-SP{}", version_sp)
} else {
String::new()
}
);
Ok(QuickFixReader {
builder: DictionaryBuilder::new(version),
node_with_header: find_tagged_child("header")?,
node_with_trailer: find_tagged_child("trailer")?,
node_with_messages: find_tagged_child("messages")?,
node_with_components: find_tagged_child("components")?,
node_with_fields: find_tagged_child("fields")?,
})
}
}
fn import_field(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
) -> ParseResult<InternalId> {
if node.tag_name().name() != "field" {
return Err(ParseDictionaryError::InvalidFormat);
}
let data_type_iid = import_datatype(builder, node);
let value_restrictions = value_restrictions_from_node(node, data_type_iid);
let name = node
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string();
let tag = node
.attribute("number")
.ok_or(ParseDictionaryError::InvalidFormat)?
.parse()
.map_err(|_| ParseDictionaryError::InvalidFormat)?;
let field = FieldData {
name,
tag,
data_type_iid,
associated_data_tag: None,
value_restrictions,
required: true,
abbr_name: None,
base_category_abbr_name: None,
base_category_id: None,
description: None,
};
Ok(builder.add_field(field))
}
fn import_message(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
) -> ParseResult<InternalId> {
debug_assert_eq!(node.tag_name().name(), "message");
let category_iid = import_category(builder, node)?;
let mut layout_items = LayoutItems::new();
for child in node.children() {
if child.is_element() {
layout_items.push(import_layout_item(builder, child)?);
}
}
let message = MessageData {
name: node
.attribute("name")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string(),
msg_type: node
.attribute("msgtype")
.ok_or(ParseDictionaryError::InvalidFormat)?
.to_string(),
component_id: 0,
category_iid,
section_id: String::new(),
layout_items,
abbr_name: None,
required: true,
elaboration: None,
description: String::new(),
};
Ok(builder.add_message(message))
}
fn import_component<S: AsRef<str>>(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
name: S,
) -> ParseResult<InternalId> {
let mut layout_items = LayoutItems::new();
for child in node.children() {
if child.is_element() {
layout_items.push(import_layout_item(builder, child)?);
}
}
let component = ComponentData {
id: 0,
component_type: FixmlComponentAttributes::Block {
is_implicit: false,
is_repeating: false,
is_optimized: false,
},
layout_items,
category_iid: 0, name: name.as_ref().to_string(),
abbr_name: None,
};
let iid = builder.add_component(component);
match builder.symbol(KeyRef::ComponentByName(name.as_ref())) {
Some(x) => Ok(*x),
None => {
builder
.symbol_table
.insert(Key::ComponentByName(name.as_ref().to_string()), iid);
Ok(iid)
}
}
}
fn import_datatype(builder: &mut DictionaryBuilder, node: roxmltree::Node) -> InternalId {
debug_assert_eq!(node.tag_name().name(), "field");
let datatype = {
let quickfix_name = node.attribute("type").unwrap();
FixDatatype::from_quickfix_name(quickfix_name).unwrap()
};
let name = datatype.name();
match builder.symbol(KeyRef::DatatypeByName(name)) {
Some(x) => *x,
None => {
let iid = builder.data_types.len() as u32;
let data = DatatypeData {
datatype,
description: String::new(),
examples: Vec::new(),
};
builder.data_types.push(data);
builder
.symbol_table
.insert(Key::DatatypeByName(name.to_string()), iid);
iid
}
}
}
fn value_restrictions_from_node(
node: roxmltree::Node,
_datatype: InternalId,
) -> Option<Vec<FieldEnumData>> {
let mut values = Vec::new();
for child in node.children() {
if child.is_element() {
let variant = child.attribute("enum").unwrap().to_string();
let description = child.attribute("description").unwrap().to_string();
let enum_value = FieldEnumData {
value: variant,
description,
};
values.push(enum_value);
}
}
if values.len() == 0 {
None
} else {
Some(values)
}
}
fn import_layout_item(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
) -> ParseResult<LayoutItemData> {
debug_assert_ne!(builder.fields.len(), 0);
let name = node.attribute("name").unwrap();
let required = node.attribute("required").unwrap() == "Y";
let tag = node.tag_name().name();
let kind = match tag {
"field" => {
let field_iid = builder.symbol(KeyRef::FieldByName(name)).unwrap();
LayoutItemKindData::Field { iid: *field_iid }
}
"component" => {
let component_iid = import_component(builder, node, name)?;
LayoutItemKindData::Component { iid: component_iid }
}
"group" => {
let len_field_iid = *builder.symbol(KeyRef::FieldByName(name)).unwrap();
let mut items = Vec::new();
for child in node.children().filter(|n| n.is_element()) {
items.push(import_layout_item(builder, child)?);
}
LayoutItemKindData::Group {
len_field_iid,
items,
}
}
_ => {
return Err(ParseDictionaryError::InvalidFormat);
}
};
let item = LayoutItemData { required, kind };
Ok(item)
}
fn import_category(
builder: &mut DictionaryBuilder,
node: roxmltree::Node,
) -> ParseResult<InternalId> {
debug_assert_eq!(node.tag_name().name(), "message");
let name = node.attribute("msgcat").ok_or(ParseError::InvalidFormat)?;
Ok(match builder.symbol(KeyRef::CategoryByName(name)) {
Some(x) => *x,
None => {
let iid = builder.categories.len() as u32;
builder.categories.push(CategoryData {
name: name.to_string(),
fixml_filename: String::new(),
});
builder
.symbol_table
.insert(Key::CategoryByName(name.to_string()), iid);
iid
}
})
}
type ParseError = ParseDictionaryError;
type ParseResult<T> = Result<T, ParseError>;
#[derive(Clone, Debug)]
pub enum ParseDictionaryError {
InvalidFormat,
InvalidData(String),
}
}
#[cfg(test)]
mod test {
use super::*;
use std::collections::HashSet;
#[test]
fn fix44_quickfix_is_ok() {
let dict = Dictionary::fix44();
let msg_heartbeat = dict.message_by_name("Heartbeat").unwrap();
assert_eq!(msg_heartbeat.msg_type(), "0");
assert_eq!(msg_heartbeat.name(), "Heartbeat".to_string());
assert!(msg_heartbeat.layout().any(|c| {
if let LayoutItemKind::Field(f) = c.kind() {
f.name() == "TestReqID"
} else {
false
}
}));
}
#[test]
fn all_datatypes_are_used_at_least_once() {
for dict in Dictionary::all().iter() {
let datatypes_count = dict.iter_datatypes().count();
let mut datatypes = HashSet::new();
for field in dict.iter_fields() {
datatypes.insert(field.data_type().name().to_string());
}
assert_eq!(datatypes_count, datatypes.len());
}
}
#[test]
fn at_least_one_datatype() {
for dict in Dictionary::all().iter() {
assert!(dict.iter_datatypes().count() >= 1);
}
}
#[test]
fn std_header_and_trailer_always_present() {
for dict in Dictionary::all().iter() {
let std_header = dict.component_by_name("StandardHeader");
let std_trailer = dict.component_by_name("StandardTrailer");
assert!(std_header.is_some() && std_trailer.is_some());
}
}
#[test]
fn fix44_field_28_has_three_variants() {
let dict = Dictionary::fix44();
let field_28 = dict.field_by_tag(28).unwrap();
assert_eq!(field_28.name(), "IOITransType");
assert_eq!(field_28.enums().unwrap().count(), 3);
}
#[test]
fn fix44_field_36_has_no_variants() {
let dict = Dictionary::fix44();
let field_36 = dict.field_by_tag(36).unwrap();
assert_eq!(field_36.name(), "NewSeqNo");
assert!(field_36.enums().is_none());
}
#[test]
fn fix44_field_167_has_eucorp_variant() {
let dict = Dictionary::fix44();
let field_167 = dict.field_by_tag(167).unwrap();
assert_eq!(field_167.name(), "SecurityType");
assert!(field_167.enums().unwrap().any(|e| e.value() == "EUCORP"));
}
const INVALID_QUICKFIX_SPECS: &[&str] = &[
include_str!("test_data/quickfix_specs/empty_file.xml"),
include_str!("test_data/quickfix_specs/missing_components.xml"),
include_str!("test_data/quickfix_specs/missing_fields.xml"),
include_str!("test_data/quickfix_specs/missing_header.xml"),
include_str!("test_data/quickfix_specs/missing_messages.xml"),
include_str!("test_data/quickfix_specs/missing_trailer.xml"),
include_str!("test_data/quickfix_specs/root_has_no_type_attr.xml"),
include_str!("test_data/quickfix_specs/root_has_no_version_attrs.xml"),
include_str!("test_data/quickfix_specs/root_is_not_fix.xml"),
];
#[test]
fn invalid_quickfix_specs() {
for spec in INVALID_QUICKFIX_SPECS.iter() {
let dict = Dictionary::from_quickfix_spec(spec);
assert!(dict.is_err(), "{}", spec);
}
}
}