Skip to content

Commit

Permalink
Make Message.ToString() not change object state
Browse files Browse the repository at this point in the history
issue #883

New function ConstructString now does the
BodyLength/CheckSum update that ToString used to do
  • Loading branch information
gbirchmeier committed Sep 16, 2024
1 parent 0d4b1b1 commit d635d24
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 80 deletions.
2 changes: 1 addition & 1 deletion Examples/JsonToFix/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ static void JsonMsgToFix(string json, QuickFix.DataDictionary.DataDictionary ses
{
var msg = new Message();
msg.FromJson(json, true, sessionDataDictionary, appDataDictionary, msgFactory);
Console.WriteLine(msg.ToString());
Console.WriteLine(msg.ConstructString());
}

static void JsonToFix(string fname, QuickFix.DataDictionary.DataDictionary sessionDataDictionary, QuickFix.DataDictionary.DataDictionary appDataDictionary)
Expand Down
4 changes: 2 additions & 2 deletions Examples/TradeClient/TradeClientApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void ToAdmin(Message message, SessionID sessionID) { }

public void FromApp(Message message, SessionID sessionID)
{
Console.WriteLine("IN: " + message.ToString());
Console.WriteLine("IN: " + message.ConstructString());
try
{
Crack(message, sessionID);
Expand Down Expand Up @@ -57,7 +57,7 @@ public void ToApp(Message message, SessionID sessionID)
{ }

Console.WriteLine();
Console.WriteLine("OUT: " + message.ToString());
Console.WriteLine("OUT: " + message.ConstructString());
}
#endregion

Expand Down
10 changes: 10 additions & 0 deletions QuickFIXn/Message/FieldMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -579,11 +579,21 @@ public int CalculateLength()
return total;
}

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <returns></returns>
public virtual string CalculateString()
{
return CalculateString(new StringBuilder(), FieldOrder);
}

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <param name="sb"></param>
/// <param name="preFields"></param>
/// <returns></returns>
public virtual string CalculateString(StringBuilder sb, int[] preFields)
{
HashSet<int> groupCounterTags = new HashSet<int>(_groups.Keys);
Expand Down
4 changes: 4 additions & 0 deletions QuickFIXn/Message/Group.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ public virtual Group Clone()
/// </summary>
public int Delim { get; }

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <returns></returns>
public override string CalculateString() {
return base.CalculateString(new StringBuilder(), FieldOrder ?? new[] { Delim });
}
Expand Down
10 changes: 10 additions & 0 deletions QuickFIXn/Message/Header.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ public Header(Header src)
: base(src) {
}

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <returns></returns>
public override string CalculateString() {
return base.CalculateString(new StringBuilder(), HEADER_FIELD_ORDER);
}

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <param name="sb"></param>
/// <param name="preFields"></param>
/// <returns></returns>
public override string CalculateString(StringBuilder sb, int[] preFields) {
return base.CalculateString(sb, HEADER_FIELD_ORDER);
}
Expand Down
21 changes: 18 additions & 3 deletions QuickFIXn/Message/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -793,10 +793,15 @@ public SessionID GetSessionID(Message m)
m.Header.GetString(Tags.TargetCompID));
}

private Object lock_ToString = new Object();
public override string ToString()
private Object lock_ConstructString = new Object();
/// <summary>
/// Update BodyLength in Header, update CheckSum in Trailer, and return a FIX string.
/// (This function changes the object state!)
/// </summary>
/// <returns></returns>
public string ConstructString()
{
lock (lock_ToString)
lock (lock_ConstructString)
{
Header.SetField(new BodyLength(BodyLength()), true);
Trailer.SetField(new CheckSum(Fields.Converters.CheckSumConverter.Convert(CheckSum())), true);
Expand All @@ -805,6 +810,16 @@ public override string ToString()
}
}

/// <summary>
/// Create a FIX-style string from the message.
/// This does NOT add/update BodyLength or CheckSum to the message header, or otherwise change the object state.
/// (Use <see cref="ConstructString"/> to compute and add/update BodyLength &amp; CheckSum.)
/// </summary>
/// <returns></returns>
public override string ToString() {
return Header.CalculateString() + CalculateString() + Trailer.CalculateString();
}

