use crate::app::Version;
use quickfix::{ParseDictionaryError, QuickFixReader};
use std::collections::HashMap;
use std::io;
use std::ops::Range;
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
pub struct MsgType(u16);
impl MsgType {
pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
let bytes = self.0.to_be_bytes();
for byte in bytes.iter() {
writer.write(&[*byte])?;
}
Ok(())
}
}
impl From<&[u8]> for MsgType {
fn from(bytes: &[u8]) -> Self {
debug_assert!(bytes.len() <= std::mem::size_of::<u16>());
let mut value: u16 = 0;
for byte in bytes {
value = (value << 8) + (*byte as u16);
}
MsgType(value)
}
}
type InternalId = u32;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PKey {
#[allow(dead_code)]
Abbreviation(String),
CategoryByName(String),
ComponentByName(String),
DatatypeByName(String),
FieldByTag(u32),
FieldByName(String),
MessageByName(String),
MessageByMsgType(MsgType),
}
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
enum PKeyRef<'a> {
Abbreviation(&'a str),
CategoryByName(&'a str),
ComponentByName(&'a str),
DatatypeByName(&'a str),
FieldByTag(u32),
FieldByName(&'a str),
MessageByName(&'a str),
MessageByMsgType(MsgType),
}
impl PKey {
fn as_ref<'a>(&'a self) -> PKeyRef<'a> {
match self {
PKey::Abbreviation(s) => PKeyRef::Abbreviation(s.as_str()),
PKey::CategoryByName(s) => PKeyRef::CategoryByName(s.as_str()),
PKey::ComponentByName(s) => PKeyRef::ComponentByName(s.as_str()),
PKey::DatatypeByName(s) => PKeyRef::DatatypeByName(s.as_str()),
PKey::FieldByTag(t) => PKeyRef::FieldByTag(*t),
PKey::FieldByName(s) => PKeyRef::FieldByName(s.as_str()),
PKey::MessageByName(s) => PKeyRef::MessageByName(s.as_str()),
PKey::MessageByMsgType(t) => PKeyRef::MessageByMsgType(*t),
}
}
}
trait SymbolTableIndex {
fn to_key(&self) -> PKeyRef;
}
impl SymbolTableIndex for PKey {
fn to_key(&self) -> PKeyRef {
self.as_ref()
}
}
impl<'a> SymbolTableIndex for PKeyRef<'a> {
fn to_key(&self) -> PKeyRef {
*self
}
}
impl<'a> std::hash::Hash for dyn SymbolTableIndex + 'a {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_key().hash(state);
}
}
impl<'a> std::borrow::Borrow<dyn SymbolTableIndex + 'a> for PKey {
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()
}
}
#[derive(Clone, Debug)]
pub struct Dictionary {
version: String,
symbol_table: HashMap<PKey, InternalId>,
abbreviations: Vec<AbbreviatonData>,
data_types: Vec<DatatypeData>,
fields: Vec<FieldData>,
components: Vec<ComponentData>,
messages: Vec<MessageData>,
layout_items: Vec<LayoutItemData>,
categories: Vec<CategoryData>,
header: Vec<FieldData>,
}
impl Dictionary {
pub fn new<S: ToString>(version: S) -> Self {
Dictionary {
version: version.to_string(),
symbol_table: HashMap::new(),
abbreviations: Vec::new(),
data_types: Vec::new(),
fields: Vec::new(),
components: Vec::new(),
messages: Vec::new(),
layout_items: Vec::new(),
categories: vec![],
header: Vec::new(),
}
}
pub fn from_version(version: Version) -> Self {
Dictionary::save_definition_spec(version.get_quickfix_spec()).unwrap()
}
pub fn empty() -> Self {
Self::new("FIX.???")
}
pub fn get_version(&self) -> &str {
self.version.as_str()
}
fn symbol(&self, pkey: PKeyRef) -> Option<&u32> {
self.symbol_table.get(&pkey as &dyn SymbolTableIndex)
}
pub fn abbreviation_for<S: AsRef<str>>(&self, term: S) -> Option<Abbreviation> {
self.symbol(PKeyRef::Abbreviation(term.as_ref()))
.map(|iid| self.abbreviations.get(*iid as usize).unwrap())
.map(move |data| Abbreviation(self, data))
}
pub fn get_message_by_name<S: AsRef<str>>(&self, name: S) -> Option<Message> {
self.symbol(PKeyRef::MessageByName(name.as_ref()))
.map(|iid| self.messages.get(*iid as usize).unwrap())
.map(|data| Message(self, data))
}
pub fn get_message_by_msg_type<S: AsRef<str>>(&self, key: S) -> Option<Message> {
self.symbol(PKeyRef::MessageByMsgType(MsgType::from(
key.as_ref().as_bytes(),
)))
.map(|iid| self.messages.get(*iid as usize).unwrap())
.map(|data| Message(self, data))
}
pub fn get_component<S: AsRef<str>>(&self, name: S) -> Option<Component> {
self.symbol(PKeyRef::ComponentByName(name.as_ref()))
.map(|iid| self.components.get(*iid as usize).unwrap())
.map(|data| Component(self, data))
}
pub fn components(&self) -> impl Iterator<Item = Component> {
self.components
.iter()
.map(move |data| Component(&self, data))
}
pub fn messages(&self) -> impl Iterator<Item = Message> {
self.messages.iter().map(move |data| Message(&self, data))
}
pub fn categories(&self) -> impl Iterator<Item = Category> {
self.categories
.iter()
.map(move |data| Category(&self, data))
}
pub fn get_field(&self, tag: u32) -> Option<Field> {
self.symbol(PKeyRef::FieldByTag(tag))
.map(|iid| self.fields.get(*iid as usize).unwrap())
.map(|data| Field(self, data))
}
pub fn get_field_by_name<S: AsRef<str>>(&self, name: S) -> Option<Field> {
self.symbol(PKeyRef::FieldByName(name.as_ref()))
.map(|iid| self.fields.get(*iid as usize).unwrap())
.map(|data| Field(self, data))
}
pub fn save_definition_spec<S: AsRef<str>>(input: S) -> Result<Self, ParseDictionaryError> {
let xml_document = roxmltree::Document::parse(input.as_ref()).unwrap();
QuickFixReader::new(&xml_document)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BaseType {
Int,
Float,
Char,
String,
Data,
}
#[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 AbbreviatonData {
abbreviation: String,
is_last: bool,
}
#[derive(Debug)]
pub struct Abbreviation<'a>(&'a Dictionary, &'a AbbreviatonData);
impl<'a> Abbreviation<'a> {
pub fn term(&self) -> &str {
self.1.abbreviation.as_str()
}
}
#[derive(Clone, Debug)]
struct ComponentData {
id: usize,
component_type: ComponentType,
layout_items_iid_range: Range<u32>,
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 category(&self) -> Category {
let data = self.0.categories.get(self.1.category_iid as usize).unwrap();
Category(self.0, data)
}
pub fn items(&self) -> impl Iterator<Item = LayoutItem> {
let start = self.1.layout_items_iid_range.start as usize;
let end = self.1.layout_items_iid_range.end as usize;
self.0.layout_items[start..end]
.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 ComponentType {
BlockRepeating,
Block,
ImplicitBlockRepeating,
ImplicitBlock,
OptimisedBlockRepeating,
OptimisedImplicitBlockRepeating,
XMLDataBlock,
Message,
}
#[derive(Clone, Debug, PartialEq)]
struct DatatypeData {
pub name: String,
base_type: Option<String>,
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.name.as_str()
}
pub fn basetype(&self) -> BaseType {
str_to_basetype(self.1.name.as_str())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Enum {}
#[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)]
pub struct Field<'a>(&'a Dictionary, &'a FieldData);
fn str_to_basetype(s: &str) -> BaseType {
match s {
"STRING" => BaseType::String,
"UTCTIMESTAMP" => BaseType::String,
"CHAR" => BaseType::Char,
"INT" => BaseType::Int,
"LENGTH" => BaseType::Int,
"SEQNUM" => BaseType::Int,
"FLOAT" => BaseType::Float,
"DATA" => BaseType::Data,
_ => BaseType::Char, }
}
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,
};
let mut url = "https://www.onixs.biz/fix-dictionary/".to_string();
url.push_str(v);
url.push_str("/tagNum_");
url.push_str(self.1.tag.to_string().as_str());
url.push_str(".html");
url
}
pub fn basetype(&self) -> BaseType {
self.data_type().basetype()
}
pub fn name(&self) -> &str {
self.1.name.as_str()
}
pub fn tag(&self) -> u32 {
self.1.tag
}
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
.data_types
.get(self.1.data_type_iid as usize)
.unwrap();
Datatype(self.0, data)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FieldRef {
pub name: String,
pub required: char,
}
#[derive(Clone, Debug)]
enum LayoutItemKindData {
Component(u32),
Group(Range<u32>),
Field(u32),
}
#[derive(Clone, Debug)]
struct LayoutItemData {
required: bool,
kind: LayoutItemKindData,
}
#[derive(Clone, Debug)]
pub struct LayoutItem<'a>(&'a Dictionary, &'a LayoutItemData);
#[derive(Debug)]
pub enum LayoutItemKind<'a> {
Component(Component<'a>),
Group(),
Field(Field<'a>),
}
impl<'a> LayoutItem<'a> {
pub fn required(&self) -> bool {
self.1.required
}
pub fn kind(&self) -> LayoutItemKind {
match &self.1.kind {
LayoutItemKindData::Component(n) => LayoutItemKind::Component(Component(
self.0,
self.0.components.get(*n as usize).unwrap(),
)),
LayoutItemKindData::Group(_range) => {
LayoutItemKind::Group() }
LayoutItemKindData::Field(n) => {
LayoutItemKind::Field(Field(self.0, self.0.fields.get(*n as usize).unwrap()))
}
}
}
pub fn tag_text(&self) -> &str {
match &self.1.kind {
LayoutItemKindData::Component(n) => {
self.0.components.get(*n as usize).unwrap().name.as_str()
}
LayoutItemKindData::Group(_range) => "",
LayoutItemKindData::Field(n) => self.0.fields.get(*n as usize).unwrap().name.as_str(),
}
}
}
#[derive(Clone, Debug)]
struct MessageData {
component_id: u32,
msg_type: String,
name: String,
category_iid: InternalId,
section_id: String,
layout_items: Range<InternalId>,
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 component_id(&self) -> u32 {
self.1.component_id
}
pub fn layout(&self) -> impl Iterator<Item = LayoutItem> {
let start = self.1.layout_items.start as usize;
let end = self.1.layout_items.end as usize;
self.0.layout_items[start..end]
.iter()
.map(move |data| LayoutItem(self.0, data))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MsgContent {
component_id: usize,
pub tag_text: String,
pub reqd: char,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Section {}
#[derive(Clone, Debug, PartialEq)]
pub struct Value {
value_enum: String,
description: Option<String>,
}
mod quickfix {
use super::*;
fn add_datatype(dict: &mut Dictionary, datatype: DatatypeData) {
let iid = dict.data_types.len();
let name = datatype.name.clone();
dict.data_types.push(datatype);
dict.symbol_table
.insert(PKey::DatatypeByName(name), iid as u32);
}
fn add_all_datatypes(dict: &mut Dictionary) {
add_datatype(
dict,
DatatypeData {
name: "STRING".to_string(),
base_type: Some("string".to_string()),
description: String::new(),
examples: vec![],
},
);
add_datatype(
dict,
DatatypeData {
name: "INT".to_string(),
base_type: Some("int".to_string()),
description: String::new(),
examples: vec![],
},
);
add_datatype(
dict,
DatatypeData {
name: "CHAR".to_string(),
base_type: Some("char".to_string()),
description: String::new(),
examples: vec![],
},
);
}
pub(crate) 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>,
dict: Dictionary,
}
impl<'a> QuickFixReader<'a> {
pub fn new(
xml_document: &'a roxmltree::Document<'a>,
) -> Result<Dictionary, ParseDictionaryError> {
let mut reader = Self::empty(&xml_document)?;
add_all_datatypes(&mut reader.dict);
for child in reader.node_with_fields.children() {
if child.is_element() {
reader.add_field(child);
}
}
for child in reader.node_with_components.children() {
if child.is_element() {
reader.add_component(child);
}
}
for child in reader.node_with_messages.children() {
if child.is_element() {
reader.add_message(child);
}
}
reader.add_component_with_name(reader.node_with_header, "StandardHeader");
reader.add_component_with_name(reader.node_with_trailer, "StandardTrailer");
Ok(reader.dict)
}
fn empty(xml_document: &'a roxmltree::Document<'a>) -> Result<Self, ParseDictionaryError> {
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 majorr version attribute.".to_string(),
))?;
let version_minor =
root.attribute("minor")
.ok_or(ParseDictionaryError::InvalidData(
"No minor version attribute.".to_string(),
))?;
let version = format!("{}.{}.{}", version_type, version_major, version_minor);
Ok(QuickFixReader {
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")?,
dict: Dictionary::new(version),
})
}
fn add_field(&mut self, node: roxmltree::Node) {
let iid = self.dict.fields.len() as u32;
let field = FieldData::definition_from_node(&mut self.dict, node);
self.dict
.symbol_table
.insert(PKey::FieldByName(field.name.clone()), iid);
self.dict
.symbol_table
.insert(PKey::FieldByTag(field.tag as u32), iid);
self.dict.fields.push(field);
}
fn add_component_with_name<S: AsRef<str>>(&mut self, node: roxmltree::Node, name: S) {
let iid = self.dict.components.len();
let component =
ComponentData::definition_from_node_with_name(&mut self.dict, node, name.as_ref());
self.dict
.symbol_table
.insert(PKey::ComponentByName(name.as_ref().to_string()), iid as u32);
self.dict.components.push(component);
}
fn add_component(&mut self, node: roxmltree::Node) {
let iid = self.dict.components.len();
let component = ComponentData::definition_from_node(&mut self.dict, node);
self.dict
.symbol_table
.insert(PKey::ComponentByName(component.name.clone()), iid as u32);
self.dict.components.push(component);
}
fn add_message(&mut self, node: roxmltree::Node) {
let iid = self.dict.messages.len() as u32;
let message = MessageData::definition_from_node(&mut self.dict, node);
self.dict
.symbol_table
.insert(PKey::MessageByName(message.name.clone()), iid);
self.dict.symbol_table.insert(
PKey::MessageByMsgType(MsgType::from(message.msg_type.as_bytes())),
iid,
);
self.dict.messages.push(message);
}
}
impl ComponentData {
fn definition_from_node(dict: &mut Dictionary, node: roxmltree::Node) -> Self {
debug_assert_eq!(node.tag_name().name(), "component");
let name = node.attribute("name").unwrap().to_string();
Self::definition_from_node_with_name(dict, node, name)
}
fn definition_from_node_with_name<S: AsRef<str>>(
dict: &mut Dictionary,
node: roxmltree::Node,
name: S,
) -> Self {
let layout_start = dict.layout_items.len() as u32;
for child in node.children() {
if child.is_element() {
let item = LayoutItemData::save_definition(dict, child);
dict.layout_items.push(item);
}
}
let layout_end = dict.layout_items.len() as u32;
ComponentData {
id: 0,
component_type: ComponentType::Block,
layout_items_iid_range: layout_start..layout_end,
category_iid: 0, name: name.as_ref().to_string(),
abbr_name: None,
}
}
fn get_or_create_iid_from_ref(dict: &mut Dictionary, node: roxmltree::Node) -> InternalId {
debug_assert_eq!(node.tag_name().name(), "component");
let name = node.attribute("name").unwrap();
match dict.symbol(PKeyRef::ComponentByName(name)) {
Some(x) => *x,
None => {
let iid = dict.data_types.len() as u32;
let data = ComponentData {
id: 0,
component_type: ComponentType::Block,
layout_items_iid_range: 0..0,
name: name.to_string(),
category_iid: 0, abbr_name: None,
};
dict.components.push(data);
dict.symbol_table
.insert(PKey::ComponentByName(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)
}
}
impl FieldData {
fn definition_from_node(dict: &mut Dictionary, node: roxmltree::Node) -> Self {
debug_assert_eq!(node.tag_name().name(), "field");
let data_type_iid = DatatypeData::get_or_create_iid_from_ref(dict, node);
let value_restrictions = value_restrictions_from_node(node, data_type_iid);
FieldData {
name: node.attribute("name").unwrap().to_string(),
tag: node.attribute("number").unwrap().parse().unwrap(),
data_type_iid: 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,
}
}
}
impl DatatypeData {
fn get_or_create_iid_from_ref(dict: &mut Dictionary, node: roxmltree::Node) -> InternalId {
debug_assert_eq!(node.tag_name().name(), "field");
let name = node.attribute("type").unwrap();
match dict.symbol(PKeyRef::DatatypeByName(name)) {
Some(x) => *x,
None => {
let iid = dict.data_types.len() as u32;
let data = DatatypeData {
name: name.to_string(),
description: String::new(),
examples: Vec::new(),
base_type: None,
};
dict.data_types.push(data);
dict.symbol_table
.insert(PKey::DatatypeByName(name.to_string()), iid);
iid
}
}
}
}
impl LayoutItemData {
fn save_definition(dict: &mut Dictionary, node: roxmltree::Node) -> Self {
debug_assert_ne!(dict.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 = dict.symbol(PKeyRef::FieldByName(name)).unwrap();
LayoutItemKindData::Field(*field_iid)
}
"component" => {
let component_iid = ComponentData::get_or_create_iid_from_ref(dict, node);
LayoutItemKindData::Component(component_iid)
}
"group" => {
let start_range = dict.layout_items.len() as u32;
let items = node
.children()
.filter(|n| n.is_element())
.map(|child| LayoutItemData::save_definition(dict, child))
.count();
LayoutItemKindData::Group(start_range..(start_range + items as u32))
}
_ => {
panic!("Invalid tag!")
}
};
LayoutItemData { required, kind }
}
}
impl MessageData {
fn definition_from_node(dict: &mut Dictionary, node: roxmltree::Node) -> Self {
debug_assert_eq!(node.tag_name().name(), "message");
let category_iid = CategoryData::get_or_create_iid_from_ref(dict, node);
let layout_start = dict.layout_items.len() as u32;
for child in node.children() {
if child.is_element() {
let data = LayoutItemData::save_definition(dict, child);
dict.layout_items.push(data);
}
}
let layout_end = dict.layout_items.len() as u32;
MessageData {
name: node.attribute("name").unwrap().to_string(),
msg_type: node.attribute("msgtype").unwrap().to_string(),
component_id: 0,
category_iid,
section_id: String::new(),
layout_items: layout_start..layout_end,
abbr_name: None,
required: true,
elaboration: None,
description: String::new(),
}
}
}
impl CategoryData {
fn get_or_create_iid_from_ref(dict: &mut Dictionary, node: roxmltree::Node) -> InternalId {
debug_assert_eq!(node.tag_name().name(), "message");
let name = node.attribute("msgcat").unwrap();
match dict.symbol(PKeyRef::CategoryByName(name)) {
Some(x) => *x,
None => {
let iid = dict.categories.len() as u32;
dict.categories.push(CategoryData {
name: name.to_string(),
fixml_filename: String::new(),
});
dict.symbol_table
.insert(PKey::CategoryByName(name.to_string()), iid);
iid
}
}
}
}
#[derive(Clone, Debug)]
pub enum ParseDictionaryError {
InvalidFormat,
InvalidData(String),
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::app::Version;
use quickcheck::QuickCheck;
use std::convert::TryInto;
#[test]
fn msg_type_conversion() {
fn prop(val: u16) -> bool {
let bytes = val.to_le_bytes();
let msg_type = MsgType::from(&bytes[..]);
let mut buffer = vec![0, 0];
msg_type.write(&mut &mut buffer[..]).unwrap();
val == u16::from_le_bytes((&buffer[..]).try_into().unwrap())
}
QuickCheck::new()
.tests(1000)
.quickcheck(prop as fn(u16) -> bool)
}
#[test]
fn fixt11_quickfix_is_ok() {
let dict = Dictionary::from_version(Version::Fixt11);
let msg_heartbeat = dict.get_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 dictionary_save_definition_spec_is_ok() {
for version in Version::all() {
Dictionary::from_version(version);
}
}
#[test]
fn fix44_field_28_has_three_variants() {
let dict = Dictionary::from_version(Version::Fix44);
let field_28 = dict.get_field(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::from_version(Version::Fix44);
let field_36 = dict.get_field(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::from_version(Version::Fix44);
let field_167 = dict.get_field(167).unwrap();
assert_eq!(field_167.name(), "SecurityType");
assert!(field_167.enums().unwrap().any(|e| e.value() == "EUCORP"));
}
}