From 0dd8b9152ffa636c2fd543c08c8013ae9cb3e689 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Thu, 19 Sep 2024 14:47:22 -0500 Subject: [PATCH 1/6] cleanup/nullable-ize FixValues/Session/DataDict also split up giant DataDictionaryTests file --- QuickFIXn/DataDictionary/DataDictionary.cs | 326 +++++++------- QuickFIXn/FixValues.cs | 123 +++--- QuickFIXn/QuickFix.csproj | 1 + QuickFIXn/Session.cs | 18 +- RELEASE_NOTES.md | 3 + UnitTests/DataDictionaryTests.cs | 476 +++------------------ UnitTests/DataDictionary_ValidateTests.cs | 378 ++++++++++++++++ spec/test/group_begins_group.xml | 23 +- 8 files changed, 702 insertions(+), 646 deletions(-) create mode 100644 UnitTests/DataDictionary_ValidateTests.cs diff --git a/QuickFIXn/DataDictionary/DataDictionary.cs b/QuickFIXn/DataDictionary/DataDictionary.cs index 48b528c68..0372b633c 100644 --- a/QuickFIXn/DataDictionary/DataDictionary.cs +++ b/QuickFIXn/DataDictionary/DataDictionary.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Xml; -using System.Xml.XPath; using QuickFix.Fields; namespace QuickFix.DataDictionary @@ -12,22 +12,21 @@ namespace QuickFix.DataDictionary /// public class DataDictionary { - public string MajorVersion { get; private set; } - public string MinorVersion { get; private set; } - public string Version { get; private set; } - public Dictionary FieldsByTag = new Dictionary(); - public Dictionary FieldsByName = new Dictionary(); - public Dictionary Messages = new Dictionary(); - private XmlDocument RootDoc; - private Dictionary ComponentsByName = new Dictionary(); + public string? MajorVersion { get; private set; } + public string? MinorVersion { get; private set; } + public string? Version { get; private set; } + public readonly Dictionary FieldsByTag = new(); + public readonly Dictionary FieldsByName = new(); + public readonly Dictionary Messages = new(); + private readonly Dictionary _componentsByName = new(); public bool CheckFieldsOutOfOrder { get; set; } public bool CheckFieldsHaveValues { get; set; } public bool CheckUserDefinedFields { get; set; } public bool AllowUnknownMessageFields { get; set; } - public DDMap Header = new DDMap(); - public DDMap Trailer = new DDMap(); + public DDMap Header = new(); + public DDMap Trailer = new(); public DataDictionary() { @@ -41,7 +40,7 @@ public DataDictionary() /// Initialize a data dictionary from a file path /// /// - public DataDictionary(String path) + public DataDictionary(string path) : this() { Load(path); @@ -64,69 +63,53 @@ public DataDictionary(Stream stream) public DataDictionary(DataDictionary src) : this() { - this.Messages = src.Messages; - this.FieldsByName = src.FieldsByName; - this.FieldsByTag = src.FieldsByTag; - if (null != src.MajorVersion) - this.MajorVersion = src.MajorVersion; - if (null != src.MinorVersion) - this.MinorVersion = src.MinorVersion; - if (null != src.Version) - this.Version = src.Version; - this.CheckFieldsHaveValues = src.CheckFieldsHaveValues; - this.CheckFieldsOutOfOrder = src.CheckFieldsOutOfOrder; - this.CheckUserDefinedFields = src.CheckUserDefinedFields; - this.Header = src.Header; - this.Trailer = src.Trailer; + Messages = src.Messages; + FieldsByName = src.FieldsByName; + FieldsByTag = src.FieldsByTag; + MajorVersion = src.MajorVersion; + MinorVersion = src.MinorVersion; + Version = src.Version; + CheckFieldsHaveValues = src.CheckFieldsHaveValues; + CheckFieldsOutOfOrder = src.CheckFieldsOutOfOrder; + CheckUserDefinedFields = src.CheckUserDefinedFields; + Header = src.Header; + Trailer = src.Trailer; } - public static void Validate(Message message, DataDictionary sessionDataDict, DataDictionary appDataDict, string beginString, string msgType) + public static void Validate(Message message, DataDictionary? transportDataDict, DataDictionary appDataDict, string beginString, string msgType) { - bool bodyOnly = (null == sessionDataDict); - - if ((null != sessionDataDict) && (null != sessionDataDict.Version)) - if (!sessionDataDict.Version.Equals(beginString)) - throw new UnsupportedVersion(beginString); + if (transportDataDict?.Version is not null && !transportDataDict.Version.Equals(beginString)) { + throw new UnsupportedVersion(beginString); + } - if (((null != sessionDataDict) && sessionDataDict.CheckFieldsOutOfOrder) || ((null != appDataDict) && appDataDict.CheckFieldsOutOfOrder)) - { + // TODO: this check is not well-designed. The ex is only triggered if Message.FromString sets an invalid state. + // (no other Message function can set this state!) + // Furthermore, this is the only time we throw a TagOutOfOrder exception. + // It's not clear to me that this logic is useful. + if(transportDataDict?.CheckFieldsOutOfOrder == true || appDataDict.CheckFieldsOutOfOrder) { if (!message.HasValidStructure(out var field)) throw new TagOutOfOrder(field); } - if ((null != appDataDict) && (null != appDataDict.Version)) + if(appDataDict.Version is not null) { appDataDict.CheckMsgType(msgType); appDataDict.CheckHasRequired(message, msgType); } - if (!bodyOnly) + if (transportDataDict is not null) { - sessionDataDict.Iterate(message.Header, msgType); - sessionDataDict.Iterate(message.Trailer, msgType); + transportDataDict.Iterate(message.Header, msgType); + transportDataDict.Iterate(message.Trailer, msgType); } appDataDict.Iterate(message, msgType); } + // TODO Get rid of this. All calls can use the static call. public void Validate(Message message, string beginString, string msgType) { - Validate(message, false, beginString, msgType); - } - - /// - /// Validate the message body, with header and trailer fields being validated conditionally - /// - /// the message - /// whether to validate just the message body, or to validate the header and trailer sections as well - /// - /// - public void Validate(Message message, bool bodyOnly, string beginString, string msgType) - { - DataDictionary sessionDataDict = null; - if (!bodyOnly) - sessionDataDict = this; - Validate(message, sessionDataDict, this, beginString, msgType); + Validate(message, this, this, beginString, msgType); } public static void CheckHasNoRepeatedTags(FieldMap map) @@ -135,13 +118,10 @@ public static void CheckHasNoRepeatedTags(FieldMap map) throw new RepeatedTag(map.RepeatedTags[0].Tag); } - public DDMap GetMapForMessage(string msgType) - { - if (Messages.ContainsKey(msgType)) - { - return Messages[msgType]; - } - return null; + public DDMap? GetMapForMessage(string msgType) { + return Messages.ContainsKey(msgType) + ? Messages[msgType] + : null; } public void CheckMsgType(string msgType) @@ -179,13 +159,13 @@ public void CheckHasRequired(Message message, string msgType) public void Iterate(FieldMap map, string msgType) { - DataDictionary.CheckHasNoRepeatedTags(map); + CheckHasNoRepeatedTags(map); // check non-group fields int lastField = 0; - foreach (KeyValuePair kvp in map) + foreach (KeyValuePair kvp in map) { - Fields.IField field = kvp.Value; + IField field = kvp.Value; if (lastField != 0 && field.Tag == lastField) throw new RepeatedTag(lastField); CheckHasValue(field); @@ -222,12 +202,12 @@ public void Iterate(FieldMap map, string msgType) public void IterateGroup(Group group, DDGrp ddgroup, string msgType) { - DataDictionary.CheckHasNoRepeatedTags(group); + CheckHasNoRepeatedTags(group); int lastField = 0; - foreach (KeyValuePair kvp in group) + foreach (KeyValuePair kvp in group) { - Fields.IField field = kvp.Value; + IField field = kvp.Value; if (lastField != 0 && field.Tag == lastField) throw new RepeatedTag(lastField); CheckHasValue(field); @@ -265,7 +245,7 @@ public void IterateGroup(Group group, DDGrp ddgroup, string msgType) /// does nothing if configuration has "ValidateFieldsHaveValues=N". /// /// - public void CheckHasValue(Fields.IField field) + public void CheckHasValue(IField field) { if (this.CheckFieldsHaveValues && (field.ToString().Length < 1)) throw new NoTagValue(field.Tag); @@ -275,7 +255,7 @@ public void CheckHasValue(Fields.IField field) /// Check that the field contents match the DataDictionary-defined type /// /// - public void CheckValidFormat(Fields.IField field) + public void CheckValidFormat(IField field) { try { @@ -309,8 +289,6 @@ public void CheckValidFormat(Fields.IField field) Fields.Converters.DateTimeConverter.ParseToDateOnly(field.ToString()); else if (type == typeof(TimeOnlyField)) Fields.Converters.DateTimeConverter.ParseToTimeOnly(field.ToString()); - return; - } catch (FieldConvertError e) { @@ -320,13 +298,12 @@ public void CheckValidFormat(Fields.IField field) public bool TryGetFieldType(int tag, out Type result) { - - if (FieldsByTag.ContainsKey(tag)) + if (FieldsByTag.TryGetValue(tag, out DDField? value)) { - result = FieldsByTag[tag].FieldType; + result = value.FieldType; return true; } - result = null; + result = typeof(object); // doesn't matter return false; } @@ -339,9 +316,7 @@ public void CheckValidTagNumber(int tag) if (AllowUnknownMessageFields) return; if (!FieldsByTag.ContainsKey(tag)) - { throw new InvalidTagNumber(tag); - } } /// @@ -349,19 +324,18 @@ public void CheckValidTagNumber(int tag) /// (If field is unknown, ignore it. It's not this function's job to test that.) /// /// - public void CheckValue(Fields.IField field) + public void CheckValue(IField field) { if (FieldsByTag.TryGetValue(field.Tag, out var fld)) { if (fld.HasEnums()) { - if (fld.IsMultipleValueFieldWithEnums) - { + if (fld.IsMultipleValueFieldWithEnums) { string[] splitted = field.ToString().Split(' '); - foreach (string value in splitted) - if (!fld.EnumDict.ContainsKey(value)) - throw new IncorrectTagValue(field.Tag); + if (splitted.Any(value => !fld.EnumDict.ContainsKey(value))) { + throw new IncorrectTagValue(field.Tag); + } } else if (!fld.EnumDict.ContainsKey(field.ToString())) throw new IncorrectTagValue(field.Tag); @@ -374,12 +348,11 @@ public void CheckValue(Fields.IField field) /// /// /// - public void CheckIsInMessage(Fields.IField field, string msgType) + public void CheckIsInMessage(IField field, string msgType) { if (AllowUnknownMessageFields) return; - DDMap dd; - if (Messages.TryGetValue(msgType, out dd)) + if (Messages.TryGetValue(msgType, out DDMap? dd)) if (dd.Fields.ContainsKey(field.Tag)) return; throw new TagNotDefinedForMessage(field.Tag, msgType); @@ -391,7 +364,7 @@ public void CheckIsInMessage(Fields.IField field, string msgType) /// /// /// Message type that contains the group (included in exceptions thrown on failure) - public void CheckIsInGroup(Fields.IField field, DDGrp ddgrp, string msgType) + public static void CheckIsInGroup(IField field, DDGrp ddgrp, string msgType) { if (ddgrp.IsField(field.Tag)) return; @@ -404,7 +377,7 @@ public void CheckIsInGroup(Fields.IField field, DDGrp ddgrp, string msgType) /// a group's counter field /// the FieldMap that contains the group being checked /// msg type of message that is/contains - public void CheckGroupCount(Fields.IField field, FieldMap map, string msgType) + public void CheckGroupCount(IField field, FieldMap map, string msgType) { if (IsGroup(msgType, field.Tag)) { @@ -417,11 +390,7 @@ public void CheckGroupCount(Fields.IField field, FieldMap map, string msgType) public bool IsGroup(string msgType, int tag) { - if (Messages.ContainsKey(msgType)) - { - return Messages[msgType].IsGroup(tag); - } - return false; + return Messages.ContainsKey(msgType) && Messages[msgType].IsGroup(tag); } /// @@ -429,11 +398,9 @@ public bool IsGroup(string msgType, int tag) /// /// /// - public bool ShouldCheckTag(Fields.IField field) + public bool ShouldCheckTag(IField field) { - if (!this.CheckUserDefinedFields && (field.Tag >= Fields.Limits.USER_MIN)) - return false; - return true; + return this.CheckUserDefinedFields || field.Tag < Fields.Limits.USER_MIN; } public bool IsHeaderField(int tag) @@ -446,7 +413,7 @@ public bool IsTrailerField(int tag) return Trailer.IsField(tag); } - public void Load(String path) + public void Load(string path) { var stream = new FileStream(path, FileMode.Open, FileAccess.Read); Load(stream); @@ -454,47 +421,53 @@ public void Load(String path) public void Load(Stream stream) { - XmlReaderSettings readerSettings = new XmlReaderSettings(); - readerSettings.IgnoreComments = true; - - using (XmlReader reader = XmlReader.Create(stream, readerSettings)) + XmlReaderSettings readerSettings = new() { - RootDoc = new XmlDocument(); - RootDoc.Load(reader); - SetVersionInfo(RootDoc); - ParseFields(RootDoc); - CacheComponents(RootDoc); - ParseMessages(RootDoc); - ParseHeader(RootDoc); - ParseTrailer(RootDoc); + IgnoreComments = true + }; + + using (XmlReader reader = XmlReader.Create(stream, readerSettings)) { + XmlDocument rootDoc = new(); + rootDoc.Load(reader); + SetVersionInfo(rootDoc); + ParseFields(rootDoc); + CacheComponents(rootDoc); + ParseMessages(rootDoc); + ParseHeader(rootDoc); + ParseTrailer(rootDoc); } } - public Boolean FieldHasValue(int tag, String val) + public bool FieldHasValue(int tag, string val) { return FieldsByTag[tag].EnumDict.ContainsKey(val); } private void SetVersionInfo(XmlDocument doc) { - MajorVersion = doc.SelectSingleNode("/fix/@major").Value; - MinorVersion = doc.SelectSingleNode("/fix/@minor").Value; - string type; - XmlNode node = doc.SelectSingleNode("/fix/@type"); - if (node == null) - type = "FIX";//FIXME has to be a better way to do this, but for now, only >=5.0 have type - else - type = node.Value; + XmlNode? majorVerNode = doc.SelectSingleNode("/fix/@major"); + XmlNode? minorVerNode = doc.SelectSingleNode("/fix/@minor"); + + if (majorVerNode is null || minorVerNode is null) { + throw new DictionaryParseException("Failed to find attributes 'major' and 'minor' in tag"); + } + + MajorVersion = majorVerNode.Value; + MinorVersion = minorVerNode.Value; + XmlNode? node = doc.SelectSingleNode("/fix/@type"); + string typeAtt = node?.Value ?? "FIX"; // att is omitted in non-FIXT DDs - if (!type.Equals("FIX") && !type.Equals("FIXT")) - throw new System.ArgumentException("Type must be FIX of FIXT in cofig"); - Version = String.Format("{0}.{1}.{2}", type, MajorVersion, MinorVersion); + if (!typeAtt.Equals("FIX") && !typeAtt.Equals("FIXT")) + throw new DictionaryParseException(" 'type' attribute must be FIX or FIXT (assumes FIX if not present)"); + Version = $"{typeAtt}.{MajorVersion}.{MinorVersion}"; } private void ParseFields(XmlDocument doc) { - XmlNodeList nodeList = doc.SelectNodes("//fields/field"); + XmlNodeList? nodeList = doc.SelectNodes("//fields/field"); + if (nodeList is null) + throw new DictionaryParseException("No tag in dictionary"); foreach (XmlNode fldEl in nodeList) { DDField fld = NewField(fldEl); @@ -505,28 +478,54 @@ private void ParseFields(XmlDocument doc) private void CacheComponents(XmlDocument doc) { - XmlNodeList nodeList = doc.SelectNodes("//components/component"); + XmlNodeList? nodeList = doc.SelectNodes("//components/component"); + if(nodeList is null) + throw new DictionaryParseException("No tag in dictionary"); foreach (XmlNode compEl in nodeList) { - ComponentsByName[compEl.Attributes["name"].Value] = compEl; + XmlAttribute? nameAtt = compEl.Attributes?["name"]; + if (nameAtt != null) + _componentsByName[nameAtt.Value] = compEl; } } private DDField NewField(XmlNode fldEl) { - String tagstr = fldEl.Attributes["number"].Value; - String name = fldEl.Attributes["name"].Value; - String fldType = fldEl.Attributes["type"].Value; + XmlAttribute? tagAtt = fldEl.Attributes?["number"]; + XmlAttribute? nameAtt = fldEl.Attributes?["name"]; + XmlAttribute? typeAtt = fldEl.Attributes?["type"]; + + if (tagAtt is null) + throw new DictionaryParseException("A is missing its 'number' attribute"); + string tagstr = tagAtt.Value; + + if(nameAtt is null) + throw new DictionaryParseException($" is missing its 'name' attribute"); + if(typeAtt is null) + throw new DictionaryParseException($" is missing its 'type' attribute"); + + string name = nameAtt.Value; + string fldType = typeAtt.Value; + int tag = QuickFix.Fields.Converters.IntConverter.Convert(tagstr); - Dictionary enums = new Dictionary(); + Dictionary enums = new Dictionary(); if (fldEl.HasChildNodes) { - foreach (XmlNode enumEl in fldEl.SelectNodes(".//value")) + XmlNodeList? enumNodeList = fldEl.SelectNodes(".//value"); + if (enumNodeList is not null) { - string description = String.Empty; - if (enumEl.Attributes["description"] != null) - description = enumEl.Attributes["description"].Value; - enums[enumEl.Attributes["enum"].Value] = description; + foreach (XmlNode enumEl in enumNodeList) + { + string description = string.Empty; + if (enumEl.Attributes?["description"] is not null) + description = enumEl.Attributes["description"]!.Value; + + XmlAttribute? enumAtt = enumEl.Attributes?["enum"]; + if (enumAtt is null) + throw new DictionaryParseException( + $"Field {tagstr} has an that is missing its 'enum' attribute"); + enums[enumAtt.Value] = description; + } } } return new DDField(tag, name, enums, fldType); @@ -534,24 +533,34 @@ private DDField NewField(XmlNode fldEl) private void ParseMessages(XmlDocument doc) { - XmlNodeList nodeList = doc.SelectNodes("//messages/message"); + XmlNodeList? nodeList = doc.SelectNodes("//messages/message"); + if (nodeList is null) + throw new DictionaryParseException("No tag in dictionary"); foreach (XmlNode msgEl in nodeList) { DDMap msg = new DDMap(); - ParseMsgEl(msgEl, msg); - String msgtype = msgEl.Attributes["msgtype"].Value; + ParseMsgNode(msgEl, msg); + string? msgtype = msgEl.Attributes?["msgtype"]?.Value; + if (msgtype is null) + throw new DictionaryParseException("A is missing its 'msgtype' attribute"); Messages.Add(msgtype, msg); } } private void ParseHeader(XmlDocument doc) { - ParseMsgEl(doc.SelectSingleNode("//header"), Header); + XmlNode? node = doc.SelectSingleNode("//header"); + if (node is null) + throw new DictionaryParseException("No
tag in the dictionary"); + ParseMsgNode(node, Header); } private void ParseTrailer(XmlDocument doc) { - ParseMsgEl(doc.SelectSingleNode("//trailer"), Trailer); + XmlNode? node = doc.SelectSingleNode("//trailer"); + if (node is null) + throw new DictionaryParseException("No tag in the dictionary"); + ParseMsgNode(node, Trailer); } /// @@ -568,23 +577,13 @@ internal static void VerifyChildNode(XmlNode childNode, XmlNode parentNode) } if (childNode.Attributes["name"] == null) { - string messageTypeName = (parentNode.Attributes["name"] != null) ? parentNode.Attributes["name"].Value : parentNode.Name; + string messageTypeName = parentNode.Attributes?["name"]?.Value ?? parentNode.Name; throw new DictionaryParseException($"Malformed data dictionary: Found '{childNode.Name}' node without 'name' within parent '{parentNode.Name}/{messageTypeName}'"); } } /// - /// Parse a message element. Its data is added to parameter `ddmap`. - /// - /// - /// - private void ParseMsgEl(XmlNode node, DDMap ddmap) - { - ParseMsgEl(node, ddmap, null); - } - - /// - /// Parse a message element. Its data is added to parameter `ddmap`. + /// Parse a message node (message, group, or component). Its data is added to parameter `ddmap`. /// /// a message, group, or component node /// the still-being-constructed DDMap for this node @@ -592,7 +591,7 @@ private void ParseMsgEl(XmlNode node, DDMap ddmap) /// If non-null, parsing is inside a component that is required (true) or not (false). /// If null, parser is not inside a component. /// - private void ParseMsgEl(XmlNode node, DDMap ddmap, bool? componentRequired) + private void ParseMsgNode(XmlNode node, DDMap ddmap, bool? componentRequired = null) { /* // This code is great for debugging DD parsing issues. @@ -602,7 +601,8 @@ private void ParseMsgEl(XmlNode node, DDMap ddmap, bool? componentRequired) Console.WriteLine(s); */ - string messageTypeName = (node.Attributes["name"] != null) ? node.Attributes["name"].Value : node.Name; + // TODO: I don't think node.Name is an acceptable alternative + string messageTypeName = node.Attributes?["name"]?.Value ?? node.Name; if (!node.HasChildNodes) { return; } foreach (XmlNode childNode in node.ChildNodes) @@ -617,20 +617,24 @@ private void ParseMsgEl(XmlNode node, DDMap ddmap, bool? componentRequired) VerifyChildNode(childNode, node); - var nameAttribute = childNode.Attributes["name"].Value; + string? nameAttribute = childNode.Attributes?["name"]?.Value; + if (nameAttribute is null) + { + throw new DictionaryParseException( + $"A child node within '{messageTypeName} is missing its 'name' attribute"); + } switch (childNode.Name) { case "field": case "group": - if (FieldsByName.ContainsKey(nameAttribute) == false) + if (FieldsByName.TryGetValue(nameAttribute, out DDField? fld) == false) { throw new DictionaryParseException( $"Field '{nameAttribute}' is not defined in section."); } - DDField fld = FieldsByName[nameAttribute]; - bool required = (childNode.Attributes["required"]?.Value == "Y") && componentRequired.GetValueOrDefault(true); + bool required = childNode.Attributes?["required"]?.Value == "Y" && componentRequired.GetValueOrDefault(true); if (required) ddmap.ReqFields.Add(fld.Tag); @@ -644,19 +648,19 @@ private void ParseMsgEl(XmlNode node, DDMap ddmap, bool? componentRequired) if (childNode.Name == "group") { - DDGrp grp = new DDGrp(); + DDGrp grp = new(); grp.NumFld = fld.Tag; if (required) grp.Required = true; - ParseMsgEl(childNode, grp); + ParseMsgNode(childNode, grp); ddmap.Groups.Add(fld.Tag, grp); } break; case "component": - XmlNode compNode = ComponentsByName[nameAttribute]; - ParseMsgEl(compNode, ddmap, (childNode.Attributes["required"]?.Value == "Y")); + XmlNode compNode = _componentsByName[nameAttribute]; + ParseMsgNode(compNode, ddmap, (childNode.Attributes?["required"]?.Value == "Y")); break; default: diff --git a/QuickFIXn/FixValues.cs b/QuickFIXn/FixValues.cs index 16500f752..91142ad71 100755 --- a/QuickFIXn/FixValues.cs +++ b/QuickFIXn/FixValues.cs @@ -4,21 +4,20 @@ namespace QuickFix { public class FixValue { - private T value_; - private string description_; + private T _value; - public T Value { get { return value_; } } - public string Description { get { return description_; } } + public T Value => _value; + public string Description { get; } public FixValue(T value, string description) { - value_ = value; - description_ = description; + _value = value; + Description = description; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if ((null == obj) || (this.GetType() != obj.GetType())) + if (obj is null || (this.GetType() != obj.GetType())) return false; FixValue rhs = (FixValue)obj; return this.Value.Equals(rhs.Value); @@ -26,12 +25,12 @@ public override bool Equals(object obj) public override int GetHashCode() { - return value_.GetHashCode(); + return _value.GetHashCode(); } public override string ToString() { - return description_; + return Description; } } @@ -39,8 +38,6 @@ namespace FixValues { public static class ApplVerID { - public const string FIX27 = "0"; - public const string FIX30 = "1"; public const string FIX40 = "2"; public const string FIX41 = "3"; public const string FIX42 = "4"; @@ -52,46 +49,34 @@ public static class ApplVerID public static string FromBeginString(string beginString) { - if (BeginString.FIX40.Equals(beginString)) - return ApplVerID.FIX40; - else if (BeginString.FIX41.Equals(beginString)) - return ApplVerID.FIX41; - else if (BeginString.FIX42.Equals(beginString)) - return ApplVerID.FIX42; - else if (BeginString.FIX43.Equals(beginString)) - return ApplVerID.FIX43; - else if (BeginString.FIX44.Equals(beginString)) - return ApplVerID.FIX44; - else if (BeginString.FIX50.Equals(beginString)) - return ApplVerID.FIX50; - else if (BeginString.FIX50SP1.Equals(beginString)) - return ApplVerID.FIX50SP1; - else if (BeginString.FIX50SP2.Equals(beginString)) - return ApplVerID.FIX50SP2; - else - return beginString; + return beginString switch + { + BeginString.FIX40 => FIX40, + BeginString.FIX41 => FIX41, + BeginString.FIX42 => FIX42, + BeginString.FIX43 => FIX43, + BeginString.FIX44 => FIX44, + BeginString.FIX50 => FIX50, + BeginString.FIX50SP1 => FIX50SP1, + BeginString.FIX50SP2 => FIX50SP2, + _ => beginString + }; } - public static string ToBeginString(string applVerId) - { - if (applVerId == ApplVerID.FIX40) - return BeginString.FIX40; - else if (applVerId == ApplVerID.FIX41) - return BeginString.FIX41; - else if (applVerId == ApplVerID.FIX42) - return BeginString.FIX42; - else if (applVerId == ApplVerID.FIX43) - return BeginString.FIX43; - else if (applVerId == ApplVerID.FIX44) - return BeginString.FIX44; - else if (applVerId == ApplVerID.FIX50) - return BeginString.FIX50; - else if (applVerId == ApplVerID.FIX50SP1) - return BeginString.FIX50SP1; - else if (applVerId == ApplVerID.FIX50SP2) - return BeginString.FIX50SP2; - else - throw new System.ArgumentException($"ApplVerId parameter '{applVerId}' does not map to a known BeginString"); + public static string ToBeginString(string applVerId) { + return applVerId switch + { + FIX40 => BeginString.FIX40, + FIX41 => BeginString.FIX41, + FIX42 => BeginString.FIX42, + FIX43 => BeginString.FIX43, + FIX44 => BeginString.FIX44, + FIX50 => BeginString.FIX50, + FIX50SP1 => BeginString.FIX50SP1, + FIX50SP2 => BeginString.FIX50SP2, + _ => throw new System.ArgumentException( + $"ApplVerId parameter '{applVerId}' does not map to a known BeginString") + }; } } @@ -112,25 +97,25 @@ public static class BeginString public class SessionRejectReason : FixValue { - public static SessionRejectReason INVALID_TAG_NUMBER = new SessionRejectReason(0, "Invalid tag number"); - public static SessionRejectReason REQUIRED_TAG_MISSING = new SessionRejectReason(1, "Required tag missing"); - public static SessionRejectReason TAG_NOT_DEFINED_FOR_THIS_MESSAGE_TYPE = new SessionRejectReason(2, "Tag not defined for this message type"); - public static SessionRejectReason UNDEFINED_TAG = new SessionRejectReason(3, "Undefined Tag"); - public static SessionRejectReason TAG_SPECIFIED_WITHOUT_A_VALUE = new SessionRejectReason(4, "Tag specified without a value"); - public static SessionRejectReason VALUE_IS_INCORRECT = new SessionRejectReason(5, "Value is incorrect (out of range) for this tag"); - public static SessionRejectReason INCORRECT_DATA_FORMAT_FOR_VALUE = new SessionRejectReason(6, "Incorrect data format for value"); - public static SessionRejectReason DECRYPTION_PROBLEM = new SessionRejectReason(7, "Decryption problem"); - public static SessionRejectReason SIGNATURE_PROBLEM = new SessionRejectReason(8, "Signature problem"); - public static SessionRejectReason COMPID_PROBLEM = new SessionRejectReason(9, "CompID problem"); - public static SessionRejectReason SENDING_TIME_ACCURACY_PROBLEM = new SessionRejectReason(10, "SendingTime accuracy problem"); - public static SessionRejectReason INVALID_MSGTYPE = new SessionRejectReason(11, "Invalid MsgType"); - public static SessionRejectReason XML_VALIDATION_ERROR = new SessionRejectReason(12, "XML validation error"); - public static SessionRejectReason TAG_APPEARS_MORE_THAN_ONCE = new SessionRejectReason(13, "Tag appears more than once"); - public static SessionRejectReason TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER = new SessionRejectReason(14, "Tag specified out of required order"); - public static SessionRejectReason REPEATING_GROUP_FIELDS_OUT_OF_ORDER = new SessionRejectReason(15, "Repeating group fields out of order"); - public static SessionRejectReason INCORRECT_NUM_IN_GROUP_COUNT_FOR_REPEATING_GROUP = new SessionRejectReason(16, "Incorrect NumInGroup count for repeating group"); - public static SessionRejectReason NON_DATA_VALUE_INCLUDES_FIELD_DELIMITER = new SessionRejectReason(17, "Non-data value includes field delimiter"); - public static SessionRejectReason OTHER = new SessionRejectReason(99, "Other"); + public static SessionRejectReason INVALID_TAG_NUMBER = new(0, "Invalid tag number"); + public static SessionRejectReason REQUIRED_TAG_MISSING = new(1, "Required tag missing"); + public static SessionRejectReason TAG_NOT_DEFINED_FOR_THIS_MESSAGE_TYPE = new(2, "Tag not defined for this message type"); + public static SessionRejectReason UNDEFINED_TAG = new(3, "Undefined Tag"); + public static SessionRejectReason TAG_SPECIFIED_WITHOUT_A_VALUE = new(4, "Tag specified without a value"); + public static SessionRejectReason VALUE_IS_INCORRECT = new(5, "Value is incorrect (out of range) for this tag"); + public static SessionRejectReason INCORRECT_DATA_FORMAT_FOR_VALUE = new(6, "Incorrect data format for value"); + public static SessionRejectReason DECRYPTION_PROBLEM = new(7, "Decryption problem"); + public static SessionRejectReason SIGNATURE_PROBLEM = new(8, "Signature problem"); + public static SessionRejectReason COMPID_PROBLEM = new(9, "CompID problem"); + public static SessionRejectReason SENDING_TIME_ACCURACY_PROBLEM = new(10, "SendingTime accuracy problem"); + public static SessionRejectReason INVALID_MSGTYPE = new(11, "Invalid MsgType"); + public static SessionRejectReason XML_VALIDATION_ERROR = new(12, "XML validation error"); + public static SessionRejectReason TAG_APPEARS_MORE_THAN_ONCE = new(13, "Tag appears more than once"); + public static SessionRejectReason TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER = new(14, "Tag specified out of required order"); + public static SessionRejectReason REPEATING_GROUP_FIELDS_OUT_OF_ORDER = new(15, "Repeating group fields out of order"); + public static SessionRejectReason INCORRECT_NUM_IN_GROUP_COUNT_FOR_REPEATING_GROUP = new(16, "Incorrect NumInGroup count for repeating group"); + public static SessionRejectReason NON_DATA_VALUE_INCLUDES_FIELD_DELIMITER = new(17, "Non-data value includes field delimiter"); + public static SessionRejectReason OTHER = new(99, "Other"); public SessionRejectReason(int value, string description) : base(value, description) diff --git a/QuickFIXn/QuickFix.csproj b/QuickFIXn/QuickFix.csproj index 225d010ff..8e1f2bd7f 100644 --- a/QuickFIXn/QuickFix.csproj +++ b/QuickFIXn/QuickFix.csproj @@ -3,6 +3,7 @@ net8.0 QuickFIX/n + enable true true QuickFIXn.Core diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 53cd34765..260a9e71e 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -922,7 +922,7 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru DoTargetTooHigh(msg, msgSeqNum); return false; } - else if (checkTooLow && IsTargetTooLow(msgSeqNum)) + if (checkTooLow && IsTargetTooLow(msgSeqNum)) { DoTargetTooLow(msg, msgSeqNum); return false; @@ -1025,7 +1025,7 @@ protected bool IsCorrectCompId(string senderCompId, string targetCompId) && SessionID.TargetCompID.Equals(senderCompId); } - /// FIXME - this fn always returns true-- should it ever be false? + /// TODO - this fn always returns true-- should it ever be false? protected bool IsTimeToGenerateLogon() { return true; @@ -1151,11 +1151,9 @@ protected bool GenerateResendRequestRange(string beginString, SeqNumType startSe Log.OnEvent("Sent ResendRequest FROM: " + startSeqNum + " TO: " + endSeqNum); return true; } - else - { - Log.OnEvent("Error sending ResendRequest (" + startSeqNum + " ," + endSeqNum + ")"); - return false; - } + + Log.OnEvent("Error sending ResendRequest (" + startSeqNum + " ," + endSeqNum + ")"); + return false; } protected void GenerateResendRequest(string beginString, SeqNumType msgSeqNum) @@ -1336,8 +1334,10 @@ public void GenerateReject(Message message, FixValues.SessionRejectReason reason msgSeqNum = message.Header.GetULong(Fields.Tags.MsgSeqNum); reject.SetField(new Fields.RefSeqNum(msgSeqNum)); } - catch (Exception) - { } + catch (Exception ex) + { + Log.OnEvent($"Exception while setting RefSeqNum: {ex}"); + } } if (string.CompareOrdinal(beginString, FixValues.BeginString.FIX42) >= 0) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 19239dd20..e77c3d03d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -24,6 +24,9 @@ What's New * correction in FIX41 and FIX42: `D` to `UNDISCLOSED` * #863 - Change Message.ToString() to not alter object state anymore. (gbirchmeier) Use new function Message.ConstructString() if you need BodyLength/CheckSum to be updated. +* #xxx - cleanup/nullable-ize XXX (gbirchmeier) + * DataDictionary.CheckIsInGroup is now static + * Get rid of non-static DataDictionary.Validate functions. **Non-breaking changes** * #864 - when multiple threads race to init DefaultMessageFactory, diff --git a/UnitTests/DataDictionaryTests.cs b/UnitTests/DataDictionaryTests.cs index 1c013feb8..9937a1946 100644 --- a/UnitTests/DataDictionaryTests.cs +++ b/UnitTests/DataDictionaryTests.cs @@ -4,6 +4,7 @@ using System.Xml; using NUnit.Framework; using QuickFix; +using QuickFix.DataDictionary; using UnitTests.TestHelpers; namespace UnitTests @@ -11,14 +12,12 @@ namespace UnitTests [TestFixture] public class DataDictionaryTests { - private QuickFix.IMessageFactory _defaultMsgFactory = new QuickFix.DefaultMessageFactory(); - - private const char NUL = Message.SOH; + private readonly IMessageFactory _defaultMsgFactory = new DefaultMessageFactory(); [Test] public void VersionTest() { - QuickFix.DataDictionary.DataDictionary dd44 = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd44 = new DataDictionary(); dd44.LoadFIXSpec("FIX44"); Assert.That(dd44.MajorVersion, Is.EqualTo("4")); Assert.That(dd44.MinorVersion, Is.EqualTo("4")); @@ -28,7 +27,7 @@ public void VersionTest() [Test] public void LoadFieldsTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.That(dd.FieldsByTag[1].Name, Is.EqualTo("Account")); Assert.That(dd.FieldsByName["Account"].Tag, Is.EqualTo(1)); @@ -39,7 +38,7 @@ public void LoadFieldsTest() [Test] public void LoadFieldsFromStreamTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); Stream stream = new FileStream(Path.Combine(TestContext.CurrentContext.TestDirectory, "spec", "fix", "FIX44.xml"), FileMode.Open, FileAccess.Read); dd.Load(stream); Assert.That(dd.FieldsByTag[1].Name, Is.EqualTo("Account")); @@ -51,7 +50,7 @@ public void LoadFieldsFromStreamTest() [Test] public void FieldHasValueTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.That(dd.FieldHasValue(QuickFix.Fields.Tags.StatusValue, "1"), Is.EqualTo(true)); Assert.That(dd.FieldHasValue(QuickFix.Fields.Tags.StatusValue, "CONNECTED"), Is.EqualTo(false)); @@ -62,7 +61,7 @@ public void FieldHasValueTest() [Test] public void FieldHasDescriptionTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.AreEqual(typeof (Dictionary), dd.FieldsByTag[945].EnumDict.GetType()); Assert.That("COMPLETED", Is.EqualTo(dd.FieldsByTag[945].EnumDict["2"])); @@ -72,7 +71,7 @@ public void FieldHasDescriptionTest() [Test] public void BasicMessageTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.That(dd.Messages["3"].Fields.Count, Is.EqualTo(7)); } @@ -80,9 +79,9 @@ public void BasicMessageTest() [Test] public void ComponentSmokeTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); - QuickFix.DataDictionary.DDMap tcr = dd.Messages["AE"]; + DDMap tcr = dd.Messages["AE"]; Assert.True(tcr.Fields.ContainsKey(55)); Assert.False(tcr.Fields.ContainsKey(5995)); } @@ -90,24 +89,24 @@ public void ComponentSmokeTest() [Test] public void GroupTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); - QuickFix.DataDictionary.DDMap tcrr = dd.Messages["AD"]; + DDMap tcrr = dd.Messages["AD"]; Assert.True(tcrr.IsGroup(711)); Assert.True(tcrr.IsField(711)); // No Field also a field Assert.True(tcrr.GetGroup(711).IsField(311)); Assert.That(tcrr.Groups[711].Fields[311].Name, Is.EqualTo("UnderlyingSymbol")); Assert.That(tcrr.Groups[711].Delim, Is.EqualTo(311)); - QuickFix.DataDictionary.DDMap tcr = dd.Messages["AE"]; + DDMap tcr = dd.Messages["AE"]; Assert.That(tcr.Groups[711].Groups[457].Fields[458].Name, Is.EqualTo("UnderlyingSecurityAltID")); } [Test] public void NestedGroupTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); - QuickFix.DataDictionary.DDMap msgJ = dd.Messages["J"]; + DDMap msgJ = dd.Messages["J"]; Assert.True(msgJ.IsGroup(73)); Assert.False(msgJ.IsGroup(756)); @@ -117,9 +116,9 @@ public void NestedGroupTest() [Test] public void GroupBeginsGroupTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadTestFIXSpec("group_begins_group"); - QuickFix.DataDictionary.DDMap msg = dd.Messages["magic"]; + DDMap msg = dd.Messages["magic"]; Assert.True(msg.IsGroup(6660)); // NoMagics group Assert.True(msg.GetGroup(6660).IsGroup(7770)); // NoMagics/NoRabbits Assert.True(msg.GetGroup(6660).IsField(6661)); // NoMagics/MagicWord @@ -131,9 +130,9 @@ public void GroupBeginsGroupTest() [Test] public void HeaderGroupTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); - QuickFix.DataDictionary.DDMap headerMap = dd.Header; + DDMap headerMap = dd.Header; Assert.True(headerMap.IsGroup(627)); QuickFix.DataDictionary.DDGrp grpMap = headerMap.GetGroup(627); Assert.True(dd.Header.GetGroup(627).IsField(628)); @@ -143,7 +142,7 @@ public void HeaderGroupTest() [Test] public void ReqFldTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.True(dd.Messages["AE"].ReqFields.Contains(571)); Assert.False(dd.Messages["AE"].ReqFields.Contains(828)); @@ -152,7 +151,7 @@ public void ReqFldTest() [Test] public void HeaderTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.True(dd.Header.ReqFields.Contains(9)); Assert.That(dd.Header.Fields.Count, Is.EqualTo(27)); @@ -161,7 +160,7 @@ public void HeaderTest() [Test] public void TrailerTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.True(dd.Trailer.ReqFields.Contains(10)); Assert.That(dd.Trailer.Fields.Count, Is.EqualTo(3)); @@ -170,21 +169,20 @@ public void TrailerTest() [Test] public void CheckValidFormat() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); dd.CheckFieldsHaveValues = true; var goodFields = new QuickFix.Fields.StringField[] { - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.Symbol, "foo"), // string - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.Side, "2"), // char - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.LastQty, "123"), // int - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.AvgPx, "1.23"), // decimal - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.ReportToExch, "Y"), // bool - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.ContraTradeTime, "20011217-09:30:47.123"), // datetime - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.MDEntryDate, "20030910"), // dateonly - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.MDEntryTime, "13:20:00.123"), // timeonly - - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.Symbol, "") // string + new(QuickFix.Fields.Tags.Symbol, "foo"), // string + new(QuickFix.Fields.Tags.Side, "2"), // char + new(QuickFix.Fields.Tags.LastQty, "123"), // int + new(QuickFix.Fields.Tags.AvgPx, "1.23"), // decimal + new(QuickFix.Fields.Tags.ReportToExch, "Y"), // bool + new(QuickFix.Fields.Tags.ContraTradeTime, "20011217-09:30:47.123"), // datetime + new(QuickFix.Fields.Tags.MDEntryDate, "20030910"), // dateonly + new(QuickFix.Fields.Tags.MDEntryTime, "13:20:00.123"), // timeonly + new(QuickFix.Fields.Tags.Symbol, "") // string }; foreach (var datum in goodFields) @@ -194,13 +192,13 @@ public void CheckValidFormat() var badFields = new QuickFix.Fields.StringField[] { - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.Side, "toolong"), // char - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.LastQty, "notint"), // int - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.AvgPx, "notdec"), // decimal - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.ReportToExch, "notbool"), // bool - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.ContraTradeTime, "notdatetime"), // datetime - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.MDEntryDate, "notdate"), // dateonly - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.MDEntryTime, "nottime") // timeonly + new(QuickFix.Fields.Tags.Side, "toolong"), // char + new(QuickFix.Fields.Tags.LastQty, "notint"), // int + new(QuickFix.Fields.Tags.AvgPx, "notdec"), // decimal + new(QuickFix.Fields.Tags.ReportToExch, "notbool"), // bool + new(QuickFix.Fields.Tags.ContraTradeTime, "notdatetime"), // datetime + new(QuickFix.Fields.Tags.MDEntryDate, "notdate"), // dateonly + new(QuickFix.Fields.Tags.MDEntryTime, "nottime") // timeonly }; foreach (var datum in badFields) @@ -210,13 +208,13 @@ public void CheckValidFormat() var emptyFields = new QuickFix.Fields.StringField[] { - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.Side, ""), // char - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.LastQty, ""), // int - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.AvgPx, ""), // decimal - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.ReportToExch, ""), // bool - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.ContraTradeTime, ""), // datetime - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.MDEntryDate, ""), // dateonly - new QuickFix.Fields.StringField(QuickFix.Fields.Tags.MDEntryTime, "") // timeonly + new(QuickFix.Fields.Tags.Side, ""), // char + new(QuickFix.Fields.Tags.LastQty, ""), // int + new(QuickFix.Fields.Tags.AvgPx, ""), // decimal + new(QuickFix.Fields.Tags.ReportToExch, ""), // bool + new(QuickFix.Fields.Tags.ContraTradeTime, ""), // datetime + new(QuickFix.Fields.Tags.MDEntryDate, ""), // dateonly + new(QuickFix.Fields.Tags.MDEntryTime, "") // timeonly }; foreach (var datum in emptyFields) @@ -235,7 +233,7 @@ public void CheckValidFormat() [Test] public void CheckValidTagNumberTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.DoesNotThrow(delegate { dd.CheckValidTagNumber(35); }); @@ -250,7 +248,7 @@ public void CheckValidTagNumberTest() [Test] public void CheckValue() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); // unknown field @@ -273,7 +271,7 @@ public void CheckValue() [Test] public void CheckIsInMessageTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.DoesNotThrow(delegate { dd.CheckIsInMessage(new QuickFix.Fields.MDReqID("foo"), "W"); }); @@ -288,30 +286,30 @@ public void CheckIsInMessageTest() [Test] public void CheckIsInGroupTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); - QuickFix.DataDictionary.DDGrp g = dd.Messages["B"].GetGroup(33); + DDGrp g = dd.Messages["B"].GetGroup(33); QuickFix.Fields.Text textField = new QuickFix.Fields.Text("woot"); QuickFix.Fields.ClOrdID clOrdIdField = new QuickFix.Fields.ClOrdID("not woot"); - Assert.DoesNotThrow(delegate() { dd.CheckIsInGroup(textField, g, "B"); }); - Assert.Throws(typeof(TagNotDefinedForMessage), delegate { dd.CheckIsInGroup(clOrdIdField, g, "B"); }); + Assert.DoesNotThrow(delegate { DataDictionary.CheckIsInGroup(textField, g, "B"); }); + Assert.Throws(typeof(TagNotDefinedForMessage), delegate { DataDictionary.CheckIsInGroup(clOrdIdField, g, "B"); }); } [Test] public void CheckGroupCountTest() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX42"); QuickFix.FIX42.NewOrderSingle n = new QuickFix.FIX42.NewOrderSingle(); - string s = "8=FIX.4.2" + NUL + "9=148" + NUL + "35=D" + NUL + "34=2" + NUL + "49=TW" + NUL + "52=20111011-15:06:23.103" + NUL + "56=ISLD" + NUL - + "11=ID" + NUL + "21=1" + NUL + "40=1" + NUL + "54=1" + NUL + "38=200.00" + NUL + "55=INTC" + NUL - + "386=3" + NUL + "336=PRE-OPEN" + NUL + "336=AFTER-HOURS" + NUL - + "60=20111011-15:06:23.103" + NUL - + "10=35" + NUL; + string s = ("8=FIX.4.2|9=148|35=D|34=2|49=TW|52=20111011-15:06:23.103|56=ISLD|" + + "11=ID|21=1|40=1|54=1|38=200.00|55=INTC|" + + "386=3|336=PRE-OPEN|336=AFTER-HOURS|" + + "60=20111011-15:06:23.103|" + + "10=35|").Replace('|', Message.SOH); n.FromString(s, true, dd, dd, _defaultMsgFactory); @@ -320,285 +318,22 @@ public void CheckGroupCountTest() Assert.AreEqual("386=3", n.NoTradingSessions.toStringField()); StringAssert.Contains("386=3", n.ConstructString()); - Assert.Throws(delegate { dd.CheckGroupCount(n.NoTradingSessions, n, "D"); }); - } - - [Test] - public void ValidateWrongType() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = {"8=FIX.4.4", "9=120", "35=D", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "11=clordid", "55=sym", "54=1", "60=20110909-09:09:09.999", "40=1", - "38=failboat", // should be a decimal - "10=64"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "D"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } - - [Test] - public void ValidateWithRepeatingGroupTest() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX42"); - QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory(); - - string msgStr = "8=FIX.4.2" + NUL + "9=87" + NUL + "35=B" + NUL + "34=3" + NUL + "49=CLIENT1" + NUL - + "52=20111012-22:15:55.474" + NUL + "56=EXECUTOR" + NUL + "148=AAAAAAA" + NUL - + "33=2" + NUL + "58=L1" + NUL + "58=L2" + NUL + "10=016" + NUL; - - QuickFix.Fields.MsgType msgType = Message.IdentifyType(msgStr); - string beginString = Message.ExtractBeginString(msgStr); - - Message message = f.Create(beginString, msgType.Obj); - message.FromString(msgStr, true, dd, dd, f); - - dd.Validate(message, beginString, msgType.Obj); + Assert.Throws(delegate { dd.CheckGroupCount(n.NoTradingSessions, n, "D"); }); } - [Test] - public void ValidateGroupBeginsGroup() - { - // TODO: In a future version, change this so that - // 1) generator will generate source for our test DD - // 2) this test will use proper type-safe methods and not generics - // Probably some or all of this would then move to MessageTests.cs - - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadTestFIXSpec("group_begins_group"); - - string pipedStr = "8=FIX.9.9|9=167|35=magic|34=3|49=CLIENT1|52=20111012-22:15:55.474|56=EXECUTOR|" - + "1111=mundane|5555=magicfield|6660=1|7770=2|7711=Hoppy|7712=brown|" - + "7711=Floppy|7712=white|6661=abracadabra|10=48|"; - // note: length and checksum might be garbage - string msgStr = pipedStr.Replace('|', Message.SOH); - - string beginString = Message.ExtractBeginString(msgStr); - Message msg = new Message(msgStr, dd, dd, false); - - // true param means body-only, i.e. don't validate length/checksum - dd.Validate(msg, true, beginString, "magic"); - - // Verify can retrieve one of the inner groups. - // (Gotta use generic methods because code isn't generated for this DD) - Group magicGroup = new Group(6660, 7770, new[] { 7770, 6661 }); - msg.GetGroup(1, magicGroup); - Group rabbitGroup = new Group(7770, 7711, new[] { 7711, 7722 }); - magicGroup.GetGroup(2, rabbitGroup); - - Assert.AreEqual("abracadabra", magicGroup.GetString(6661)); - Assert.AreEqual("Floppy", rabbitGroup.GetString(7711)); - Assert.AreEqual("white", rabbitGroup.GetString(7712)); - } - - [Test] - public void ValidateWrongTypeInRepeatingGroup() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = {"8=FIX.4.4", "9=111", "35=V", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "262=mdreqid", "263=0", "264=5", - "267=1", // MDReqGrp - "269=failboat", // should be a char - "146=1", // InstrmtMDReqGrp - "55=sym", - "10=91"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "V"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } - - [Test] - public void ValidateWrongTypeInNestedRepeatingGroup() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = {"8=FIX.4.4", "9=185", "35=J", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "70=AllocID", "71=0", "626=1", "857=0", "54=1", "55=sym", "53=1", "6=5.5", "75=20110909-09:09:09.999", - "73=1", // NoOrders - "11=clordid", - "756=1", // NoNested2PartyIDs - "757=nested2partyid", - "759=failboat", // supposed to be a int - "10=48"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "J"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } - - [Test] - public void ValidateDateAndTimeFields() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = { "8=FIX.4.4", "9=104", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=19" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "W"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - try - { - dd.Validate(message, beginString, msgType); - } - catch (QuickFix.TagException e) - { - Console.WriteLine(e.ToString()); - Console.WriteLine(e.Field); - throw; - } - } - - [Test] - public void ValidateDateTime_Invalid() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - // intentionally invalid SendingTime (52/DateTime) - string[] msgFields = { "8=FIX.4.4", "9=91", "35=W", "34=3", "49=sender", "52=20110909", "56=target", - "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=51" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "W"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } - - [Test] - public void ValidateDateOnly_Invalid() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - // intentionally invalid MDEntryDate (272/DateOnly) - string[] msgFields = { "8=FIX.4.4", "9=117", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", "268=1", "269=0", "272=20111012-22:15:30.444", "273=22:15:30.444", "10=175" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "W"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } - - [Test] - public void ValidateTimeOnly_Invalid() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - // intentionally invalid MDEntryTime (272/TimeOnly) - string[] msgFields = { "8=FIX.4.4", "9=113", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", "268=1", "269=0", "272=20111012", "273=20111012-22:15:30.444", "10=200" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "W"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } [Test] public void DuplicateEnumsDoesNotThrow() { // If this test throws, it failed. - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadTestFIXSpec("FIX43_dup_enum"); } - [Test] - public void OptionalComponentRequiredField() - { - // issue #98 - message erroneously rejected because DD says that - // component-required field is missing even though component is not present - - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = { "8=FIX.4.4", "9=77", "35=AD", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "568=tradereqid", "569=0", "10=109" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "AD"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - dd.Validate(message, beginString, msgType); - } - - [Test] - public void RequiredComponentRequiredField() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = { "8=FIX.4.4", "9=76", "35=7", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "2=AdvId", "5=N", "4=B", "53=1", "10=138" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "7"; - string beginString = "FIX.4.4"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd, f); - - var ex = Assert.Throws(delegate { dd.Validate(message, beginString, msgType); }); - Assert.AreEqual(55, ex.Field); - } - [Test] public void ComponentFieldsRequirements() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); Assert.False(dd.Messages["AD"].ReqFields.Contains(55)); Assert.True(dd.Messages["7"].ReqFields.Contains(55)); @@ -607,7 +342,7 @@ public void ComponentFieldsRequirements() [Test] public void Issue134_RequiredIsOptional() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadTestFIXSpec("required_is_optional"); Assert.True(dd.Messages["magic"].ReqFields.Contains(1111)); //base required field Assert.False(dd.Messages["magic"].ReqFields.Contains(5555)); //base optional field @@ -617,78 +352,10 @@ public void Issue134_RequiredIsOptional() Assert.False(dd.Messages["magic"].Groups[6660].ReqFields.Contains(6662)); // group optional field } - [Test] // Issue #66 - public void ValidateMultipleValueStringType() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = {"8=FIX.4.4", "9=99", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", - "268=1", "269=0", "270=123.23", "271=2", "277=A B", - "10=213"}; - string msgStr = string.Join( Message.SOH, msgFields ) + Message.SOH; - - string msgType = "W"; - string beginString = "FIX.4.4"; - - Message message = f.Create( beginString, msgType ); - message.FromString(msgStr, true, dd, dd); - - dd.Validate( message, beginString, msgType ); - } - - [Test] // Issue #66 - public void ValidateMultipleValueStringType_Invalid() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - - string[] msgFields = {"8=FIX.4.4", "9=99", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", - "268=1", "269=0", "270=123.23", "271=2", "277=A 1", - "10=196"}; - string msgStr = string.Join( Message.SOH, msgFields ) + Message.SOH; - - string msgType = "W"; - string beginString = "FIX.4.4"; - - Message message = f.Create( beginString, msgType ); - message.FromString(msgStr, true, dd, dd); - - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); - } - - [Test] // Issue #282 - public void ValidateTagSpecifiedWithoutAValue() - { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX42"); - QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory(); - - string[] msgFields = {"8=FIX.4.2", "9=70", "35=B", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "358=", "148=", "33=0", "10=150"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - - string msgType = "B"; - string beginString = "FIX.4.2"; - - Message message = f.Create(beginString, msgType); - message.FromString(msgStr, true, dd, dd); - - dd.CheckFieldsHaveValues = true; - Assert.Throws(delegate { dd.Validate(message, beginString, msgType); }); - - dd.CheckFieldsHaveValues = false; - Assert.DoesNotThrow(delegate { dd.Validate(message, beginString, msgType); }); - } - [Test] // Issue #493 public void ParseThroughComments() { - QuickFix.DataDictionary.DataDictionary dd = new QuickFix.DataDictionary.DataDictionary(); + DataDictionary dd = new DataDictionary(); dd.LoadTestFIXSpec("comments"); // The fact that it doesn't throw is sufficient, but we'll do some other checks anyway. @@ -703,11 +370,10 @@ public void ParseThroughComments() Assert.True(news.GetGroup(33).IsField(355)); // EncodedText } - - XmlNode MakeNode(string xmlString) + private static XmlNode MakeNode(string xmlString) { XmlDocument doc = new XmlDocument(); - if (xmlString.StartsWith("<", StringComparison.Ordinal)) + if (xmlString.StartsWith('<')) { doc.LoadXml(xmlString); return doc.DocumentElement; @@ -721,15 +387,15 @@ public void VerifyChildNode() XmlNode parentNode = MakeNode(""); Assert.DoesNotThrow( - delegate { QuickFix.DataDictionary.DataDictionary.VerifyChildNode(MakeNode(""), parentNode); }); + delegate { DataDictionary.VerifyChildNode(MakeNode(""), parentNode); }); DictionaryParseException dpx = Assert.Throws( - delegate { QuickFix.DataDictionary.DataDictionary.VerifyChildNode(MakeNode("foo"), parentNode); }); - Assert.AreEqual("Malformed data dictionary: Found text-only node containing 'foo'", dpx.Message); + delegate { DataDictionary.VerifyChildNode(MakeNode("foo"), parentNode); }); + Assert.AreEqual("Malformed data dictionary: Found text-only node containing 'foo'", dpx!.Message); dpx = Assert.Throws( - delegate { QuickFix.DataDictionary.DataDictionary.VerifyChildNode(MakeNode("qty"), parentNode); }); - Assert.AreEqual("Malformed data dictionary: Found 'field' node without 'name' within parent 'message/Daddy'", dpx.Message); + delegate { DataDictionary.VerifyChildNode(MakeNode("qty"), parentNode); }); + Assert.AreEqual("Malformed data dictionary: Found 'field' node without 'name' within parent 'message/Daddy'", dpx!.Message); } } } diff --git a/UnitTests/DataDictionary_ValidateTests.cs b/UnitTests/DataDictionary_ValidateTests.cs new file mode 100644 index 000000000..2f7cf0fce --- /dev/null +++ b/UnitTests/DataDictionary_ValidateTests.cs @@ -0,0 +1,378 @@ +#nullable enable + +using System; +using NUnit.Framework; +using QuickFix; +using QuickFix.DataDictionary; +using QuickFix.Fields; +using UnitTests.TestHelpers; + +namespace UnitTests; + +public class DataDictionary_ValidateTests +{ + [Test] + public void UnsupportedVersionTest() { + //string ddFileContent = + // "
"; + //DataDictionary txDd = new(new MemoryStream(Encoding.Unicode.GetBytes(ddFileContent))); + + DataDictionary dd = new(); + dd.LoadFIXSpec("FIX44"); + + var ex = Assert.Throws(delegate { + DataDictionary.Validate(new Message(), dd, dd, "foobar", "X"); }); + StringAssert.Contains("Incorrect BeginString (foobar)", ex!.Message); + } + + [Test] + public void TagOutOfOrderTest() { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string msgStr = ("8=FIX.4.4|9=102|35=B|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "148=headline|33=2|58=line1|58=line2|" + + "142=narnia|" + + "10=121|").Replace('|', Message.SOH); + string msgType = "B"; + string beginString = "FIX.4.4"; + + Message msg = f.Create(beginString, msgType); + msg.FromString(msgStr, true, dd, dd, f); + + // Mess the message up to trigger the fail + msg.SetField(new SenderLocationID("narnia")); // header field in body + msg.ConstructString(); // recalc checksum + msg.Validate(); + + var ex = Assert.Throws(delegate { + DataDictionary.Validate(msg, dd, dd, "FIX.4.4", "B"); }); + StringAssert.Contains("Tag specified out of required order", ex!.Message); + } + + [Test] + public void ValidateWrongType() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = {"8=FIX.4.4", "9=120", "35=D", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "11=clordid", "55=sym", "54=1", "60=20110909-09:09:09.999", "40=1", + "38=failboat", // should be a decimal + "10=64"}; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "D"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] + public void ValidateWithRepeatingGroupTest() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX42"); + QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory(); + + string msgStr = ("8=FIX.4.2|9=87|35=B|34=3|49=CLIENT1|" + + "52=20111012-22:15:55.474|56=EXECUTOR|148=AAAAAAA|" + + "33=2|58=L1|58=L2|10=016|").Replace('|', Message.SOH); + + QuickFix.Fields.MsgType msgType = Message.IdentifyType(msgStr); + string beginString = Message.ExtractBeginString(msgStr); + + Message message = f.Create(beginString, msgType.Obj); + message.FromString(msgStr, true, dd, dd, f); + + dd.Validate(message, beginString, msgType.Obj); + } + + [Test] + public void ValidateGroupBeginsGroup() + { + DataDictionary dd = new DataDictionary(); + dd.LoadTestFIXSpec("group_begins_group"); + + string pipedStr = "8=FIX.9.9|9=167|35=magic|34=3|49=CLIENT1|52=20111012-22:15:55.474|56=EXECUTOR|" + + "1111=mundane|5555=magicfield|6660=1|7770=2|7711=Hoppy|7712=brown|" + + "7711=Floppy|7712=white|6661=abracadabra|10=48|"; + string msgStr = pipedStr.Replace('|', Message.SOH); + + string beginString = Message.ExtractBeginString(msgStr); + Message msg = new Message(msgStr, dd, dd, false); + + DataDictionary.Validate(msg, dd, dd, beginString, "magic"); + + // Verify can retrieve one of the inner groups. + // (Gotta use generic methods because code isn't generated for this DD) + Group magicGroup = new Group(6660, 7770, new[] { 7770, 6661 }); + msg.GetGroup(1, magicGroup); + Group rabbitGroup = new Group(7770, 7711, new[] { 7711, 7722 }); + magicGroup.GetGroup(2, rabbitGroup); + + Assert.AreEqual("abracadabra", magicGroup.GetString(6661)); + Assert.AreEqual("Floppy", rabbitGroup.GetString(7711)); + Assert.AreEqual("white", rabbitGroup.GetString(7712)); + } + + [Test] + public void ValidateWrongTypeInRepeatingGroup() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = {"8=FIX.4.4", "9=111", "35=V", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "262=mdreqid", "263=0", "264=5", + "267=1", // MDReqGrp + "269=failboat", // should be a char + "146=1", // InstrmtMDReqGrp + "55=sym", + "10=91"}; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "V"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] + public void ValidateWrongTypeInNestedRepeatingGroup() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = {"8=FIX.4.4", "9=185", "35=J", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "70=AllocID", "71=0", "626=1", "857=0", "54=1", "55=sym", "53=1", "6=5.5", "75=20110909-09:09:09.999", + "73=1", // NoOrders + "11=clordid", + "756=1", // NoNested2PartyIDs + "757=nested2partyid", + "759=failboat", // supposed to be a int + "10=48"}; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "J"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] + public void ValidateDateAndTimeFields() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = { "8=FIX.4.4", "9=104", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=19" }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "W"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + dd.Validate(message, beginString, msgType); + } + + [Test] + public void ValidateDateTime_Invalid() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + // intentionally invalid SendingTime (52/DateTime) + string[] msgFields = { "8=FIX.4.4", "9=91", "35=W", "34=3", "49=sender", "52=20110909", "56=target", + "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=51" }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "W"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] + public void ValidateDateOnly_Invalid() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + // intentionally invalid MDEntryDate (272/DateOnly) + string[] msgFields = { "8=FIX.4.4", "9=117", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "55=sym", "268=1", "269=0", "272=20111012-22:15:30.444", "273=22:15:30.444", "10=175" }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "W"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] + public void ValidateTimeOnly_Invalid() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + // intentionally invalid MDEntryTime (272/TimeOnly) + string[] msgFields = { "8=FIX.4.4", "9=113", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "55=sym", "268=1", "269=0", "272=20111012", "273=20111012-22:15:30.444", "10=200" }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "W"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] + public void OptionalComponentRequiredField() + { + // issue #98 - message erroneously rejected because DD says that + // component-required field is missing even though component is not present + + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = { "8=FIX.4.4", "9=77", "35=AD", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "568=tradereqid", "569=0", "10=109" }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "AD"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + dd.Validate(message, beginString, msgType); + } + + [Test] + public void RequiredComponentRequiredField() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = { "8=FIX.4.4", "9=76", "35=7", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "2=AdvId", "5=N", "4=B", "53=1", "10=138" }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "7"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd, f); + + var ex = Assert.Throws(delegate { dd.Validate(message, beginString, msgType); }); + Assert.AreEqual(55, ex!.Field); + } + + [Test] // Issue #66 + public void ValidateMultipleValueStringType() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = + { + "8=FIX.4.4", "9=99", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "55=sym", + "268=1", "269=0", "270=123.23", "271=2", "277=A B", + "10=213" + }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "W"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd); + + dd.Validate(message, beginString, msgType); + } + + [Test] // Issue #66 + public void ValidateMultipleValueStringType_Invalid() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX44"); + QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); + + string[] msgFields = + { + "8=FIX.4.4", "9=99", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "55=sym", + "268=1", "269=0", "270=123.23", "271=2", "277=A 1", + "10=196" + }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "W"; + string beginString = "FIX.4.4"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd); + + Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + } + + [Test] // Issue #282 + public void ValidateTagSpecifiedWithoutAValue() + { + DataDictionary dd = new DataDictionary(); + dd.LoadFIXSpec("FIX42"); + QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory(); + + string[] msgFields = + { + "8=FIX.4.2", "9=70", "35=B", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", + "358=", "148=", "33=0", "10=150" + }; + string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; + + string msgType = "B"; + string beginString = "FIX.4.2"; + + Message message = f.Create(beginString, msgType); + message.FromString(msgStr, true, dd, dd); + + dd.CheckFieldsHaveValues = true; + Assert.Throws(delegate { dd.Validate(message, beginString, msgType); }); + + dd.CheckFieldsHaveValues = false; + Assert.DoesNotThrow(delegate { dd.Validate(message, beginString, msgType); }); + } +} diff --git a/spec/test/group_begins_group.xml b/spec/test/group_begins_group.xml index c83005622..1e25a7c10 100644 --- a/spec/test/group_begins_group.xml +++ b/spec/test/group_begins_group.xml @@ -6,8 +6,18 @@ There's no reason we *can't* support it, so we will. --> -
- +
+ + + + + + + +
+ + + @@ -25,6 +35,15 @@ + + + + + + + + + From 61f568d0a2ddf1db60fa874b5ca79559554a3031 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 20 Sep 2024 17:40:53 -0500 Subject: [PATCH 2/6] cleanup/nullable-ize more sources --- QuickFIXn/DataDictionaryProvider.cs | 36 +++++++++------------ QuickFIXn/Message/Message.cs | 35 ++++++++++---------- QuickFIXn/MessageCracker.cs | 43 ++++++++----------------- QuickFIXn/SocketSettings.cs | 9 +++--- QuickFIXn/Transport/SslStreamFactory.cs | 4 +++ RELEASE_NOTES.md | 4 +-- 6 files changed, 57 insertions(+), 74 deletions(-) diff --git a/QuickFIXn/DataDictionaryProvider.cs b/QuickFIXn/DataDictionaryProvider.cs index 2b8f59743..04ac741b0 100755 --- a/QuickFIXn/DataDictionaryProvider.cs +++ b/QuickFIXn/DataDictionaryProvider.cs @@ -4,49 +4,43 @@ namespace QuickFix { public class DataDictionaryProvider { - private Dictionary transportDataDictionaries_; - private Dictionary applicationDataDictionaries_; - private DataDictionary.DataDictionary emptyDataDictionary_; + private readonly Dictionary _transportDataDictionaries; + private readonly Dictionary _applicationDataDictionaries; + private readonly DataDictionary.DataDictionary _emptyDataDictionary; public DataDictionaryProvider() { - transportDataDictionaries_ = new Dictionary(); - applicationDataDictionaries_ = new Dictionary(); - emptyDataDictionary_ = new DataDictionary.DataDictionary(); + _transportDataDictionaries = new Dictionary(); + _applicationDataDictionaries = new Dictionary(); + _emptyDataDictionary = new DataDictionary.DataDictionary(); } /// TODO need to make deeper copy? public DataDictionaryProvider(DataDictionaryProvider src) { - transportDataDictionaries_ = new Dictionary(src.transportDataDictionaries_); - applicationDataDictionaries_ = new Dictionary(src.applicationDataDictionaries_); - emptyDataDictionary_ = new DataDictionary.DataDictionary(src.emptyDataDictionary_); + _transportDataDictionaries = new Dictionary(src._transportDataDictionaries); + _applicationDataDictionaries = new Dictionary(src._applicationDataDictionaries); + _emptyDataDictionary = new DataDictionary.DataDictionary(src._emptyDataDictionary); } public void AddTransportDataDictionary(string beginString, DataDictionary.DataDictionary dataDictionary) { - transportDataDictionaries_[beginString] = dataDictionary; + _transportDataDictionaries[beginString] = dataDictionary; } - public void AddApplicationDataDictionary(string applVerID, DataDictionary.DataDictionary dataDictionary) + public void AddApplicationDataDictionary(string applVerId, DataDictionary.DataDictionary dataDictionary) { - applicationDataDictionaries_[applVerID] = dataDictionary; + _applicationDataDictionaries[applVerId] = dataDictionary; } public DataDictionary.DataDictionary GetSessionDataDictionary(string beginString) { - DataDictionary.DataDictionary dd; - if (!transportDataDictionaries_.TryGetValue(beginString, out dd)) - return emptyDataDictionary_; - return dd; + return _transportDataDictionaries.GetValueOrDefault(beginString, _emptyDataDictionary); } - public DataDictionary.DataDictionary GetApplicationDataDictionary(string applVerID) + public DataDictionary.DataDictionary GetApplicationDataDictionary(string applVerId) { - DataDictionary.DataDictionary dd; - if (!applicationDataDictionaries_.TryGetValue(applVerID, out dd)) - return emptyDataDictionary_; - return dd; + return _applicationDataDictionaries.GetValueOrDefault(applVerId, _emptyDataDictionary); } } } diff --git a/QuickFIXn/Message/Message.cs b/QuickFIXn/Message/Message.cs index 3112afdf8..c5cce14ba 100644 --- a/QuickFIXn/Message/Message.cs +++ b/QuickFIXn/Message/Message.cs @@ -205,6 +205,7 @@ public static bool IsTrailerField(int tag) return false; } } + public static bool IsTrailerField(int tag, DD? dd) { if (IsTrailerField(tag)) @@ -454,7 +455,7 @@ public void FromJson(string json, bool validate, $"JSON message has invalid/missing beginString ({beginString}) and/or msgType ({msgType})"); } - IFieldMapSpec msgMap = appDict.GetMapForMessage(msgType); + IFieldMapSpec? msgMap = appDict.GetMapForMessage(msgType); FromJson(document.RootElement.GetProperty("Header"), beginString, msgType, msgMap, msgFactory, transportDict, Header); FromJson(document.RootElement.GetProperty("Body"), beginString, msgType, msgMap, msgFactory, appDict, this); FromJson(document.RootElement.GetProperty("Trailer"), beginString, msgType, msgMap, msgFactory, transportDict, Trailer); @@ -472,7 +473,7 @@ public void FromJson(string json, bool validate, protected void FromJson(JsonElement jsonElement, string beginString, string msgType, - IFieldMapSpec msgMap, + IFieldMapSpec? msgMap, IMessageFactory? msgFactory, DD dataDict, FieldMap fieldMap) @@ -793,7 +794,7 @@ public SessionID GetSessionID(Message m) m.Header.GetString(Tags.TargetCompID)); } - private Object lock_ConstructString = new Object(); + private readonly Object _lockConstructString = new Object(); /// /// Update BodyLength in Header, update CheckSum in Trailer, and return a FIX string. /// (This function changes the object state!) @@ -801,7 +802,7 @@ public SessionID GetSessionID(Message m) /// public string ConstructString() { - lock (lock_ConstructString) + lock (_lockConstructString) { Header.SetField(new BodyLength(BodyLength()), true); Trailer.SetField(new CheckSum(Fields.Converters.CheckSumConverter.Convert(CheckSum())), true); @@ -825,7 +826,7 @@ protected int BodyLength() return Header.CalculateLength() + CalculateLength() + Trailer.CalculateLength(); } - private static string FieldMapToXML(DD? dd, FieldMap fields, int space) + private static string FieldMapToXml(DD? dd, FieldMap fields) { StringBuilder s = new StringBuilder(); @@ -848,7 +849,7 @@ private static string FieldMapToXML(DD? dd, FieldMap fields, int space) for (int counter = 1; counter <= fields.GroupCount(groupTag); counter++) { s.Append(""); - s.Append(FieldMapToXML(dd, fields.GetGroup(counter, groupTag), space+1)); + s.Append(FieldMapToXml(dd, fields.GetGroup(counter, groupTag))); s.Append(""); } } @@ -861,7 +862,7 @@ private static string FieldMapToXML(DD? dd, FieldMap fields, int space) /// ToJSON() helper method. /// /// an XML string - private static StringBuilder FieldMapToJSON(StringBuilder sb, DD? dd, FieldMap fields, bool humanReadableValues) + private static StringBuilder FieldMapToJson(StringBuilder sb, DD? dd, FieldMap fields, bool humanReadableValues) { IList numInGroupTagList = fields.GetGroupTags(); IList numInGroupFieldList = new List(); @@ -905,16 +906,16 @@ private static StringBuilder FieldMapToJSON(StringBuilder sb, DD? dd, FieldMap f foreach(IField numInGroupField in numInGroupFieldList) { // The name of the NumInGroup field is the key of the JSON list containing the Group items - if (dd is not null && dd.FieldsByTag.ContainsKey(numInGroupField.Tag)) - sb.Append("\"" + dd.FieldsByTag[numInGroupField.Tag].Name + "\":["); + if (dd is not null && dd.FieldsByTag.TryGetValue(numInGroupField.Tag, out DDField? field)) + sb.Append("\"" + field.Name + "\":["); else sb.Append("\"" + numInGroupField.Tag + "\":["); // Populate the JSON list with the Group items for (int counter = 1; counter <= fields.GroupCount(numInGroupField.Tag); counter++) { - sb.Append("{"); - FieldMapToJSON(sb, dd, fields.GetGroup(counter, numInGroupField.Tag), humanReadableValues); + sb.Append('{'); + FieldMapToJson(sb, dd, fields.GetGroup(counter, numInGroupField.Tag), humanReadableValues); sb.Append("},"); } @@ -942,13 +943,13 @@ public string ToXML(DD? dataDictionary = null) StringBuilder s = new StringBuilder(); s.Append(""); s.Append("
"); - s.Append(FieldMapToXML(dataDictionary, Header, 4)); + s.Append(FieldMapToXml(dataDictionary, Header)); s.Append("
"); s.Append(""); - s.Append(FieldMapToXML(dataDictionary, this, 4)); + s.Append(FieldMapToXml(dataDictionary, this)); s.Append(""); s.Append(""); - s.Append(FieldMapToXML(dataDictionary, Trailer, 4)); + s.Append(FieldMapToXml(dataDictionary, Trailer)); s.Append(""); s.Append("
"); return s.ToString(); @@ -974,9 +975,9 @@ public string ToJSON(DD? dataDictionary = null, bool convertEnumsToDescriptions } StringBuilder sb = new StringBuilder().Append('{').Append("\"Header\":{"); - FieldMapToJSON(sb, dataDictionary, Header, convertEnumsToDescriptions).Append("},\"Body\":{"); - FieldMapToJSON(sb, dataDictionary, this, convertEnumsToDescriptions).Append("},\"Trailer\":{"); - FieldMapToJSON(sb, dataDictionary, Trailer, convertEnumsToDescriptions).Append("}}"); + FieldMapToJson(sb, dataDictionary, Header, convertEnumsToDescriptions).Append("},\"Body\":{"); + FieldMapToJson(sb, dataDictionary, this, convertEnumsToDescriptions).Append("},\"Trailer\":{"); + FieldMapToJson(sb, dataDictionary, Trailer, convertEnumsToDescriptions).Append("}}"); return sb.ToString(); } } diff --git a/QuickFIXn/MessageCracker.cs b/QuickFIXn/MessageCracker.cs index 3f9efb39d..989a969ae 100644 --- a/QuickFIXn/MessageCracker.cs +++ b/QuickFIXn/MessageCracker.cs @@ -1,9 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -using QuickFix.Fields; using System.Reflection; using System.Linq.Expressions; @@ -15,14 +11,14 @@ namespace QuickFix ///
public abstract class MessageCracker { - private Dictionary> _callCache = new Dictionary>(); + private readonly Dictionary> _callCache = new (); - public MessageCracker() + protected MessageCracker() { Initialize(this); } - private void Initialize(Object messageHandler) + private void Initialize(object messageHandler) { Type handlerType = messageHandler.GetType(); @@ -43,35 +39,26 @@ private void TryBuildCallCache(MethodInfo m) if (IsHandlerMethod(m)) { var parameters = m.GetParameters(); - var expParamMessage = parameters[0]; - var expParamSessionId = parameters[1]; - var messageParam = Expression.Parameter(typeof(Message), "message"); - var sessionParam = Expression.Parameter(typeof(SessionID), "sessionID"); - var instance = Expression.Constant(this); - var methodCall = Expression.Call(instance, m, Expression.Convert(messageParam, expParamMessage.ParameterType), Expression.Convert(sessionParam, expParamSessionId.ParameterType)); - var action = Expression.Lambda>(methodCall, messageParam, sessionParam).Compile(); - _callCache[expParamMessage.ParameterType] = action; - } } - static public bool IsHandlerMethod(MethodInfo m) + public static bool IsHandlerMethod(MethodInfo m) { - return (m.IsPublic == true - && m.Name.Equals("OnMessage") - && m.GetParameters().Length == 2 - && m.GetParameters()[0].ParameterType.IsSubclassOf(typeof(QuickFix.Message)) - && typeof(QuickFix.SessionID).IsAssignableFrom(m.GetParameters()[1].ParameterType) - && m.ReturnType == typeof(void)); + return m.IsPublic + && m.Name.Equals("OnMessage") + && m.GetParameters().Length == 2 + && m.GetParameters()[0].ParameterType.IsSubclassOf(typeof(Message)) + && typeof(SessionID).IsAssignableFrom(m.GetParameters()[1].ParameterType) + && m.ReturnType == typeof(void); } @@ -79,16 +66,14 @@ static public bool IsHandlerMethod(MethodInfo m) /// Process ("crack") a FIX message and call the registered handlers for that type, if any /// /// - /// - public void Crack(Message message, SessionID sessionID) + /// + public void Crack(Message message, SessionID sessionId) { Type messageType = message.GetType(); - Action onMessage = null; - - if (_callCache.TryGetValue(messageType, out onMessage)) + if (_callCache.TryGetValue(messageType, out Action? onMessage)) { - onMessage(message, sessionID); + onMessage(message, sessionId); } else { diff --git a/QuickFIXn/SocketSettings.cs b/QuickFIXn/SocketSettings.cs index 02a535b9f..c64f3b981 100644 --- a/QuickFIXn/SocketSettings.cs +++ b/QuickFIXn/SocketSettings.cs @@ -76,7 +76,7 @@ public class SocketSettings : ICloneable /// The common name is the name of the Server's certificate and it is usually /// the DNS name of the server. /// - public string ServerCommonName { get; internal set; } + public string? ServerCommonName { get; internal set; } /// /// Gets a value indicating whether certificates of the other endpoint should be validated. @@ -92,7 +92,7 @@ public class SocketSettings : ICloneable /// /// The certificate path. /// - public string CertificatePath { get; internal set; } + public string? CertificatePath { get; internal set; } /// /// Gets the certificate password. @@ -100,7 +100,7 @@ public class SocketSettings : ICloneable /// /// The certificate password. /// - public string CertificatePassword { get; internal set; } + public string? CertificatePassword { get; internal set; } /// /// Gets the SSL protocol to use (for initiator) or accept (for acceptor) @@ -126,14 +126,13 @@ public class SocketSettings : ICloneable /// public bool UseSSL { get; private set; } - /// /// Path to .cer with the public part of the Certificate CA to validate clients against (acceptor setting). /// /// /// The CA certificate path. /// - public string CACertificatePath { get; set; } + public string? CACertificatePath { get; set; } /// /// Gets a value indicating whether client certificate are required (acceptor setting). diff --git a/QuickFIXn/Transport/SslStreamFactory.cs b/QuickFIXn/Transport/SslStreamFactory.cs index a7b388bc1..d4a784360 100644 --- a/QuickFIXn/Transport/SslStreamFactory.cs +++ b/QuickFIXn/Transport/SslStreamFactory.cs @@ -46,6 +46,10 @@ public Stream CreateClientStreamAndAuthenticate(Stream innerStream) { // Setup secure SSL Communication X509CertificateCollection clientCertificates = GetClientCertificates(); + if (_socketSettings.ServerCommonName is null) { + throw new AuthenticationException( + $"QuickFIX/n configuration '{SessionSettings.SSL_SERVERNAME}' is unset"); + } sslStream.AuthenticateAsClient(_socketSettings.ServerCommonName, clientCertificates, _socketSettings.SslProtocol, diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e77c3d03d..607f546b4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -24,9 +24,9 @@ What's New * correction in FIX41 and FIX42: `D` to `UNDISCLOSED` * #863 - Change Message.ToString() to not alter object state anymore. (gbirchmeier) Use new function Message.ConstructString() if you need BodyLength/CheckSum to be updated. -* #xxx - cleanup/nullable-ize XXX (gbirchmeier) +* #TBD - cleanup/nullable-ize FixValues/Session/DataDictionary/DataDictionaryProvider/Message/MessageCracker/SocketSettings (gbirchmeier) * DataDictionary.CheckIsInGroup is now static - * Get rid of non-static DataDictionary.Validate functions. + * Get rid of non-static DataDictionary.Validate functions **Non-breaking changes** * #864 - when multiple threads race to init DefaultMessageFactory, From df3331027fb886e2d043d155e271277209037b6d Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Mon, 23 Sep 2024 20:41:28 -0500 Subject: [PATCH 3/6] cleanup DD.ParseMsgEl (now ParseMsgNode) and tests --- QuickFIXn/DataDictionary/DataDictionary.cs | 36 +++++++++++++--------- UnitTests/DataDictionaryTests.cs | 24 ++++++++++----- UnitTests/DataDictionary_ValidateTests.cs | 2 ++ 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/QuickFIXn/DataDictionary/DataDictionary.cs b/QuickFIXn/DataDictionary/DataDictionary.cs index 0372b633c..208ee692f 100644 --- a/QuickFIXn/DataDictionary/DataDictionary.cs +++ b/QuickFIXn/DataDictionary/DataDictionary.cs @@ -569,17 +569,22 @@ private void ParseTrailer(XmlDocument doc) /// /// /// - internal static void VerifyChildNode(XmlNode childNode, XmlNode parentNode) + internal static string VerifyChildNodeAndReturnNameAtt(XmlNode childNode, XmlNode parentNode) { if (childNode.Attributes == null) { throw new DictionaryParseException($"Malformed data dictionary: Found text-only node containing '{childNode.InnerText.Trim()}'"); } - if (childNode.Attributes["name"] == null) + + string? nameatt = childNode.Attributes["name"]?.Value; + + if (nameatt is null) { string messageTypeName = parentNode.Attributes?["name"]?.Value ?? parentNode.Name; throw new DictionaryParseException($"Malformed data dictionary: Found '{childNode.Name}' node without 'name' within parent '{parentNode.Name}/{messageTypeName}'"); } + + return nameatt; } /// @@ -601,8 +606,15 @@ private void ParseMsgNode(XmlNode node, DDMap ddmap, bool? componentRequired = n Console.WriteLine(s); */ - // TODO: I don't think node.Name is an acceptable alternative - string messageTypeName = node.Attributes?["name"]?.Value ?? node.Name; + string? messageTypeName = node.Name switch + { + "header" => "header", + "trailer" => "trailer", + _ => node.Attributes?["name"]?.Value + }; + + if (messageTypeName is null) + throw new DictionaryParseException($"Found <{node.Name}> that is missing its 'name' attribute"); if (!node.HasChildNodes) { return; } foreach (XmlNode childNode in node.ChildNodes) @@ -615,14 +627,7 @@ private void ParseMsgNode(XmlNode node, DDMap ddmap, bool? componentRequired = n Console.WriteLine(s); */ - VerifyChildNode(childNode, node); - - string? nameAttribute = childNode.Attributes?["name"]?.Value; - if (nameAttribute is null) - { - throw new DictionaryParseException( - $"A child node within '{messageTypeName} is missing its 'name' attribute"); - } + string nameAttribute = VerifyChildNodeAndReturnNameAtt(childNode, node); switch (childNode.Name) { @@ -634,6 +639,8 @@ private void ParseMsgNode(XmlNode node, DDMap ddmap, bool? componentRequired = n $"Field '{nameAttribute}' is not defined in section."); } + // If this child is in non-required component (componentRequired=false), + // then ignore the "required" attribute bool required = childNode.Attributes?["required"]?.Value == "Y" && componentRequired.GetValueOrDefault(true); if (required) @@ -648,8 +655,7 @@ private void ParseMsgNode(XmlNode node, DDMap ddmap, bool? componentRequired = n if (childNode.Name == "group") { - DDGrp grp = new(); - grp.NumFld = fld.Tag; + DDGrp grp = new() { NumFld = fld.Tag }; if (required) grp.Required = true; @@ -660,7 +666,7 @@ private void ParseMsgNode(XmlNode node, DDMap ddmap, bool? componentRequired = n case "component": XmlNode compNode = _componentsByName[nameAttribute]; - ParseMsgNode(compNode, ddmap, (childNode.Attributes?["required"]?.Value == "Y")); + ParseMsgNode(compNode, ddmap, childNode.Attributes?["required"]?.Value == "Y"); break; default: diff --git a/UnitTests/DataDictionaryTests.cs b/UnitTests/DataDictionaryTests.cs index 9937a1946..7fa26a631 100644 --- a/UnitTests/DataDictionaryTests.cs +++ b/UnitTests/DataDictionaryTests.cs @@ -335,7 +335,10 @@ public void ComponentFieldsRequirements() { DataDictionary dd = new DataDictionary(); dd.LoadFIXSpec("FIX44"); + + // AD => Instrument component (optional) => 55 (Symbol) Assert.False(dd.Messages["AD"].ReqFields.Contains(55)); + // 7 => Instrument component (required) => 55 (Symbol) Assert.True(dd.Messages["7"].ReqFields.Contains(55)); } @@ -382,20 +385,25 @@ private static XmlNode MakeNode(string xmlString) } [Test] - public void VerifyChildNode() - { - XmlNode parentNode = MakeNode(""); + public void VerifyChildNodeAndReturnNameAtt() { + XmlNode parentNode = MakeNode(""); - Assert.DoesNotThrow( - delegate { DataDictionary.VerifyChildNode(MakeNode(""), parentNode); }); + Assert.AreEqual("qty", DataDictionary.VerifyChildNodeAndReturnNameAtt( + MakeNode(""), parentNode)); DictionaryParseException dpx = Assert.Throws( - delegate { DataDictionary.VerifyChildNode(MakeNode("foo"), parentNode); }); + delegate { DataDictionary.VerifyChildNodeAndReturnNameAtt(MakeNode("foo"), parentNode); }); Assert.AreEqual("Malformed data dictionary: Found text-only node containing 'foo'", dpx!.Message); dpx = Assert.Throws( - delegate { DataDictionary.VerifyChildNode(MakeNode("qty"), parentNode); }); - Assert.AreEqual("Malformed data dictionary: Found 'field' node without 'name' within parent 'message/Daddy'", dpx!.Message); + delegate { DataDictionary.VerifyChildNodeAndReturnNameAtt(MakeNode("qty"), parentNode); }); + Assert.AreEqual("Malformed data dictionary: Found 'field' node without 'name' within parent 'parentnode/Daddy'", dpx!.Message); + + // alt error message, where parent has no name + parentNode = MakeNode(""); + dpx = Assert.Throws( + delegate { DataDictionary.VerifyChildNodeAndReturnNameAtt(MakeNode("qty"), parentNode); }); + Assert.AreEqual("Malformed data dictionary: Found 'field' node without 'name' within parent 'parentnode/parentnode'", dpx!.Message); } } } diff --git a/UnitTests/DataDictionary_ValidateTests.cs b/UnitTests/DataDictionary_ValidateTests.cs index 2f7cf0fce..61dfcb9bb 100644 --- a/UnitTests/DataDictionary_ValidateTests.cs +++ b/UnitTests/DataDictionary_ValidateTests.cs @@ -275,6 +275,8 @@ public void OptionalComponentRequiredField() Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); + // AD only requires 568 and 569. + // It has components, but none are required. dd.Validate(message, beginString, msgType); } From 2143106aca695e2c708e8717026421a534e5d758 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Tue, 24 Sep 2024 00:43:11 -0500 Subject: [PATCH 4/6] final cleanup of DataDictionary --- QuickFIXn/DataDictionary/DataDictionary.cs | 6 - QuickFIXn/Session.cs | 2 +- UnitTests/DataDictionaryTests.cs | 6 +- UnitTests/DataDictionary_ValidateTests.cs | 161 ++++++++------------- 4 files changed, 66 insertions(+), 109 deletions(-) diff --git a/QuickFIXn/DataDictionary/DataDictionary.cs b/QuickFIXn/DataDictionary/DataDictionary.cs index 208ee692f..c99568432 100644 --- a/QuickFIXn/DataDictionary/DataDictionary.cs +++ b/QuickFIXn/DataDictionary/DataDictionary.cs @@ -106,12 +106,6 @@ public static void Validate(Message message, DataDictionary? transportDataDict, appDataDict.Iterate(message, msgType); } - // TODO Get rid of this. All calls can use the static call. - public void Validate(Message message, string beginString, string msgType) - { - Validate(message, this, this, beginString, msgType); - } - public static void CheckHasNoRepeatedTags(FieldMap map) { if (map.RepeatedTags.Count > 0) diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index 260a9e71e..8612fc8c6 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -569,7 +569,7 @@ internal void Next(MessageBuilder msgBuilder) } else { - SessionDataDictionary.Validate(message, beginString, msgType); + DataDictionary.DataDictionary.Validate(message, SessionDataDictionary, SessionDataDictionary, beginString, msgType); } if (MsgType.LOGON.Equals(msgType)) diff --git a/UnitTests/DataDictionaryTests.cs b/UnitTests/DataDictionaryTests.cs index 7fa26a631..fdb261884 100644 --- a/UnitTests/DataDictionaryTests.cs +++ b/UnitTests/DataDictionaryTests.cs @@ -134,7 +134,7 @@ public void HeaderGroupTest() dd.LoadFIXSpec("FIX44"); DDMap headerMap = dd.Header; Assert.True(headerMap.IsGroup(627)); - QuickFix.DataDictionary.DDGrp grpMap = headerMap.GetGroup(627); + DDGrp grpMap = headerMap.GetGroup(627); Assert.True(dd.Header.GetGroup(627).IsField(628)); Assert.True(grpMap.IsField(628)); } @@ -363,11 +363,11 @@ public void ParseThroughComments() // The fact that it doesn't throw is sufficient, but we'll do some other checks anyway. - var logon = dd.GetMapForMessage("A"); + DDMap logon = dd.GetMapForMessage("A")!; Assert.True(logon.IsField(108)); // HeartBtInt Assert.True(logon.IsField(9000)); // CustomField - var news = dd.GetMapForMessage("B"); + DDMap news = dd.GetMapForMessage("B")!; Assert.True(news.IsField(148)); // Headline Assert.True(news.IsGroup(33)); // LinesOfText Assert.True(news.GetGroup(33).IsField(355)); // EncodedText diff --git a/UnitTests/DataDictionary_ValidateTests.cs b/UnitTests/DataDictionary_ValidateTests.cs index 61dfcb9bb..809399ff2 100644 --- a/UnitTests/DataDictionary_ValidateTests.cs +++ b/UnitTests/DataDictionary_ValidateTests.cs @@ -13,10 +13,6 @@ public class DataDictionary_ValidateTests { [Test] public void UnsupportedVersionTest() { - //string ddFileContent = - // "
"; - //DataDictionary txDd = new(new MemoryStream(Encoding.Unicode.GetBytes(ddFileContent))); - DataDictionary dd = new(); dd.LoadFIXSpec("FIX44"); @@ -58,19 +54,18 @@ public void ValidateWrongType() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = {"8=FIX.4.4", "9=120", "35=D", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "11=clordid", "55=sym", "54=1", "60=20110909-09:09:09.999", "40=1", - "38=failboat", // should be a decimal - "10=64"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=120|35=D|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "11=clordid|55=sym|54=1|60=20110909-09:09:09.999|40=1|" + + "38=failboat|" + // should be a decimal + "10=64|").Replace('|', Message.SOH); string msgType = "D"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.That(() => DataDictionary.Validate(message, dd, dd, beginString, msgType), + Throws.TypeOf()); } [Test] @@ -83,14 +78,13 @@ public void ValidateWithRepeatingGroupTest() string msgStr = ("8=FIX.4.2|9=87|35=B|34=3|49=CLIENT1|" + "52=20111012-22:15:55.474|56=EXECUTOR|148=AAAAAAA|" + "33=2|58=L1|58=L2|10=016|").Replace('|', Message.SOH); - - QuickFix.Fields.MsgType msgType = Message.IdentifyType(msgStr); + MsgType msgType = Message.IdentifyType(msgStr); string beginString = Message.ExtractBeginString(msgStr); Message message = f.Create(beginString, msgType.Obj); message.FromString(msgStr, true, dd, dd, f); - dd.Validate(message, beginString, msgType.Obj); + DataDictionary.Validate(message, dd, dd, beginString, msgType.Obj); } [Test] @@ -99,11 +93,9 @@ public void ValidateGroupBeginsGroup() DataDictionary dd = new DataDictionary(); dd.LoadTestFIXSpec("group_begins_group"); - string pipedStr = "8=FIX.9.9|9=167|35=magic|34=3|49=CLIENT1|52=20111012-22:15:55.474|56=EXECUTOR|" - + "1111=mundane|5555=magicfield|6660=1|7770=2|7711=Hoppy|7712=brown|" - + "7711=Floppy|7712=white|6661=abracadabra|10=48|"; - string msgStr = pipedStr.Replace('|', Message.SOH); - + string msgStr = ("8=FIX.9.9|9=167|35=magic|34=3|49=CLIENT1|52=20111012-22:15:55.474|56=EXECUTOR|" + + "1111=mundane|5555=magicfield|6660=1|7770=2|7711=Hoppy|7712=brown|" + + "7711=Floppy|7712=white|6661=abracadabra|10=48|").Replace('|', Message.SOH); string beginString = Message.ExtractBeginString(msgStr); Message msg = new Message(msgStr, dd, dd, false); @@ -128,22 +120,20 @@ public void ValidateWrongTypeInRepeatingGroup() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = {"8=FIX.4.4", "9=111", "35=V", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "262=mdreqid", "263=0", "264=5", - "267=1", // MDReqGrp - "269=failboat", // should be a char - "146=1", // InstrmtMDReqGrp - "55=sym", - "10=91"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=111|35=V|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "262=mdreqid|263=0|264=5|" + + "267=1|" + // MDReqGrp + "269=failboat|" + // should be a char + "146=1|" + // InstrmtMDReqGrp + "55=sym|" + + "10=91|").Replace('|', Message.SOH); string msgType = "V"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.Throws(() => DataDictionary.Validate(message, dd, dd, beginString, msgType)); } [Test] @@ -153,23 +143,21 @@ public void ValidateWrongTypeInNestedRepeatingGroup() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = {"8=FIX.4.4", "9=185", "35=J", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "70=AllocID", "71=0", "626=1", "857=0", "54=1", "55=sym", "53=1", "6=5.5", "75=20110909-09:09:09.999", - "73=1", // NoOrders - "11=clordid", - "756=1", // NoNested2PartyIDs - "757=nested2partyid", - "759=failboat", // supposed to be a int - "10=48"}; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=185|35=J|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "70=AllocID|71=0|626=1|857=0|54=1|55=sym|53=1|6=5.5|75=20110909-09:09:09.999|" + + "73=1|" + // NoOrders + "11=clordid|" + + "756=1|" + // NoNested2PartyIDs + "757=nested2partyid|" + + "759=failboat|" + // supposed to be a int + "10=48|").Replace('|', Message.SOH); string msgType = "J"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.Throws(() => DataDictionary.Validate(message, dd, dd, beginString, msgType)); } [Test] @@ -179,17 +167,15 @@ public void ValidateDateAndTimeFields() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = { "8=FIX.4.4", "9=104", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=19" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=104|35=W|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "55=sym|268=1|269=0|272=20111012|273=22:15:30.444|10=19|").Replace('|', Message.SOH); string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - dd.Validate(message, beginString, msgType); + DataDictionary.Validate(message, dd, dd, beginString, msgType); } [Test] @@ -200,17 +186,15 @@ public void ValidateDateTime_Invalid() QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); // intentionally invalid SendingTime (52/DateTime) - string[] msgFields = { "8=FIX.4.4", "9=91", "35=W", "34=3", "49=sender", "52=20110909", "56=target", - "55=sym", "268=1", "269=0", "272=20111012", "273=22:15:30.444", "10=51" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=91|35=W|34=3|49=sender|52=20110909|56=target|" + + "55=sym|268=1|269=0|272=20111012|273=22:15:30.444|10=51|").Replace('|', Message.SOH); string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.Throws(() => DataDictionary.Validate(message, dd, dd, beginString, msgType)); } [Test] @@ -221,17 +205,16 @@ public void ValidateDateOnly_Invalid() QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); // intentionally invalid MDEntryDate (272/DateOnly) - string[] msgFields = { "8=FIX.4.4", "9=117", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", "268=1", "269=0", "272=20111012-22:15:30.444", "273=22:15:30.444", "10=175" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=117|35=W|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "55=sym|268=1|269=0|272=20111012-22:15:30.444|273=22:15:30.444|10=175|") + .Replace('|', Message.SOH); string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.Throws(() => DataDictionary.Validate(message, dd, dd, beginString, msgType)); } [Test] @@ -242,17 +225,15 @@ public void ValidateTimeOnly_Invalid() QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); // intentionally invalid MDEntryTime (272/TimeOnly) - string[] msgFields = { "8=FIX.4.4", "9=113", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", "268=1", "269=0", "272=20111012", "273=20111012-22:15:30.444", "10=200" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=113|35=W|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "55=sym|268=1|269=0|272=20111012|273=20111012-22:15:30.444|10=200|").Replace('|', Message.SOH); string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.Throws(() => DataDictionary.Validate(message, dd, dd, beginString, msgType)); } [Test] @@ -265,10 +246,8 @@ public void OptionalComponentRequiredField() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = { "8=FIX.4.4", "9=77", "35=AD", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "568=tradereqid", "569=0", "10=109" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=77|35=AD|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "568=tradereqid|569=0|10=109|").Replace('|', Message.SOH); string msgType = "AD"; string beginString = "FIX.4.4"; @@ -277,7 +256,7 @@ public void OptionalComponentRequiredField() // AD only requires 568 and 569. // It has components, but none are required. - dd.Validate(message, beginString, msgType); + DataDictionary.Validate(message, dd, dd, beginString, msgType); } [Test] @@ -287,17 +266,16 @@ public void RequiredComponentRequiredField() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = { "8=FIX.4.4", "9=76", "35=7", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "2=AdvId", "5=N", "4=B", "53=1", "10=138" }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=76|35=7|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "2=AdvId|5=N|4=B|53=1|10=138|").Replace('|', Message.SOH); string msgType = "7"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd, f); - var ex = Assert.Throws(delegate { dd.Validate(message, beginString, msgType); }); + var ex = Assert.Throws(() => + DataDictionary.Validate(message, dd, dd, beginString, msgType)); Assert.AreEqual(55, ex!.Field); } @@ -308,22 +286,17 @@ public void ValidateMultipleValueStringType() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = - { - "8=FIX.4.4", "9=99", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", - "268=1", "269=0", "270=123.23", "271=2", "277=A B", - "10=213" - }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=99|35=W|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "55=sym|" + + "268=1|269=0|270=123.23|271=2|277=A B|" + + "10=213|").Replace('|', Message.SOH); string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd); - dd.Validate(message, beginString, msgType); + DataDictionary.Validate(message, dd, dd, beginString, msgType); } [Test] // Issue #66 @@ -333,22 +306,17 @@ public void ValidateMultipleValueStringType_Invalid() dd.LoadFIXSpec("FIX44"); QuickFix.FIX44.MessageFactory f = new QuickFix.FIX44.MessageFactory(); - string[] msgFields = - { - "8=FIX.4.4", "9=99", "35=W", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "55=sym", - "268=1", "269=0", "270=123.23", "271=2", "277=A 1", - "10=196" - }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = ("8=FIX.4.4|9=99|35=W|34=3|49=sender|52=20110909-09:09:09.999|56=target|" + + "55=sym|" + + "268=1|269=0|270=123.23|271=2|277=A 1|" + + "10=196|").Replace('|', Message.SOH); string msgType = "W"; string beginString = "FIX.4.4"; Message message = f.Create(beginString, msgType); message.FromString(msgStr, true, dd, dd); - Assert.That(() => dd.Validate(message, beginString, msgType), Throws.TypeOf()); + Assert.Throws(() => DataDictionary.Validate(message, dd, dd, beginString, msgType)); } [Test] // Issue #282 @@ -358,13 +326,8 @@ public void ValidateTagSpecifiedWithoutAValue() dd.LoadFIXSpec("FIX42"); QuickFix.FIX42.MessageFactory f = new QuickFix.FIX42.MessageFactory(); - string[] msgFields = - { - "8=FIX.4.2", "9=70", "35=B", "34=3", "49=sender", "52=20110909-09:09:09.999", "56=target", - "358=", "148=", "33=0", "10=150" - }; - string msgStr = string.Join(Message.SOH, msgFields) + Message.SOH; - + string msgStr = "8=FIX.4.2|9=70|35=B|34=3|49=sender|52=20110909-09:09:09.999|56=target|358=|148=|33=0|10=150|" + .Replace('|', Message.SOH); string msgType = "B"; string beginString = "FIX.4.2"; @@ -372,9 +335,9 @@ public void ValidateTagSpecifiedWithoutAValue() message.FromString(msgStr, true, dd, dd); dd.CheckFieldsHaveValues = true; - Assert.Throws(delegate { dd.Validate(message, beginString, msgType); }); + Assert.Throws(delegate { DataDictionary.Validate(message, dd, dd, beginString, msgType); }); dd.CheckFieldsHaveValues = false; - Assert.DoesNotThrow(delegate { dd.Validate(message, beginString, msgType); }); + Assert.DoesNotThrow(delegate { DataDictionary.Validate(message, dd, dd, beginString, msgType); }); } } From 5f4b55068253de755901e4b68031fab22f5c3823 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Tue, 24 Sep 2024 15:03:04 -0500 Subject: [PATCH 5/6] non-null FixValues template param --- QuickFIXn/FixValues.cs | 2 +- RELEASE_NOTES.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/QuickFIXn/FixValues.cs b/QuickFIXn/FixValues.cs index 91142ad71..77097345f 100755 --- a/QuickFIXn/FixValues.cs +++ b/QuickFIXn/FixValues.cs @@ -2,7 +2,7 @@ namespace QuickFix { - public class FixValue + public class FixValue where T : notnull { private T _value; diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 607f546b4..aa8a7c97b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -24,7 +24,8 @@ What's New * correction in FIX41 and FIX42: `D` to `UNDISCLOSED` * #863 - Change Message.ToString() to not alter object state anymore. (gbirchmeier) Use new function Message.ConstructString() if you need BodyLength/CheckSum to be updated. -* #TBD - cleanup/nullable-ize FixValues/Session/DataDictionary/DataDictionaryProvider/Message/MessageCracker/SocketSettings (gbirchmeier) +* #887 - cleanup/nullable-ize FixValues, Session, DataDictionary, DataDictionaryProvider, Message, MessageCracker, + SocketSettings, SslStreamFactory (gbirchmeier) * DataDictionary.CheckIsInGroup is now static * Get rid of non-static DataDictionary.Validate functions From 2eb2c7719026db5021f5debe306a1ef7539915e6 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Tue, 24 Sep 2024 15:32:04 -0500 Subject: [PATCH 6/6] split FixValue classes into separate files --- QuickFIXn/FixValue.cs | 33 +++++ QuickFIXn/FixValues.cs | 141 -------------------- QuickFIXn/FixValues/ApplVerID.cs | 45 +++++++ QuickFIXn/FixValues/BeginString.cs | 16 +++ QuickFIXn/FixValues/BusinessRejectReason.cs | 18 +++ QuickFIXn/FixValues/SessionRejectReason.cs | 28 ++++ 6 files changed, 140 insertions(+), 141 deletions(-) create mode 100755 QuickFIXn/FixValue.cs delete mode 100755 QuickFIXn/FixValues.cs create mode 100644 QuickFIXn/FixValues/ApplVerID.cs create mode 100644 QuickFIXn/FixValues/BeginString.cs create mode 100644 QuickFIXn/FixValues/BusinessRejectReason.cs create mode 100644 QuickFIXn/FixValues/SessionRejectReason.cs diff --git a/QuickFIXn/FixValue.cs b/QuickFIXn/FixValue.cs new file mode 100755 index 000000000..b6090aa6c --- /dev/null +++ b/QuickFIXn/FixValue.cs @@ -0,0 +1,33 @@ +namespace QuickFix; + +public class FixValue where T : notnull +{ + private T _value; + + public T Value => _value; + public string Description { get; } + + public FixValue(T value, string description) + { + _value = value; + Description = description; + } + + public override bool Equals(object? obj) + { + if (obj is null || (this.GetType() != obj.GetType())) + return false; + FixValue rhs = (FixValue)obj; + return this.Value.Equals(rhs.Value); + } + + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + public override string ToString() + { + return Description; + } +} diff --git a/QuickFIXn/FixValues.cs b/QuickFIXn/FixValues.cs deleted file mode 100755 index 77097345f..000000000 --- a/QuickFIXn/FixValues.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System.Collections.Generic; - -namespace QuickFix -{ - public class FixValue where T : notnull - { - private T _value; - - public T Value => _value; - public string Description { get; } - - public FixValue(T value, string description) - { - _value = value; - Description = description; - } - - public override bool Equals(object? obj) - { - if (obj is null || (this.GetType() != obj.GetType())) - return false; - FixValue rhs = (FixValue)obj; - return this.Value.Equals(rhs.Value); - } - - public override int GetHashCode() - { - return _value.GetHashCode(); - } - - public override string ToString() - { - return Description; - } - } - - namespace FixValues - { - public static class ApplVerID - { - public const string FIX40 = "2"; - public const string FIX41 = "3"; - public const string FIX42 = "4"; - public const string FIX43 = "5"; - public const string FIX44 = "6"; - public const string FIX50 = "7"; - public const string FIX50SP1 = "8"; - public const string FIX50SP2 = "9"; - - public static string FromBeginString(string beginString) - { - return beginString switch - { - BeginString.FIX40 => FIX40, - BeginString.FIX41 => FIX41, - BeginString.FIX42 => FIX42, - BeginString.FIX43 => FIX43, - BeginString.FIX44 => FIX44, - BeginString.FIX50 => FIX50, - BeginString.FIX50SP1 => FIX50SP1, - BeginString.FIX50SP2 => FIX50SP2, - _ => beginString - }; - } - - public static string ToBeginString(string applVerId) { - return applVerId switch - { - FIX40 => BeginString.FIX40, - FIX41 => BeginString.FIX41, - FIX42 => BeginString.FIX42, - FIX43 => BeginString.FIX43, - FIX44 => BeginString.FIX44, - FIX50 => BeginString.FIX50, - FIX50SP1 => BeginString.FIX50SP1, - FIX50SP2 => BeginString.FIX50SP2, - _ => throw new System.ArgumentException( - $"ApplVerId parameter '{applVerId}' does not map to a known BeginString") - }; - } - } - - public static class BeginString - { - // The FIX5+ beginstrings aren't legitimate, but we use them internally. - - public const string FIXT11 = "FIXT.1.1"; - public const string FIX50SP2 = "FIX.5.0SP2"; - public const string FIX50SP1 = "FIX.5.0SP1"; - public const string FIX50 = "FIX.5.0"; - public const string FIX44 = "FIX.4.4"; - public const string FIX43 = "FIX.4.3"; - public const string FIX42 = "FIX.4.2"; - public const string FIX41 = "FIX.4.1"; - public const string FIX40 = "FIX.4.0"; - } - - public class SessionRejectReason : FixValue - { - public static SessionRejectReason INVALID_TAG_NUMBER = new(0, "Invalid tag number"); - public static SessionRejectReason REQUIRED_TAG_MISSING = new(1, "Required tag missing"); - public static SessionRejectReason TAG_NOT_DEFINED_FOR_THIS_MESSAGE_TYPE = new(2, "Tag not defined for this message type"); - public static SessionRejectReason UNDEFINED_TAG = new(3, "Undefined Tag"); - public static SessionRejectReason TAG_SPECIFIED_WITHOUT_A_VALUE = new(4, "Tag specified without a value"); - public static SessionRejectReason VALUE_IS_INCORRECT = new(5, "Value is incorrect (out of range) for this tag"); - public static SessionRejectReason INCORRECT_DATA_FORMAT_FOR_VALUE = new(6, "Incorrect data format for value"); - public static SessionRejectReason DECRYPTION_PROBLEM = new(7, "Decryption problem"); - public static SessionRejectReason SIGNATURE_PROBLEM = new(8, "Signature problem"); - public static SessionRejectReason COMPID_PROBLEM = new(9, "CompID problem"); - public static SessionRejectReason SENDING_TIME_ACCURACY_PROBLEM = new(10, "SendingTime accuracy problem"); - public static SessionRejectReason INVALID_MSGTYPE = new(11, "Invalid MsgType"); - public static SessionRejectReason XML_VALIDATION_ERROR = new(12, "XML validation error"); - public static SessionRejectReason TAG_APPEARS_MORE_THAN_ONCE = new(13, "Tag appears more than once"); - public static SessionRejectReason TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER = new(14, "Tag specified out of required order"); - public static SessionRejectReason REPEATING_GROUP_FIELDS_OUT_OF_ORDER = new(15, "Repeating group fields out of order"); - public static SessionRejectReason INCORRECT_NUM_IN_GROUP_COUNT_FOR_REPEATING_GROUP = new(16, "Incorrect NumInGroup count for repeating group"); - public static SessionRejectReason NON_DATA_VALUE_INCLUDES_FIELD_DELIMITER = new(17, "Non-data value includes field delimiter"); - public static SessionRejectReason OTHER = new(99, "Other"); - - public SessionRejectReason(int value, string description) - : base(value, description) - { } - } - - public class BusinessRejectReason - { - public static readonly Dictionary RejText = new Dictionary() - { - { Fields.BusinessRejectReason.OTHER, "Other" }, - { Fields.BusinessRejectReason.UNKNOWN_ID, "Unknown ID" }, - { Fields.BusinessRejectReason.UNKNOWN_SECURITY, "Unknown Security" }, - { Fields.BusinessRejectReason.UNKNOWN_MESSAGE_TYPE, "Unsupported Message Type" }, - { Fields.BusinessRejectReason.APPLICATION_NOT_AVAILABLE, "Application Not Available" }, - { Fields.BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, "Conditionally Required Field Missing" }, - { Fields.BusinessRejectReason.NOT_AUTHORIZED, "Not authorized" }, - { Fields.BusinessRejectReason.DELIVERTO_FIRM_NOT_AVAILABLE_AT_THIS_TIME, "DeliverTo Firm Not Available At This Time" } - }; - - } - } -} diff --git a/QuickFIXn/FixValues/ApplVerID.cs b/QuickFIXn/FixValues/ApplVerID.cs new file mode 100644 index 000000000..55c4f14ac --- /dev/null +++ b/QuickFIXn/FixValues/ApplVerID.cs @@ -0,0 +1,45 @@ +namespace QuickFix.FixValues; + +public static class ApplVerID +{ + public const string FIX40 = "2"; + public const string FIX41 = "3"; + public const string FIX42 = "4"; + public const string FIX43 = "5"; + public const string FIX44 = "6"; + public const string FIX50 = "7"; + public const string FIX50SP1 = "8"; + public const string FIX50SP2 = "9"; + + public static string FromBeginString(string beginString) + { + return beginString switch + { + BeginString.FIX40 => FIX40, + BeginString.FIX41 => FIX41, + BeginString.FIX42 => FIX42, + BeginString.FIX43 => FIX43, + BeginString.FIX44 => FIX44, + BeginString.FIX50 => FIX50, + BeginString.FIX50SP1 => FIX50SP1, + BeginString.FIX50SP2 => FIX50SP2, + _ => beginString + }; + } + + public static string ToBeginString(string applVerId) { + return applVerId switch + { + FIX40 => BeginString.FIX40, + FIX41 => BeginString.FIX41, + FIX42 => BeginString.FIX42, + FIX43 => BeginString.FIX43, + FIX44 => BeginString.FIX44, + FIX50 => BeginString.FIX50, + FIX50SP1 => BeginString.FIX50SP1, + FIX50SP2 => BeginString.FIX50SP2, + _ => throw new System.ArgumentException( + $"ApplVerId parameter '{applVerId}' does not map to a known BeginString") + }; + } +} diff --git a/QuickFIXn/FixValues/BeginString.cs b/QuickFIXn/FixValues/BeginString.cs new file mode 100644 index 000000000..73a48d50c --- /dev/null +++ b/QuickFIXn/FixValues/BeginString.cs @@ -0,0 +1,16 @@ +namespace QuickFix.FixValues; + +public static class BeginString +{ + // The FIX5+ beginstrings aren't legitimate, but we use them internally. + + public const string FIXT11 = "FIXT.1.1"; + public const string FIX50SP2 = "FIX.5.0SP2"; + public const string FIX50SP1 = "FIX.5.0SP1"; + public const string FIX50 = "FIX.5.0"; + public const string FIX44 = "FIX.4.4"; + public const string FIX43 = "FIX.4.3"; + public const string FIX42 = "FIX.4.2"; + public const string FIX41 = "FIX.4.1"; + public const string FIX40 = "FIX.4.0"; +} diff --git a/QuickFIXn/FixValues/BusinessRejectReason.cs b/QuickFIXn/FixValues/BusinessRejectReason.cs new file mode 100644 index 000000000..d928c587b --- /dev/null +++ b/QuickFIXn/FixValues/BusinessRejectReason.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace QuickFix.FixValues; + +public class BusinessRejectReason +{ + public static readonly Dictionary RejText = new() + { + { Fields.BusinessRejectReason.OTHER, "Other" }, + { Fields.BusinessRejectReason.UNKNOWN_ID, "Unknown ID" }, + { Fields.BusinessRejectReason.UNKNOWN_SECURITY, "Unknown Security" }, + { Fields.BusinessRejectReason.UNKNOWN_MESSAGE_TYPE, "Unsupported Message Type" }, + { Fields.BusinessRejectReason.APPLICATION_NOT_AVAILABLE, "Application Not Available" }, + { Fields.BusinessRejectReason.CONDITIONALLY_REQUIRED_FIELD_MISSING, "Conditionally Required Field Missing" }, + { Fields.BusinessRejectReason.NOT_AUTHORIZED, "Not authorized" }, + { Fields.BusinessRejectReason.DELIVERTO_FIRM_NOT_AVAILABLE_AT_THIS_TIME, "DeliverTo Firm Not Available At This Time" } + }; +} diff --git a/QuickFIXn/FixValues/SessionRejectReason.cs b/QuickFIXn/FixValues/SessionRejectReason.cs new file mode 100644 index 000000000..e25fcfa43 --- /dev/null +++ b/QuickFIXn/FixValues/SessionRejectReason.cs @@ -0,0 +1,28 @@ +namespace QuickFix.FixValues; + +public class SessionRejectReason : FixValue +{ + public static SessionRejectReason INVALID_TAG_NUMBER = new(0, "Invalid tag number"); + public static SessionRejectReason REQUIRED_TAG_MISSING = new(1, "Required tag missing"); + public static SessionRejectReason TAG_NOT_DEFINED_FOR_THIS_MESSAGE_TYPE = new(2, "Tag not defined for this message type"); + public static SessionRejectReason UNDEFINED_TAG = new(3, "Undefined Tag"); + public static SessionRejectReason TAG_SPECIFIED_WITHOUT_A_VALUE = new(4, "Tag specified without a value"); + public static SessionRejectReason VALUE_IS_INCORRECT = new(5, "Value is incorrect (out of range) for this tag"); + public static SessionRejectReason INCORRECT_DATA_FORMAT_FOR_VALUE = new(6, "Incorrect data format for value"); + public static SessionRejectReason DECRYPTION_PROBLEM = new(7, "Decryption problem"); + public static SessionRejectReason SIGNATURE_PROBLEM = new(8, "Signature problem"); + public static SessionRejectReason COMPID_PROBLEM = new(9, "CompID problem"); + public static SessionRejectReason SENDING_TIME_ACCURACY_PROBLEM = new(10, "SendingTime accuracy problem"); + public static SessionRejectReason INVALID_MSGTYPE = new(11, "Invalid MsgType"); + public static SessionRejectReason XML_VALIDATION_ERROR = new(12, "XML validation error"); + public static SessionRejectReason TAG_APPEARS_MORE_THAN_ONCE = new(13, "Tag appears more than once"); + public static SessionRejectReason TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER = new(14, "Tag specified out of required order"); + public static SessionRejectReason REPEATING_GROUP_FIELDS_OUT_OF_ORDER = new(15, "Repeating group fields out of order"); + public static SessionRejectReason INCORRECT_NUM_IN_GROUP_COUNT_FOR_REPEATING_GROUP = new(16, "Incorrect NumInGroup count for repeating group"); + public static SessionRejectReason NON_DATA_VALUE_INCLUDES_FIELD_DELIMITER = new(17, "Non-data value includes field delimiter"); + public static SessionRejectReason OTHER = new(99, "Other"); + + public SessionRejectReason(int value, string description) + : base(value, description) + { } +}