protected int BodyLength()
{
return Header.CalculateLength() + CalculateLength() + Trailer.CalculateLength();
Expand Down
10 changes: 10 additions & 0 deletions QuickFIXn/Message/Trailer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ public Trailer(Trailer src)
: base(src) {
}

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <returns></returns>
public override string CalculateString() {
return base.CalculateString(new StringBuilder(), TRAILER_FIELD_ORDER);
}

/// <summary>
/// Creates a FIX (ish) string representation of this FieldMap (does not change the object state)
/// </summary>
/// <param name="sb"></param>
/// <param name="preFields"></param>
/// <returns></returns>
public override string CalculateString(StringBuilder sb, int[] preFields) {
return base.CalculateString(sb, TRAILER_FIELD_ORDER);
}
Expand Down
6 changes: 3 additions & 3 deletions QuickFIXn/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ protected void NextResendRequest(Message resendReq)
{
GenerateSequenceReset(resendReq, begin, msgSeqNum);
}
Send(msg.ToString());
Send(msg.ConstructString());
begin = 0;
}
current = msgSeqNum + 1;
Expand Down Expand Up @@ -1517,7 +1517,7 @@ protected bool NextQueued(SeqNumType num)
}
else
{
NextMessage(msg.ToString());
NextMessage(msg.ConstructString());
}
return true;
}
Expand Down Expand Up @@ -1567,7 +1567,7 @@ protected bool SendRaw(Message message, SeqNumType seqNum)
}
}

string messageString = message.ToString();
string messageString = message.ConstructString();
if (0 == seqNum)
Persist(message, messageString);
return Send(messageString);
Expand Down
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ What's New
* #878 - corrections to tag 45 "Side" in various DDs (gbirchmeier) - most people won't notice, easy fix if they do
* fix typo in FIX50 and FIX50SP1: `CROSS_SHORT_EXXMPT` fixed to `CROSS_SHORT_EXEMPT`
* 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.

**Non-breaking changes**
* #864 - when multiple threads race to init DefaultMessageFactory,
Expand Down
2 changes: 1 addition & 1 deletion UnitTests/DataDictionaryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ public void CheckGroupCountTest()
//verify that FromString didn't correct the counter
//HEY YOU, READ THIS NOW: if these fail, first check if MessageTests::FromString_DoNotCorrectCounter() passes
Assert.AreEqual("386=3", n.NoTradingSessions.toStringField());
StringAssert.Contains("386=3", n.ToString());
StringAssert.Contains("386=3", n.ConstructString());

Assert.Throws<QuickFix.RepeatingGroupCountMismatch>(delegate { dd.CheckGroupCount(n.NoTradingSessions, n, "D"); });
}
Expand Down
108 changes: 48 additions & 60 deletions UnitTests/MessageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using QuickFix;
using QuickFix.Fields;
using UnitTests.TestHelpers;
using Message = QuickFix.Message;

namespace UnitTests
{
Expand Down Expand Up @@ -185,7 +186,7 @@ public void FromString_DoNotCorrectCounter()

n.FromString(s, true, dd, dd, _defaultMsgFactory);
Assert.AreEqual("386=3", n.NoTradingSessions.toStringField());
StringAssert.Contains("386=3", n.ToString()); //should not be "corrected" to 2!
StringAssert.Contains("386=3", n.ConstructString()); //should not be "corrected" to 2!
}

[Test]
Expand All @@ -209,52 +210,7 @@ public void FromString_GroupDelimiterIssue()
}

