From 878bf42a301f160264abc8dbc3fb9048b6107c2b Mon Sep 17 00:00:00 2001 From: Chris Busbey <cbusbey@connamara.com> Date: Sat, 24 Oct 2015 19:57:17 -0700 Subject: [PATCH] marshal, unmarshal --- marshal.go | 130 +++++++++++++++++++++ marshal_test.go | 282 +++++++++++++++++++++++++++++++++++++++++++++ repeating_group.go | 2 +- unmarshal.go | 155 +++++++++++++++++++++++++ unmarshal_test.go | 228 ++++++++++++++++++++++++++++++++++++ 5 files changed, 796 insertions(+), 1 deletion(-) create mode 100644 marshal.go create mode 100644 marshal_test.go create mode 100644 unmarshal.go create mode 100644 unmarshal_test.go diff --git a/marshal.go b/marshal.go new file mode 100644 index 00000000..4c62a4b4 --- /dev/null +++ b/marshal.go @@ -0,0 +1,130 @@ +package quickfix + +import ( + "reflect" + "strconv" + "time" +) + +type encoder struct { + FieldMap +} + +func (e encoder) encodeField(f reflect.StructField, t reflect.Type, v reflect.Value) { + if f.Tag.Get("fix") != "" { + fixTag, err := strconv.Atoi(f.Tag.Get("fix")) + if err != nil { + panic(err) + } + e.encodeValue(Tag(fixTag), v) + } + + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return + } + e.encodeField(f, v.Elem().Type(), v.Elem()) + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + e.encodeField(t.Field(i), t.Field(i).Type, v.Field(i)) + } + } +} + +func (e encoder) encodeValue(fixTag Tag, v reflect.Value) { + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return + } + e.encodeValue(fixTag, v.Elem()) + case reflect.Struct: + switch t := v.Interface().(type) { + case time.Time: + e.FieldMap.SetField(fixTag, FIXUTCTimestamp{Value: t}) + } + case reflect.String: + e.FieldMap.SetField(fixTag, FIXString(v.String())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.FieldMap.SetField(fixTag, FIXInt(v.Int())) + case reflect.Bool: + e.FieldMap.SetField(fixTag, FIXBoolean(v.Bool())) + case reflect.Float32, reflect.Float64: + e.FieldMap.SetField(fixTag, FIXFloat(v.Float())) + case reflect.Slice: + elem := v.Type().Elem() + if elem.Kind() != reflect.Struct { + panic("repeating group must be a slice of type struct") + } + + template := make(GroupTemplate, elem.NumField()) + for i := 0; i < elem.NumField(); i++ { + sf := elem.Field(i) + fixTag, err := strconv.Atoi(sf.Tag.Get("fix")) + + if err != nil { + panic(err) + } + + template[i] = GroupElement(Tag(fixTag)) + } + + repeatingGroup := RepeatingGroup{Tag: fixTag, GroupTemplate: template} + + for i := 0; i < v.Len(); i++ { + group := repeatingGroup.Add() + groupEncoder := encoder{FieldMap: group.FieldMap} + + sliceV := v.Index(i) + for i := 0; i < sliceV.NumField(); i++ { + sf := sliceV.Type().Field(i) + groupEncoder.encodeField(sf, sf.Type, sliceV.Field(i)) + } + } + + e.FieldMap.SetGroup(repeatingGroup) + } +} + +// Marshal returns the Message encoding of v +func Marshal(v interface{}) Message { + m := NewMessage() + + reflectValue := reflect.ValueOf(v) + if !reflectValue.IsValid() { + panic("Message value is invalid") + } + + var msgType string + + reflectType := reflectValue.Type() + e := encoder{} + for i := 0; i < reflectType.NumField(); i++ { + sf := reflectType.Field(i) + + switch sf.Name { + case "FIXMsgType": + msgType = sf.Tag.Get("fix") + if msgType == "" { + panic("FIXMsgType has no Tag Value") + } + continue + case "Header": + e.FieldMap = m.Header + case "Trailer": + e.FieldMap = m.Trailer + default: + e.FieldMap = m.Body + } + + e.encodeField(sf, sf.Type, reflectValue.Field(i)) + } + + if msgType != "" { + m.Header.SetField(tagMsgType, FIXString(msgType)) + } + + return m +} diff --git a/marshal_test.go b/marshal_test.go new file mode 100644 index 00000000..94aa9004 --- /dev/null +++ b/marshal_test.go @@ -0,0 +1,282 @@ +package quickfix_test + +import ( + "bytes" + "github.com/quickfixgo/quickfix" + "github.com/quickfixgo/quickfix/field" + "testing" + "time" +) + +func TestMarshal_FIXMsgType(t *testing.T) { + type Message struct { + FIXMsgType string `fix:"0"` + } + var m Message + fixMsg := quickfix.Marshal(m) + fixMsg.Build() + + var msgType field.MsgTypeField + fixMsg.Header.Get(&msgType) + if msgType.FIXString != "0" { + t.Errorf("Expected %v got %v", "1", msgType) + } +} + +func TestMarshal_Literals(t *testing.T) { + type Message struct { + FIXMsgType string `fix:"0"` + StringField string `fix:"114"` + IntField int `fix:"115"` + UnsetIntField int `fix:"116"` + BoolField bool `fix:"117"` + FloatField float64 `fix:"118"` + UTCTimestampField time.Time `fix:"119"` + } + + tVal, _ := time.Parse("2006-Jan-02", "2014-Jun-16") + + m := Message{ + StringField: "world", + IntField: 5, + BoolField: true, + FloatField: 100.02, + UTCTimestampField: tVal, + } + fixMsg := quickfix.Marshal(m) + + var tests = []struct { + tag quickfix.Tag + fieldValue quickfix.FieldValue + expected []byte + }{ + {quickfix.Tag(114), new(quickfix.FIXString), []byte("world")}, + {quickfix.Tag(115), new(quickfix.FIXInt), []byte("5")}, + {quickfix.Tag(116), new(quickfix.FIXInt), []byte("0")}, + {quickfix.Tag(117), new(quickfix.FIXBoolean), []byte("Y")}, + {quickfix.Tag(118), new(quickfix.FIXFloat), []byte("100.02")}, + {quickfix.Tag(119), new(quickfix.FIXUTCTimestamp), []byte("20140616-00:00:00.000")}, + } + + for _, test := range tests { + if err := fixMsg.Body.GetField(test.tag, test.fieldValue); err != nil { + t.Error("Unexpected error", err) + } + + if !bytes.Equal(test.expected, test.fieldValue.Write()) { + t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write()) + } + } +} + +func TestMarshal_LiteralsOptional(t *testing.T) { + type Message struct { + FIXMsgType string `fix:"0"` + StringPtrField *string `fix:"112"` + UnsetStringPtrField *string `fix:"113"` + IntPtrField *int `fix:"115"` + BoolPtrField *bool `fix:"117"` + FloatPtrField *float64 `fix:"119"` + } + + strVal := "hello" + intVal := 42 + boolVal := false + floatVal := 456.123 + m := Message{ + StringPtrField: &strVal, + IntPtrField: &intVal, + BoolPtrField: &boolVal, + FloatPtrField: &floatVal, + } + fixMsg := quickfix.Marshal(m) + + var tests = []struct { + tag quickfix.Tag + fieldValue quickfix.FieldValue + fieldSet bool + expected []byte + }{ + {quickfix.Tag(112), new(quickfix.FIXString), true, []byte("hello")}, + {quickfix.Tag(113), new(quickfix.FIXString), false, []byte{}}, + {quickfix.Tag(115), new(quickfix.FIXInt), true, []byte("42")}, + {quickfix.Tag(117), new(quickfix.FIXBoolean), true, []byte("N")}, + {quickfix.Tag(119), new(quickfix.FIXFloat), true, []byte("456.123")}, + } + + for _, test := range tests { + if !test.fieldSet { + if fixMsg.Body.Has(test.tag) { + t.Error("Unexpected field for tag", test.tag) + } + continue + } + + if err := fixMsg.Body.GetField(test.tag, test.fieldValue); err != nil { + t.Error("Unexpected error", err) + } + + if !bytes.Equal(test.expected, test.fieldValue.Write()) { + t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write()) + } + } +} + +func TestMarshal_Components(t *testing.T) { + type Component1 struct { + StringField string `fix:"8"` + } + type Component2 struct { + IntField int `fix:"1"` + } + + type Message struct { + FIXMsgType string `fix:"0"` + Component1 + StringField string `fix:"112"` + *Component2 + } + + m := Message{ + Component1: Component1{StringField: "hello"}, + StringField: "world", + } + fixMsg := quickfix.Marshal(m) + + var tests = []struct { + tag quickfix.Tag + fieldValue quickfix.FieldValue + fieldSet bool + expected []byte + }{ + {quickfix.Tag(8), new(quickfix.FIXString), true, []byte("hello")}, + {quickfix.Tag(1), new(quickfix.FIXInt), false, []byte{}}, + {quickfix.Tag(112), new(quickfix.FIXString), true, []byte("world")}, + } + + for _, test := range tests { + if !test.fieldSet { + if fixMsg.Body.Has(test.tag) { + t.Error("Unexpected field for tag", test.tag) + } + continue + } + + if err := fixMsg.Body.GetField(test.tag, test.fieldValue); err != nil { + t.Error("Unexpected error", err) + continue + } + + if !bytes.Equal(test.expected, test.fieldValue.Write()) { + t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write()) + } + } +} + +func TestMarshal_RepeatingGroups(t *testing.T) { + type Group1 struct { + StringField1 string `fix:"8"` + StringField2 *string `fix:"9"` + } + + type Group2 struct { + IntField1 int `fix:"1"` + IntField2 int `fix:"2"` + } + + type Message struct { + FIXMsgType string `fix:"0"` + GroupField1 []Group1 `fix:"100"` + StringField string `fix:"112"` + GroupField2 []Group2 `fix:"101"` + } + + s := "world" + m := Message{ + GroupField1: []Group1{Group1{StringField1: "hello", StringField2: &s}, Group1{StringField1: "goodbye"}, Group1{StringField1: "OHHAI", StringField2: &s}}, + StringField: "world", + GroupField2: []Group2{Group2{IntField1: 1, IntField2: 42}}, + } + fixMsg := quickfix.Marshal(m) + + group1Template := quickfix.GroupTemplate{ + quickfix.GroupElement(quickfix.Tag(8)), + quickfix.GroupElement(quickfix.Tag(9)), + } + + group2Template := quickfix.GroupTemplate{ + quickfix.GroupElement(quickfix.Tag(1)), + quickfix.GroupElement(quickfix.Tag(2)), + } + + var tests = []struct { + groupTag quickfix.Tag + expectedGroupSize int + template quickfix.GroupTemplate + }{ + {quickfix.Tag(100), 3, group1Template}, + {quickfix.Tag(101), 1, group2Template}, + } + + for _, test := range tests { + if !fixMsg.Body.Has(test.groupTag) { + t.Errorf("Expected tag %v", test.groupTag) + continue + } + + groupField := quickfix.RepeatingGroup{Tag: test.groupTag, GroupTemplate: test.template} + if err := fixMsg.Body.GetGroup(&groupField); err != nil { + t.Error("Unexpected error", err) + continue + } + + if len(groupField.Groups) != test.expectedGroupSize { + t.Errorf("Expected group %v to have size %v, got %v", test.groupTag, test.expectedGroupSize, len(groupField.Groups)) + } + } +} + +func TestMarshal_HeaderTrailer(t *testing.T) { + type Header struct { + StringField string `fix:"49"` + } + type Trailer struct { + IntField int `fix:"89"` + } + + type Message struct { + FIXMsgType string `fix:"0"` + StringField string `fix:"112"` + Header + Trailer + } + + m := Message{ + Header: Header{StringField: "hello"}, + StringField: "world", + Trailer: Trailer{IntField: 3}, + } + fixMsg := quickfix.Marshal(m) + + var tests = []struct { + tag quickfix.Tag + fieldValue quickfix.FieldValue + fixMsgPart quickfix.FieldMap + expected []byte + }{ + {quickfix.Tag(35), new(quickfix.FIXString), fixMsg.Header, []byte("0")}, + {quickfix.Tag(49), new(quickfix.FIXString), fixMsg.Header, []byte("hello")}, + {quickfix.Tag(112), new(quickfix.FIXString), fixMsg.Body, []byte("world")}, + {quickfix.Tag(89), new(quickfix.FIXInt), fixMsg.Trailer, []byte("3")}, + } + + for _, test := range tests { + if err := test.fixMsgPart.GetField(test.tag, test.fieldValue); err != nil { + t.Error("Unexpected error", err) + } + + if !bytes.Equal(test.expected, test.fieldValue.Write()) { + t.Errorf("Expected %s got %s", test.expected, test.fieldValue.Write()) + } + } +} diff --git a/repeating_group.go b/repeating_group.go index 288223a4..dcdf0903 100644 --- a/repeating_group.go +++ b/repeating_group.go @@ -117,7 +117,6 @@ func (f RepeatingGroup) isDelimiter(t Tag) bool { } func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) { - var err error expectedGroupSize, err := atoi(tv[0].Value) if err != nil { return tv, err @@ -155,5 +154,6 @@ func (f *RepeatingGroup) Read(tv TagValues) (TagValues, error) { if len(f.Groups) != expectedGroupSize { return tv, fmt.Errorf("Only found %v instead of %v expected groups, is template wrong?", len(f.Groups), expectedGroupSize) } + return tv, err } diff --git a/unmarshal.go b/unmarshal.go new file mode 100644 index 00000000..af6a063b --- /dev/null +++ b/unmarshal.go @@ -0,0 +1,155 @@ +package quickfix + +import ( + "reflect" + "strconv" + "time" +) + +type decoder struct { + FieldMap +} + +func (d decoder) decodeValue(fixTag Tag, t reflect.Type, v reflect.Value) MessageRejectError { + switch v.Kind() { + case reflect.Ptr: + v.Set(reflect.New(t.Elem())) + return d.decodeValue(fixTag, t.Elem(), v.Elem()) + case reflect.Struct: + switch v.Interface().(type) { + case time.Time: + fieldValue := new(FIXUTCTimestamp) + if err := d.FieldMap.GetField(Tag(fixTag), fieldValue); err != nil { + return err + } + v.Set(reflect.ValueOf(fieldValue.Value)) + } + case reflect.String: + var fieldValue FIXString + if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil { + return err + } + v.SetString(string(fieldValue)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + var fieldValue FIXInt + if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil { + return err + } + v.SetInt(int64(fieldValue)) + case reflect.Bool: + var fieldValue FIXBoolean + if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil { + return err + } + v.SetBool(bool(fieldValue)) + case reflect.Float32, reflect.Float64: + var fieldValue FIXFloat + if err := d.FieldMap.GetField(Tag(fixTag), &fieldValue); err != nil { + return err + } + v.SetFloat(float64(fieldValue)) + case reflect.Slice: + elem := v.Type().Elem() + if elem.Kind() != reflect.Struct { + panic("repeating group must be a slice of type struct") + } + template := make(GroupTemplate, elem.NumField()) + for i := 0; i < elem.NumField(); i++ { + sf := elem.Field(i) + fixTag, err := strconv.Atoi(sf.Tag.Get("fix")) + + if err != nil { + panic(err) + } + + template[i] = GroupElement(Tag(fixTag)) + } + repeatingGroup := RepeatingGroup{Tag: fixTag, GroupTemplate: template} + if err := d.FieldMap.GetGroup(&repeatingGroup); err != nil { + return err + } + + v.Set(reflect.MakeSlice(v.Type(), len(repeatingGroup.Groups), len(repeatingGroup.Groups))) + + for i := 0; i < len(repeatingGroup.Groups); i++ { + groupDecoder := decoder{FieldMap: repeatingGroup.Groups[i].FieldMap} + + for j := 0; j < v.Type().Elem().NumField(); j++ { + sf := v.Type().Elem().Field(j) + sv := v.Index(i).Field(j) + + if err := groupDecoder.decodeField(sf, sf.Type, sv); err != nil { + return err + } + } + } + } + return nil +} + +func (d decoder) decodeField(sf reflect.StructField, t reflect.Type, v reflect.Value) MessageRejectError { + if sf.Tag.Get("fix") != "" { + fixTag, err := strconv.Atoi(sf.Tag.Get("fix")) + if err != nil { + panic(err) + } + + if !d.FieldMap.Has(Tag(fixTag)) { + return nil + } + + return d.decodeValue(Tag(fixTag), t, v) + } + + switch t.Kind() { + case reflect.Ptr: + v.Set(reflect.New(t.Elem())) + return d.decodeField(sf, t.Elem(), v.Elem()) + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + if err := d.decodeField(t.Field(i), t.Field(i).Type, v.Field(i)); err != nil { + return err + } + } + } + + return nil +} + +// Unmarshal populates v from a Message +func Unmarshal(m Message, v interface{}) MessageRejectError { + reflectValue := reflect.ValueOf(v) + + switch reflectValue.Kind() { + case reflect.Ptr: + reflectValue = reflectValue.Elem() + default: + panic("v not a pointer") + } + + reflectType := reflectValue.Type() + d := decoder{} + + for i := 0; i < reflectType.NumField(); i++ { + sf := reflectType.Field(i) + sv := reflectValue.Field(i) + + switch sf.Name { + case "FIXMsgType": + continue + case "Header": + d.FieldMap = m.Header + case "Trailer": + d.FieldMap = m.Trailer + default: + d.FieldMap = m.Body + } + + if err := d.decodeField(sf, sf.Type, sv); err != nil { + return err + } + } + + return nil +} diff --git a/unmarshal_test.go b/unmarshal_test.go new file mode 100644 index 00000000..1237e51b --- /dev/null +++ b/unmarshal_test.go @@ -0,0 +1,228 @@ +package quickfix_test + +import ( + "github.com/quickfixgo/quickfix" + "testing" + "time" +) + +func TestUnmarshal_Literals(t *testing.T) { + type Message struct { + FIXMsgType string `fix:"0"` + StringField string `fix:"114"` + IntField int `fix:"115"` + UnsetIntField int `fix:"116"` + BoolField bool `fix:"117"` + FloatField float64 `fix:"118"` + UTCTimestampField time.Time `fix:"119"` + } + + fixMsg := quickfix.NewMessage() + fixMsg.Body.SetField(quickfix.Tag(114), quickfix.FIXString("world")) + fixMsg.Body.SetField(quickfix.Tag(115), quickfix.FIXInt(3)) + fixMsg.Body.SetField(quickfix.Tag(117), quickfix.FIXBoolean(true)) + fixMsg.Body.SetField(quickfix.Tag(118), quickfix.FIXFloat(500.123)) + tVal, _ := time.Parse("2006-Jan-02", "2014-Jun-16") + fixMsg.Body.SetField(quickfix.Tag(119), quickfix.FIXUTCTimestamp{Value: tVal}) + + var msgOut Message + quickfix.Unmarshal(fixMsg, &msgOut) + + var tests = []struct { + expected interface{} + actual interface{} + }{ + {"world", msgOut.StringField}, + {3, msgOut.IntField}, + {0, msgOut.UnsetIntField}, + {true, msgOut.BoolField}, + {500.123, msgOut.FloatField}, + {tVal, msgOut.UTCTimestampField}, + } + + for _, test := range tests { + if test.expected != test.actual { + t.Errorf("Expected %v got %v", test.expected, test.actual) + } + } +} + +func TestUnmarshal_LiteralsOptional(t *testing.T) { + type Message struct { + FIXMsgType string `fix:"0"` + StringPtrField *string `fix:"112"` + UnsetStringPtrField *string `fix:"113"` + IntPtrField *int `fix:"114"` + BoolPtrField *bool `fix:"115"` + FloatPtrField *float64 `fix:"116"` + UTCTimestampPtrField *time.Time `fix:"117"` + } + + fixMsg := quickfix.NewMessage() + fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world")) + fixMsg.Body.SetField(quickfix.Tag(114), quickfix.FIXInt(3)) + fixMsg.Body.SetField(quickfix.Tag(115), quickfix.FIXBoolean(false)) + fixMsg.Body.SetField(quickfix.Tag(116), quickfix.FIXFloat(500.123)) + tVal, _ := time.Parse("2006-Jan-02", "2014-Jun-16") + fixMsg.Body.SetField(quickfix.Tag(117), quickfix.FIXUTCTimestamp{Value: tVal}) + + var msgOut Message + quickfix.Unmarshal(fixMsg, &msgOut) + + var tests = []struct { + testName string + expectNil bool + actualIsNil bool + expectedDeref interface{} + actualDeref interface{} + }{ + {"set string ptr", false, msgOut.StringPtrField == nil, "world", *msgOut.StringPtrField}, + {"unset string ptr", true, msgOut.UnsetStringPtrField == nil, "", "unset"}, + {"int ptr", false, msgOut.IntPtrField == nil, 3, *msgOut.IntPtrField}, + {"bool ptr", false, msgOut.BoolPtrField == nil, false, *msgOut.BoolPtrField}, + {"float ptr", false, msgOut.FloatPtrField == nil, 500.123, *msgOut.FloatPtrField}, + {"timestamp ptr", false, msgOut.UTCTimestampPtrField == nil, tVal, *msgOut.UTCTimestampPtrField}, + } + + for _, test := range tests { + if test.expectNil { + if !test.actualIsNil { + t.Errorf("%v: Expected nil not %v", test.testName, test.actualDeref) + } + continue + } + + if test.actualIsNil { + t.Errorf("%v: Did not expect nil", test.testName) + continue + } + + if test.expectedDeref != test.actualDeref { + t.Errorf("Expected '%v' got '%v'", test.expectedDeref, test.actualDeref) + } + } +} + +func TestUnmarshal_Components(t *testing.T) { + type Component1 struct { + StringField string `fix:"8"` + } + type Component2 struct { + IntField int `fix:"1"` + } + + type Message struct { + FIXMsgType string `fix:"0"` + Component1 + StringField string `fix:"112"` + *Component2 + } + + fixMsg := quickfix.NewMessage() + fixMsg.Body.SetField(quickfix.Tag(8), quickfix.FIXString("hello")) + fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world")) + + var msgOut Message + quickfix.Unmarshal(fixMsg, &msgOut) + + if msgOut.Component1.StringField != "hello" { + t.Errorf("Expected '%v' got '%v'", "hello", msgOut.Component1.StringField) + } + + if msgOut.StringField != "world" { + t.Errorf("Expected '%v' got '%v'", "world", msgOut.StringField) + } +} + +func TestUnmarshal_RepeatingGroups(t *testing.T) { + type Group1 struct { + StringField1 string `fix:"8"` + StringField2 *string `fix:"9"` + } + + type Group2 struct { + IntField1 int `fix:"1"` + IntField2 int `fix:"2"` + } + + type Message struct { + FIXMsgType string `fix:"0"` + GroupField1 []Group1 `fix:"100"` + StringField string `fix:"112"` + GroupField2 []Group2 `fix:"101"` + } + + fixMsg := quickfix.NewMessage() + fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world")) + + group1Template := quickfix.GroupTemplate{ + quickfix.GroupElement(quickfix.Tag(8)), + quickfix.GroupElement(quickfix.Tag(9)), + } + + group1 := quickfix.RepeatingGroup{Tag: quickfix.Tag(100), GroupTemplate: group1Template} + group1.Add().SetField(quickfix.Tag(8), quickfix.FIXString("hello")).SetField(quickfix.Tag(9), quickfix.FIXString("world")) + group1.Add().SetField(quickfix.Tag(8), quickfix.FIXString("goodbye")) + group1.Add().SetField(quickfix.Tag(8), quickfix.FIXString("OHHAI")).SetField(quickfix.Tag(9), quickfix.FIXString("world")) + fixMsg.Body.SetGroup(group1) + + var msgOut Message + quickfix.Unmarshal(fixMsg, &msgOut) + + if msgOut.StringField != "world" { + t.Errorf("Expected '%v' got '%v'", "world", msgOut.StringField) + } + + if len(msgOut.GroupField1) != 3 { + t.Errorf("Expected group size '%v' got '%v'", 3, len(msgOut.GroupField1)) + } + + if msgOut.GroupField1[0].StringField1 != "hello" { + t.Errorf("Expected %v got %v", "hello", msgOut.GroupField1[0].StringField1) + } + + if *(msgOut.GroupField1[0].StringField2) != "world" { + t.Errorf("Expected %v got %v", "world", *(msgOut.GroupField1[0].StringField2)) + } + + if msgOut.GroupField1[1].StringField1 != "goodbye" { + t.Errorf("Expected %v got %v", "goodbye", msgOut.GroupField1[1].StringField1) + } + + if msgOut.GroupField1[1].StringField2 != nil { + t.Errorf("Expected stringfield 2 to be nil, got %v", *(msgOut.GroupField1[1].StringField2)) + } +} + +func TestUnmarshal_HeaderTrailer(t *testing.T) { + type Header struct { + StringField string `fix:"49"` + } + type Trailer struct { + IntField int `fix:"89"` + } + + type Message struct { + FIXMsgType string `fix:"0"` + StringField string `fix:"112"` + Header + Trailer + } + + fixMsg := quickfix.NewMessage() + fixMsg.Header.SetField(quickfix.Tag(35), quickfix.FIXString("0")) + fixMsg.Header.SetField(quickfix.Tag(49), quickfix.FIXString("hello")) + fixMsg.Body.SetField(quickfix.Tag(112), quickfix.FIXString("world")) + fixMsg.Trailer.SetField(quickfix.Tag(89), quickfix.FIXInt(3)) + + var msgOut Message + quickfix.Unmarshal(fixMsg, &msgOut) + + if msgOut.Header.StringField != "hello" { + t.Errorf("Expected '%v' got '%v'", "hello", msgOut.Header.StringField) + } + if msgOut.Trailer.IntField != 3 { + t.Errorf("Expected '%v' got '%v'", 3, msgOut.Trailer.IntField) + } + +} -- GitLab