From 2bad49e8c4b55da1a44403b4c6815adc0e698c44 Mon Sep 17 00:00:00 2001 From: NoviProg <80051122+NoviProg@users.noreply.github.com> Date: Thu, 1 Sep 2022 22:47:12 +0300 Subject: [PATCH 1/4] Add extension method ReadGroups This method make it easy to work groups. By use enumerator --- QuickFIXn/Extensions/GroupExtension.cs | 35 +++++++++++++ UnitTests/Extensions/GroupExtensionTest.cs | 61 ++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 QuickFIXn/Extensions/GroupExtension.cs create mode 100644 UnitTests/Extensions/GroupExtensionTest.cs diff --git a/QuickFIXn/Extensions/GroupExtension.cs b/QuickFIXn/Extensions/GroupExtension.cs new file mode 100644 index 000000000..0199a1c25 --- /dev/null +++ b/QuickFIXn/Extensions/GroupExtension.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace QuickFix.Extensions +{ + public static class GroupExtension + { + /// + /// Gets an instance of enumerable groups + /// + /// A repeating field group within a message + /// Field container used by messages, groups, and composites + /// The FIX group tag + /// retrieved enumerable groups + /// + public static IEnumerable ReadGroups(this FieldMap message, int noGroupTag) where TGroup : Group //, new() + { + if (message != null && message.IsSetField(noGroupTag)) + { + int grpCount = message.GetInt(noGroupTag); + for (int grpIndex = 1; grpIndex <= grpCount; grpIndex += 1) + { + //var group = new TGroup(); + //message.GetGroup(grpIndex, group); + var group = message.GetGroup(grpIndex, noGroupTag) as TGroup; + //if (group == null) + // throw new InvalidCastException($"Can't cast {message.GetGroup(grpIndex, noGroupTag).GetType()} to {typeof(TGroup)}"); + yield return group; + } + } + } + } +} diff --git a/UnitTests/Extensions/GroupExtensionTest.cs b/UnitTests/Extensions/GroupExtensionTest.cs new file mode 100644 index 000000000..9729d1664 --- /dev/null +++ b/UnitTests/Extensions/GroupExtensionTest.cs @@ -0,0 +1,61 @@ +using NUnit.Framework; +using QuickFix; +using QuickFix.Fields; +using QuickFix.Extensions; +using System; +using UnitTests.TestHelpers; +using System.Linq; + +namespace UnitTests.Extensions +{ + [TestFixture] + public class GroupExtensionTest + { + private IMessageFactory _defaultMsgFactory = new DefaultMessageFactory(); + + [Test] + public void EnumerableGroupsTest() + { + var nos = new QuickFix.FIX42.NewOrderSingle(); + for (int i = 0; i < 100; i++) + { + var noTradingSessions = new QuickFix.FIX42.NewOrderSingle.NoTradingSessionsGroup(); + noTradingSessions.SetField(new StringField(336, $"OHHAI_{i}")); + nos.AddGroup(noTradingSessions); + } + int iterator = 0; + foreach (var item in nos.ReadGroups(Tags.NoTradingSessions)) + { + Assert.That(item.GetString(336), Is.EqualTo($"OHHAI_{iterator++}")); + } + Assert.True(iterator == 100); + + // Wrong tag + Assert.IsEmpty(nos.ReadGroups(1)); + + // Wrong group + foreach (var item in nos.ReadGroups(Tags.NoTradingSessions)) + { + Assert.IsNull(item); + } + + //-- + String data = "8=FIX.4.4\x01" + "9=309\x01" + "35=8\x01" + "49=ASX\x01" + "56=CL1_FIX44\x01" + "34=4\x01" + "52=20060324-01:05:58\x01" + "" + + "17=X-B-WOW-1494E9A0:58BD3F9D-1109\x01" + "150=D\x01" + "39=0\x01" + "11=184271\x01" + "38=200\x01" + "198=1494E9A0:58BD3F9D\x01" + "" + + "526=4324\x01" + "37=B-WOW-1494E9A0:58BD3F9D\x01" + "55=WOW\x01" + "54=1\x01" + "151=200\x01" + "14=0\x01" + "40=2\x01" + "44=15\x01" + "59=1\x01" + "6=0\x01" + "" + + "453=3\x01" + + "448=AAA35791\x01" + "447=D\x01" + "452=3\x01" + "802=1\x01" + "523=OHAI123\x01" + + "448=8\x01" + "447=D\x01" + "452=4\x01" + + "448=FIX11\x01" + "447=D\x01" + "452=36\x01" + "" + + "60=20060320-03:34:29\x01" + "10=169\x01" + ""; + var msg = new QuickFix.FIX44.ExecutionReport(); + var dd = new QuickFix.DataDictionary.DataDictionary(); + dd.LoadFIXSpec("FIX44"); + msg.FromString(data, false, dd, dd, _defaultMsgFactory); + + var eRNoPartyIDsGroup = msg.ReadGroups(Tags.NoPartyIDs).ToList(); + Assert.That(eRNoPartyIDsGroup[0].PartyID?.Obj, Is.EqualTo("AAA35791")); + Assert.That(eRNoPartyIDsGroup[2].PartyID?.Obj, Is.EqualTo("FIX11")); + } + } +} From 512a06ac92c46162a744cd9a6c826d211167d012 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 20 Dec 2024 11:35:03 -0600 Subject: [PATCH 2/4] use NUnit 4 asserts --- UnitTests/Extensions/GroupExtensionTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UnitTests/Extensions/GroupExtensionTest.cs b/UnitTests/Extensions/GroupExtensionTest.cs index 9729d1664..a5a96d066 100644 --- a/UnitTests/Extensions/GroupExtensionTest.cs +++ b/UnitTests/Extensions/GroupExtensionTest.cs @@ -28,15 +28,15 @@ public void EnumerableGroupsTest() { Assert.That(item.GetString(336), Is.EqualTo($"OHHAI_{iterator++}")); } - Assert.True(iterator == 100); + Assert.That(iterator == 100); // Wrong tag - Assert.IsEmpty(nos.ReadGroups(1)); + Assert.That(nos.ReadGroups(1), Is.Empty); // Wrong group foreach (var item in nos.ReadGroups(Tags.NoTradingSessions)) { - Assert.IsNull(item); + Assert.That(item, Is.Null); } //-- @@ -54,8 +54,8 @@ public void EnumerableGroupsTest() msg.FromString(data, false, dd, dd, _defaultMsgFactory); var eRNoPartyIDsGroup = msg.ReadGroups(Tags.NoPartyIDs).ToList(); - Assert.That(eRNoPartyIDsGroup[0].PartyID?.Obj, Is.EqualTo("AAA35791")); - Assert.That(eRNoPartyIDsGroup[2].PartyID?.Obj, Is.EqualTo("FIX11")); + Assert.That(eRNoPartyIDsGroup[0].PartyID?.Value, Is.EqualTo("AAA35791")); + Assert.That(eRNoPartyIDsGroup[2].PartyID?.Value, Is.EqualTo("FIX11")); } } } From 2e2cd8200064ad1e186388a9ce5ded36b52d813e Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 20 Dec 2024 11:51:07 -0600 Subject: [PATCH 3/4] Improve ReadGroups and its tests --- QuickFIXn/Extensions/GroupExtension.cs | 15 ++- UnitTests/Extensions/GroupExtensionTest.cs | 102 ++++++++++++--------- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/QuickFIXn/Extensions/GroupExtension.cs b/QuickFIXn/Extensions/GroupExtension.cs index 0199a1c25..a3acbfacc 100644 --- a/QuickFIXn/Extensions/GroupExtension.cs +++ b/QuickFIXn/Extensions/GroupExtension.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; namespace QuickFix.Extensions { @@ -17,17 +15,16 @@ public static class GroupExtension /// public static IEnumerable ReadGroups(this FieldMap message, int noGroupTag) where TGroup : Group //, new() { - if (message != null && message.IsSetField(noGroupTag)) + if (message.IsSetField(noGroupTag) && message.GetGroupTags().Contains(noGroupTag)) { int grpCount = message.GetInt(noGroupTag); + for (int grpIndex = 1; grpIndex <= grpCount; grpIndex += 1) { - //var group = new TGroup(); - //message.GetGroup(grpIndex, group); - var group = message.GetGroup(grpIndex, noGroupTag) as TGroup; - //if (group == null) - // throw new InvalidCastException($"Can't cast {message.GetGroup(grpIndex, noGroupTag).GetType()} to {typeof(TGroup)}"); - yield return group; + Group group = message.GetGroup(grpIndex, noGroupTag); + if(group is not TGroup tgroup) + throw new InvalidCastException($"Can't cast {group.GetType()} to {typeof(TGroup)}"); + yield return tgroup; } } } diff --git a/UnitTests/Extensions/GroupExtensionTest.cs b/UnitTests/Extensions/GroupExtensionTest.cs index a5a96d066..99944c943 100644 --- a/UnitTests/Extensions/GroupExtensionTest.cs +++ b/UnitTests/Extensions/GroupExtensionTest.cs @@ -1,61 +1,77 @@ -using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using QuickFix; +using QuickFix.FIX42; using QuickFix.Fields; using QuickFix.Extensions; -using System; using UnitTests.TestHelpers; -using System.Linq; +using Message = QuickFix.Message; namespace UnitTests.Extensions { [TestFixture] public class GroupExtensionTest { - private IMessageFactory _defaultMsgFactory = new DefaultMessageFactory(); + private readonly IMessageFactory _defaultMsgFactory = new DefaultMessageFactory(); - [Test] - public void EnumerableGroupsTest() - { - var nos = new QuickFix.FIX42.NewOrderSingle(); - for (int i = 0; i < 100; i++) - { - var noTradingSessions = new QuickFix.FIX42.NewOrderSingle.NoTradingSessionsGroup(); - noTradingSessions.SetField(new StringField(336, $"OHHAI_{i}")); - nos.AddGroup(noTradingSessions); + private static News CreateNewsForTest(int numberOfLines) { + News msg = new(); + msg.Headline = new Headline("Blah headline"); + for (int i = 0; i < numberOfLines; i++) { + News.LinesOfTextGroup grp = new(); + grp.SetField(new Text($"OHHAI_{i}")); + msg.AddGroup(grp); } - int iterator = 0; - foreach (var item in nos.ReadGroups(Tags.NoTradingSessions)) - { - Assert.That(item.GetString(336), Is.EqualTo($"OHHAI_{iterator++}")); - } - Assert.That(iterator == 100); + return msg; + } - // Wrong tag - Assert.That(nos.ReadGroups(1), Is.Empty); + [Test] + public void ReadGroups_NormalScenario() { + News msg = CreateNewsForTest(10); + int iterCount = 0; + foreach (News.LinesOfTextGroup item in msg.ReadGroups(Tags.NoLinesOfText)) + Assert.That(item.Text.Value, Is.EqualTo($"OHHAI_{iterCount++}")); + Assert.That(iterCount == 10); + } - // Wrong group - foreach (var item in nos.ReadGroups(Tags.NoTradingSessions)) - { - Assert.That(item, Is.Null); - } + [Test] + public void ReadGroups_ZeroCount() { + News msg = new(); + Assert.That(msg.ReadGroups(Tags.NoLinesOfText).ToList(), Is.Empty); + } + + [Test] + public void ReadGroups_WrongTag() { + // Tag doesn't match the templated group + // case 1: incorrect tag is not present in message + News msg = CreateNewsForTest(3); + Assert.That(msg.IsSetField(Tags.Account), Is.False); + Assert.That(msg.ReadGroups(Tags.Account).ToList(), Is.Empty); + + // case 2: incorrect tag present but value isn't an int (like a group counter would be) + Assert.That(msg.IsSetHeadline()); + Assert.That(msg.ReadGroups(Tags.Headline).ToList(), Is.Empty); + + // case 3: incorrect tag present and an int, but isn't group counter at all + msg.SetField(new RawDataLength(100)); + Assert.That(msg.ReadGroups(Tags.RawDataLength).ToList(), Is.Empty); + } + + [Test] + public void ReadGroups_WrongGroup() { + // The tag is good, but the template type is incorrect + // Case 1: The tag's group is empty, so the templated type doesn't matter. + News msg = CreateNewsForTest(0); + Assert.That(msg.ReadGroups(Tags.NoLinesOfText).ToList(), + Is.Empty); - //-- - String data = "8=FIX.4.4\x01" + "9=309\x01" + "35=8\x01" + "49=ASX\x01" + "56=CL1_FIX44\x01" + "34=4\x01" + "52=20060324-01:05:58\x01" + "" - + "17=X-B-WOW-1494E9A0:58BD3F9D-1109\x01" + "150=D\x01" + "39=0\x01" + "11=184271\x01" + "38=200\x01" + "198=1494E9A0:58BD3F9D\x01" + "" - + "526=4324\x01" + "37=B-WOW-1494E9A0:58BD3F9D\x01" + "55=WOW\x01" + "54=1\x01" + "151=200\x01" + "14=0\x01" + "40=2\x01" + "44=15\x01" + "59=1\x01" + "6=0\x01" + "" - + "453=3\x01" - + "448=AAA35791\x01" + "447=D\x01" + "452=3\x01" + "802=1\x01" + "523=OHAI123\x01" - + "448=8\x01" + "447=D\x01" + "452=4\x01" - + "448=FIX11\x01" + "447=D\x01" + "452=36\x01" + "" - + "60=20060320-03:34:29\x01" + "10=169\x01" + ""; - var msg = new QuickFix.FIX44.ExecutionReport(); - var dd = new QuickFix.DataDictionary.DataDictionary(); - dd.LoadFIXSpec("FIX44"); - msg.FromString(data, false, dd, dd, _defaultMsgFactory); - - var eRNoPartyIDsGroup = msg.ReadGroups(Tags.NoPartyIDs).ToList(); - Assert.That(eRNoPartyIDsGroup[0].PartyID?.Value, Is.EqualTo("AAA35791")); - Assert.That(eRNoPartyIDsGroup[2].PartyID?.Value, Is.EqualTo("FIX11")); + // Case 2: Group is not empty, so InvalidCastException will occur + msg = CreateNewsForTest(1); + var ex = Assert.Throws( + () => msg.ReadGroups(Tags.NoLinesOfText).ToList()); + Assert.That(ex!.Message, Is.EqualTo("Can't cast QuickFix.FIX42.News+LinesOfTextGroup to QuickFix.FIX44.ExecutionReport+NoLegsGroup")); } } } From 10c4c4400cb68f74a44f0733f65f5285e2f78377 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Fri, 20 Dec 2024 12:17:19 -0600 Subject: [PATCH 4/4] promote ReadGroups to a FieldMap method no reason for it to be an extension --- QuickFIXn/Extensions/GroupExtension.cs | 32 ------------------- QuickFIXn/Message/FieldMap.cs | 31 ++++++++++++++---- RELEASE_NOTES.md | 1 + ...ionTest.cs => FieldMap_ReadGroupsTests.cs} | 16 +++------- 4 files changed, 30 insertions(+), 50 deletions(-) delete mode 100644 QuickFIXn/Extensions/GroupExtension.cs rename UnitTests/{Extensions/GroupExtensionTest.cs => FieldMap_ReadGroupsTests.cs} (90%) diff --git a/QuickFIXn/Extensions/GroupExtension.cs b/QuickFIXn/Extensions/GroupExtension.cs deleted file mode 100644 index a3acbfacc..000000000 --- a/QuickFIXn/Extensions/GroupExtension.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace QuickFix.Extensions -{ - public static class GroupExtension - { - /// - /// Gets an instance of enumerable groups - /// - /// A repeating field group within a message - /// Field container used by messages, groups, and composites - /// The FIX group tag - /// retrieved enumerable groups - /// - public static IEnumerable ReadGroups(this FieldMap message, int noGroupTag) where TGroup : Group //, new() - { - if (message.IsSetField(noGroupTag) && message.GetGroupTags().Contains(noGroupTag)) - { - int grpCount = message.GetInt(noGroupTag); - - for (int grpIndex = 1; grpIndex <= grpCount; grpIndex += 1) - { - Group group = message.GetGroup(grpIndex, noGroupTag); - if(group is not TGroup tgroup) - throw new InvalidCastException($"Can't cast {group.GetType()} to {typeof(TGroup)}"); - yield return tgroup; - } - } - } - } -} diff --git a/QuickFIXn/Message/FieldMap.cs b/QuickFIXn/Message/FieldMap.cs index c544affd8..904eb7850 100644 --- a/QuickFIXn/Message/FieldMap.cs +++ b/QuickFIXn/Message/FieldMap.cs @@ -657,22 +657,39 @@ public List GetGroupTags() return new List(_groups.Keys); } - #region IEnumerable> Members + /// + /// Read groups via IEnumerable. + /// + /// The group class type retrieved by groupCountTag + /// The FIX group tag + /// retrieved enumerable groups + /// If groupCountTag retreives a group that can't be cast to TGroup + public IEnumerable ReadGroups(int groupCountTag) where TGroup : Group + { + if (IsSetField(groupCountTag) && GetGroupTags().Contains(groupCountTag)) + { + int grpCount = GetInt(groupCountTag); + + for (int grpIndex = 1; grpIndex <= grpCount; grpIndex += 1) + { + Group group = GetGroup(grpIndex, groupCountTag); + if(group is not TGroup tgroup) + throw new InvalidCastException($"Can't cast {group.GetType()} to {typeof(TGroup)}"); + yield return tgroup; + } + } + } + // IEnumerable> Member public IEnumerator> GetEnumerator() { return _fields.GetEnumerator(); } - #endregion - - #region IEnumerable Members - + // IEnumerable Member System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _fields.GetEnumerator(); } - - #endregion } } diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 025268447..7a188562c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -48,6 +48,7 @@ What's New * #907 - A fix to make body-less messages work, specifically 35=n aka XMLnonFIX (gbirchmeier) * #910 - faster Message.GetMsgType that doesn't use Regex (jkulubya) * #516 - remove ability to toggle Session-enable via HttpServer because it never really worked (gbirchmeier) +* #913/#741 - new FieldMap.ReadGroups for iterating on groups (NoviProg/gbirchmeier) ### v1.12.0 diff --git a/UnitTests/Extensions/GroupExtensionTest.cs b/UnitTests/FieldMap_ReadGroupsTests.cs similarity index 90% rename from UnitTests/Extensions/GroupExtensionTest.cs rename to UnitTests/FieldMap_ReadGroupsTests.cs index 99944c943..a3c8a8ef6 100644 --- a/UnitTests/Extensions/GroupExtensionTest.cs +++ b/UnitTests/FieldMap_ReadGroupsTests.cs @@ -1,21 +1,15 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; using NUnit.Framework; -using QuickFix; using QuickFix.FIX42; using QuickFix.Fields; -using QuickFix.Extensions; -using UnitTests.TestHelpers; -using Message = QuickFix.Message; -namespace UnitTests.Extensions -{ - [TestFixture] +namespace UnitTests; + +[TestFixture] +public class FieldMap_ReadGroupsTests { public class GroupExtensionTest { - private readonly IMessageFactory _defaultMsgFactory = new DefaultMessageFactory(); - private static News CreateNewsForTest(int numberOfLines) { News msg = new(); msg.Headline = new Headline("Blah headline");