[Test]
public void ToStringTest()
{
string str1 = "8=FIX.4.2\x01" + "9=55\x01" + "35=0\x01" + "34=3\x01" + "49=TW\x01" +
"52=20000426-12:05:06\x01" + "56=ISLD\x01" + "1=acct123\x01" + "10=123\x01";
Message msg = new Message();
try
{
msg.FromString(str1, true, null, null, _defaultMsgFactory);
}
catch (InvalidMessage e)
{
Assert.Fail("Unexpected exception (InvalidMessage): " + e.Message);
}
StringField f1 = new StringField(8);
StringField f2 = new StringField(9);
StringField f3 = new StringField(35);
StringField f4 = new StringField(34);
StringField f5 = new StringField(49);
StringField f6 = new StringField(52);
StringField f7 = new StringField(56);
StringField f8 = new StringField(10);
StringField f9 = new StringField(1);
msg.Header.GetField(f1);
msg.Header.GetField(f2);
msg.Header.GetField(f3);
msg.Header.GetField(f4);
msg.Header.GetField(f5);
msg.Header.GetField(f6);
msg.Header.GetField(f7);
msg.GetField(f9);
msg.Trailer.GetField(f8);
Assert.That(f1.Obj, Is.EqualTo("FIX.4.2"));
Assert.That(f2.Obj, Is.EqualTo("55"));
Assert.That(f3.Obj, Is.EqualTo("0"));
Assert.That(f4.Obj, Is.EqualTo("3"));
Assert.That(f5.Obj, Is.EqualTo("TW"));
Assert.That(f6.Obj, Is.EqualTo("20000426-12:05:06"));
Assert.That(f7.Obj, Is.EqualTo("ISLD"));
Assert.That(f8.Obj, Is.EqualTo("123"));
Assert.That(f9.Obj, Is.EqualTo("acct123"));
string raw = msg.ToString();
Assert.That(raw, Is.EqualTo(str1));
}

[Test]
public void ToStringFieldOrder()
public void ConstructStringFieldOrder()
{
Message msg = new Message();
msg.Header.SetField(new QuickFix.Fields.MsgType("A"));
Expand All @@ -263,7 +219,7 @@ public void ToStringFieldOrder()
msg.Header.SetField(new QuickFix.Fields.TargetCompID("TARGET"));
msg.Header.SetField(new QuickFix.Fields.MsgSeqNum(42));
string expect = "8=FIX.4.2\x01" + "9=31\x01" + "35=A\x01" + "34=42\x01" + "49=SENDER\x01" + "56=TARGET\x01" + "10=200\x01";
Assert.That(msg.ToString(), Is.EqualTo(expect));
Assert.That(msg.ConstructString(), Is.EqualTo(expect));
}

[Test]
Expand Down Expand Up @@ -525,7 +481,7 @@ public void RepeatingGroup()
group2.EncodedText = new QuickFix.Fields.EncodedText("bbb");
news.AddGroup(group2);

string raw = news.ToString();
string raw = news.ConstructString();

