Skip to content

Commit 7201946

Browse files
authored
Merge pull request #475 from MarketFactory/issue472
Issue472 Handle presence="optional"
2 parents f2dfacd + 46b58e3 commit 7201946

File tree

6 files changed

+160
-21
lines changed

6 files changed

+160
-21
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ task(generateGolangCodecsWithXSD, type: JavaExec) {
523523
args = ['sbe-tool/src/test/resources/group-with-data-schema.xml',
524524
'sbe-tool/src/test/resources/FixBinary.xml',
525525
'sbe-tool/src/test/resources/issue435.xml',
526+
'sbe-tool/src/test/resources/issue472.xml',
526527
'sbe-tool/src/test/resources/since-deprecated-test-schema.xml',
527528
'sbe-tool/src/test/resources/example-bigendian-test-schema.xml',
528529
'gocode/resources/example-composite.xml',

gocode/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ test: $(DEP)
6868
go install \
6969
;done))
7070
(export GOPATH=$(GOPATH) && \
71-
(for t in baseline-bignendian mktdata group_with_data group_with_data_extension composite_elements composite since-deprecated simple issue435; do \
71+
(for t in baseline-bigendian mktdata group_with_data group_with_data_extension composite_elements composite since-deprecated simple issue435 issue472; do \
7272
cd $(GOPATH)/src/$$t && \
7373
go build && \
7474
go fmt && \

gocode/src/issue472/Issue472_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package issue472
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
// Optional values should allow NullValue in RangeCheck
9+
func TestOptionalRangeCheck(t *testing.T) {
10+
var in Issue472
11+
Issue472Init(&in)
12+
if err := in.RangeCheck(in.SbeSchemaVersion(), in.SbeSchemaVersion()); err != nil {
13+
t.Log("Issue472 RangeCheck() did not accept a NullValue for an optional field", err)
14+
t.Fail()
15+
}
16+
}
17+
18+
// Optional values should be set to Null Value by the Init() method
19+
func TestOptionalInit(t *testing.T) {
20+
var in Issue472
21+
Issue472Init(&in)
22+
if in.Optional != in.OptionalNullValue() {
23+
t.Log("Issue472Init() failed to initialize optional field to Null Value")
24+
t.Fail()
25+
}
26+
}
27+
28+
func TestEncodeDecode(t *testing.T) {
29+
30+
m := NewSbeGoMarshaller()
31+
var in Issue472
32+
Issue472Init(&in)
33+
var buf = new(bytes.Buffer)
34+
35+
// in contains a single optional field which is not initialized
36+
if err := in.Encode(m, buf, true); err != nil {
37+
t.Log("Encoding Error", err)
38+
t.Fail()
39+
}
40+
41+
// Create a new Issue472 and decode into it
42+
out := *new(Issue472)
43+
44+
if err := out.Decode(m, buf, in.SbeSchemaVersion(), in.SbeBlockLength(), true); err != nil {
45+
t.Log("Decoding Error", err)
46+
t.Fail()
47+
}
48+
t.Log("Issue472 decodes as: ", out)
49+
50+
// Sanity checks
51+
if buf.Len() != 0 {
52+
t.Logf("buffer not drained")
53+
t.Fail()
54+
}
55+
if in != out {
56+
t.Logf("in != out\n%s\n%s", in, out)
57+
t.Fail()
58+
}
59+
}

sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/golang/GolangGenerator.java

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -422,16 +422,23 @@ private void generateDecodePrimitive(
422422
private void generateRangeCheckPrimitive(
423423
final StringBuilder sb,
424424
final String varName,
425-
final Token token)
425+
final Token token,
426+
final Boolean isOptional)
426427
{
428+
// Note that constant encoding is a property of the type
429+
// and so works on signal/encoding tokens but optionality is
430+
// a property of the field and so is not set on the encoding token.
431+
// For this reason we can use the token to check for constancy
432+
// but pass in optionality as a parameter
433+
427434
// Constant values don't need checking
428435
if (token.isConstantEncoding())
429436
{
430437
return;
431438
}
432439

433440
// If this field is unknown then we have nothing to check
434-
// Otherwise do a The Min,MaxValue checks (possibly for arrays)
441+
// Otherwise do the Min,MaxValue checks (possibly for arrays)
435442
this.imports.add("fmt");
436443
if (token.arrayLength() > 1)
437444
{
@@ -449,9 +456,22 @@ private void generateRangeCheckPrimitive(
449456
}
450457
else
451458
{
459+
// Optional fields can be NullValue which may be outside the
460+
// range of Min->Max so we need a special case.
461+
// Structured this way for go fmt sanity on both cases.
462+
final String check;
463+
if (isOptional)
464+
{
465+
check = "\t\tif %1$s != %1$sNullValue() && (%1$s < %1$sMinValue() || %1$s > %1$sMaxValue()) {\n";
466+
}
467+
else
468+
{
469+
check = "\t\tif %1$s < %1$sMinValue() || %1$s > %1$sMaxValue() {\n";
470+
}
471+
452472
sb.append(String.format(
453473
"\tif %1$sInActingVersion(actingVersion) {\n" +
454-
"\t\tif %1$s < %1$sMinValue() || %1$s > %1$sMaxValue() {\n" +
474+
check +
455475
"\t\t\treturn fmt.Errorf(\"Range check failed on %1$s " +
456476
"(%%d < %%d > %%d)\", %1$sMinValue(), %1$s, %1$sMaxValue())\n" +
457477
"\t\t}\n" +
@@ -468,40 +488,56 @@ private void generateRangeCheckPrimitive(
468488
}
469489
}
470490

471-
private void generateInitPrimitive(
491+
private void generateOptionalInitPrimitive(
472492
final StringBuilder sb,
473493
final String varName,
474494
final Token token)
475495
{
496+
final Encoding encoding = token.encoding();
497+
498+
// Optional items get initialized to their NullValue
499+
sb.append(String.format(
500+
"\t%1$s = %2$s\n",
501+
varName,
502+
generateNullValueLiteral(encoding.primitiveType(), encoding)));
503+
}
504+
505+
private void generateConstantInitPrimitive(
506+
final StringBuilder sb,
507+
final String varName,
508+
final Token token)
509+
{
510+
final Encoding encoding = token.encoding();
511+
476512
// Decode of a constant is simply assignment
477513
if (token.isConstantEncoding())
478514
{
479515
// if primitiveType="char" this is a character array
480-
if (token.encoding().primitiveType() == CHAR)
516+
if (encoding.primitiveType() == CHAR)
481517
{
482-
if (token.encoding().constValue().size() > 1)
518+
if (encoding.constValue().size() > 1)
483519
{
484520
// constValue is a string
485521
sb.append(String.format(
486522
"\tcopy(%1$s[:], \"%2$s\")\n",
487523
varName,
488-
token.encoding().constValue()));
524+
encoding.constValue()));
489525
}
490526
else
491527
{
492528
// constValue is a char
493529
sb.append(String.format(
494530
"\t%1$s[0] = %2$s\n",
495531
varName,
496-
token.encoding().constValue()));
532+
encoding.constValue()));
497533
}
498534
}
499535
else
500536
{
501537
sb.append(String.format(
502538
"\t%1$s = %2$s\n",
503539
varName,
504-
generateLiteral(token.encoding().primitiveType(), token.encoding().constValue().toString())));
540+
generateLiteral(encoding.primitiveType(), encoding.constValue().toString())));
505541
}
506542
}
507543
}
@@ -652,17 +688,28 @@ private int generateEncodeDecode(
652688
encode.append(generateEncodeOffset(gap, ""));
653689
decode.append(generateDecodeOffset(gap, ""));
654690
currentOffset += signalToken.encodedLength() + gap;
691+
final String primitive = Character.toString(varName) + "." + propertyName;
655692

656-
// Encode of a constant is a nullop
657-
if (!signalToken.isConstantEncoding())
693+
// Encode of a constant is a nullop and we want to
694+
// initialize constant values.
695+
if (signalToken.isConstantEncoding())
696+
{
697+
generateConstantInitPrimitive(init, primitive, signalToken);
698+
}
699+
else
658700
{
659701
generateEncodePrimitive(encode, varName, formatPropertyName(signalToken.name()), signalToken);
702+
}
660703

704+
// Optional tokens also get initialized
705+
if (signalToken.isOptionalEncoding())
706+
{
707+
generateOptionalInitPrimitive(init, primitive, signalToken);
661708
}
662-
final String primitive = Character.toString(varName) + "." + propertyName;
709+
663710
generateDecodePrimitive(decode, primitive, signalToken);
664-
generateRangeCheckPrimitive(rangeCheck, primitive, signalToken);
665-
generateInitPrimitive(init, primitive, signalToken);
711+
generateRangeCheckPrimitive(rangeCheck, primitive, signalToken, signalToken.isOptionalEncoding());
712+
666713
break;
667714

668715
case BEGIN_GROUP:
@@ -1004,16 +1051,29 @@ private int generateFieldEncodeDecode(
10041051
gap = encodingToken.offset() - currentOffset;
10051052
encode.append(generateEncodeOffset(gap, ""));
10061053
decode.append(generateDecodeOffset(gap, ""));
1054+
final String primitive = Character.toString(varName) + "." + propertyName;
10071055

1008-
// Encode of a constant is a nullop
1009-
if (!encodingToken.isConstantEncoding())
1056+
// Encode of a constant is a nullop and we want to
1057+
// initialize constant values.
1058+
// (note: constancy is determined by the type's token)
1059+
if (encodingToken.isConstantEncoding())
1060+
{
1061+
generateConstantInitPrimitive(init, primitive, encodingToken);
1062+
}
1063+
else
10101064
{
10111065
generateEncodePrimitive(encode, varName, formatPropertyName(signalToken.name()), encodingToken);
10121066
}
1013-
final String primitive = Character.toString(varName) + "." + propertyName;
1067+
1068+
// Optional tokens get initialized to NullValue
1069+
// (note: optionality is determined by the field's token)
1070+
if (signalToken.isOptionalEncoding())
1071+
{
1072+
generateOptionalInitPrimitive(init, primitive, encodingToken);
1073+
}
1074+
10141075
generateDecodePrimitive(decode, primitive, encodingToken);
1015-
generateRangeCheckPrimitive(rc, primitive, encodingToken);
1016-
generateInitPrimitive(init, primitive, encodingToken);
1076+
generateRangeCheckPrimitive(rc, primitive, encodingToken, signalToken.isOptionalEncoding());
10171077
break;
10181078
}
10191079

sbe-tool/src/main/resources/golang/templates/SbeMarshallingBigEndian.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ func (m *SbeGoMessageHeader) Decode(_m *SbeGoMarshaller, _r io.Reader) error {
8484
return nil
8585
}
8686

87-
8887
func (m *SbeGoMarshaller) WriteUint8(w io.Writer, v uint8) error {
8988
m.b1[0] = byte(v)
9089
_, err := w.Write(m.b1)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
3+
package="issue472"
4+
id="472"
5+
version="0"
6+
semanticVersion="1.0"
7+
description="issue 472 test case"
8+
byteOrder="bigEndian">
9+
<types>
10+
<composite name="messageHeader" description="Message identifiers and length of message root">
11+
<type name="blockLength" primitiveType="uint16"/>
12+
<type name="templateId" primitiveType="uint16"/>
13+
<type name="schemaId" primitiveType="uint16"/>
14+
<type name="version" primitiveType="uint16"/>
15+
</composite>
16+
</types>
17+
<sbe:message name="issue472" id="1" description="issue 472 test">
18+
<field name="optional" type="uint64" id="2" presence="optional"/>
19+
</sbe:message>
20+
</sbe:messageSchema>

0 commit comments

Comments
 (0)