string nul = "\x01";
StringAssert.Contains(
Expand All @@ -547,7 +503,7 @@ public void RepeatingGroup_ReuseObject()
group.Text = new QuickFix.Fields.Text("line2");
news.AddGroup(group);

string raw = news.ToString();
string raw = news.ConstructString();

string nul = "\x01";
StringAssert.Contains(
Expand Down Expand Up @@ -676,7 +632,7 @@ public void RepeatingGroup_DelimiterFieldFirst()
symGroup.SecurityID = new SecurityID("secid2");
msg.AddGroup(symGroup);

string msgString = msg.ToString();
string msgString = msg.ConstructString();
string expected = String.Join(Message.SOH, new string[] { "146=2", "55=FOO1", "48=secid1", "55=FOO2", "48=secid2" });

StringAssert.Contains(expected, msgString);
Expand Down Expand Up @@ -710,7 +666,7 @@ public void RepeatingGroup_FieldOrder()
symGroup.SecurityIDSource = new SecurityIDSource("src2");
msg.AddGroup(symGroup);

string msgString = msg.ToString();
string msgString = msg.ConstructString();
string expected = String.Join(Message.SOH, new string[] { "146=2",
"55=FOO1", "65=sfx1", "48=secid1", "22=src1",
"55=FOO2", "65=sfx2", "48=secid2", "22=src2",
Expand All @@ -720,12 +676,12 @@ public void RepeatingGroup_FieldOrder()
}

[Test]
public void ToString_FIX50()
public void ConstructString_FIX50()
{
QuickFix.FIX50.News msg = new QuickFix.FIX50.News();
msg.Headline = new Headline("FOO");

StringAssert.StartsWith("8=FIXT.1.1" + Message.SOH, msg.ToString());
StringAssert.StartsWith("8=FIXT.1.1" + Message.SOH, msg.ConstructString());
}

[Test]
Expand Down Expand Up @@ -753,7 +709,7 @@ public void RepeatingGroup_SubgroupCounterTagAppearsOnlyOnce()

ci.AddGroup(noParty);

string msgString = ci.ToString();
string msgString = ci.ConstructString();
string expected = String.Join(Message.SOH, new string[] {
"909=CollateralInquiry", // top-level fields (non-header)
"453=1", //NoPartyIDs
Expand Down Expand Up @@ -896,7 +852,7 @@ public void SendDateOnlyTimeOnlyConvertProblem()
grp.QuoteEntryID = new QuoteEntryID("15467902");
msg.AddGroup(grp);

string msgString = msg.ToString();
string msgString = msg.ConstructString();

string expected = String.Join(Message.SOH, new string[] { "35=W", "22=4", "48=BE0932900518", "55=[N/A]", "262=1b145288-9c9a-4911-a084-7341c69d3e6b", "762=EURO_EUR", "268=2",
"269=0", "270=97.625", "15=EUR", "271=1246000", "272=20121024", "273=07:30:47", "276=I", "282=BEARGB21XXX", "299=15478575",
Expand Down Expand Up @@ -971,13 +927,13 @@ public void issue95()
msg.FromString(msgStr, false, dd, dd, _defaultMsgFactory);

// make sure no fields were dropped in parsing
Assert.AreEqual(msgStr.Length, msg.ToString().Length);
Assert.AreEqual(msgStr.Length, msg.ConstructString().Length);

// make sure repeating groups are not rearranged
// 1 level deep
StringAssert.Contains(String.Join(Message.SOH, new string[] { "55=ABC", "65=CD", "48=securityid", "22=1" }), msg.ToString());
StringAssert.Contains(String.Join(Message.SOH, new string[] { "55=ABC", "65=CD", "48=securityid", "22=1" }), msg.ConstructString());
// 2 levels deep
StringAssert.Contains(String.Join(Message.SOH, new string[] { "311=underlyingsymbol", "312=WI", "309=underlyingsecurityid", "305=1" }), msg.ToString());
StringAssert.Contains(String.Join(Message.SOH, new string[] { "311=underlyingsymbol", "312=WI", "309=underlyingsecurityid", "305=1" }), msg.ConstructString());
}

[Test]
Expand Down Expand Up @@ -1009,7 +965,7 @@ public void ChecksumIsLastFieldOfTrailer()
msg.Trailer.SetField(new Signature("woot"));
msg.Trailer.SetField(new SignatureLength(4));

string foo = msg.ToString().Replace(Message.SOH, '|');
string foo = msg.ConstructString().Replace(Message.SOH, '|');
StringAssert.EndsWith("|10=099|", foo);
}

Expand Down Expand Up @@ -1101,5 +1057,37 @@ public void JsonNestedRepeatingGroupParseGroupTest()
Assert.That(noPartySubGrp.GetString(Tags.PartySubIDType), Is.EqualTo("4"));
Assert.That(noPartySubGrp.GetString(31338), Is.EqualTo("custom group field"));
}

private QuickFix.FIX44.News CreateStringResultInput() {
QuickFix.FIX44.News msg = new();
msg.Header.SetField(new BeginString(QuickFix.FixValues.BeginString.FIX44));
msg.Header.SetField(new MsgType("B"));
msg.SetField(new Headline("myHeadline"));

QuickFix.FIX44.News.LinesOfTextGroup grp1 = new() { Text = new Text("line1") };
QuickFix.FIX44.News.LinesOfTextGroup grp2 = new() { Text = new Text("line2") };
msg.AddGroup(grp1);
msg.AddGroup(grp2);
return msg;
}

[Test]
public void ToStringTest() {
QuickFix.FIX44.News msg = CreateStringResultInput();
// ToString() does not add BodyLength or CheckSum -- it does not change object state
string expected = "8=FIX.4.4|35=B|148=myHeadline|33=2|58=line1|58=line2|";
Assert.AreEqual(expected, msg.ToString().Replace(Message.SOH, '|'));
}

[Test]
public void ConstructStringTest() {
QuickFix.FIX44.News msg = CreateStringResultInput();
// ConstructString() adds BodyLength and CheckSum
string expected = "8=FIX.4.4|9=43|35=B|148=myHeadline|33=2|58=line1|58=line2|10=161|";
Assert.AreEqual(expected, msg.ConstructString().Replace(Message.SOH, '|'));

// the object state is changed
Assert.AreEqual(expected, msg.ToString().Replace(Message.SOH, '|'));
}
}
}
Loading

0 comments on commit d635d24

Please sign in to comment.