From 0ccf515bdabdea217048bd7e1e4d180bd7572a86 Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Wed, 6 Sep 2023 23:15:39 +0200 Subject: [PATCH 1/7] make experiment with simplified structs work --- exp_test.go | 34 ++++++++++++++++++++++++++++++++++ field/numeric.go | 26 ++++++++++++++++++++++++++ field/string.go | 15 +++++++++++++++ message.go | 4 +++- 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 exp_test.go diff --git a/exp_test.go b/exp_test.go new file mode 100644 index 00000000..4c750ca7 --- /dev/null +++ b/exp_test.go @@ -0,0 +1,34 @@ +package iso8583_test + +import ( + "testing" + + "github.com/moov-io/iso8583" + "github.com/moov-io/iso8583/specs" + "github.com/stretchr/testify/require" +) + +func TestStructWithTypes(t *testing.T) { + type authRequestData struct { + MTI string `index:"0"` + PrimaryAccountNumber string `index:"2"` + ProcessingCode int `index:"3"` + } + + t.Run("pack", func(t *testing.T) { + authRequest := &authRequestData{ + MTI: "0110", + PrimaryAccountNumber: "4242424242424242", + ProcessingCode: 200000, + } + + message := iso8583.NewMessage(specs.Spec87ASCII) + err := message.Marshal(authRequest) + require.NoError(t, err) + + packed, err := message.Pack() + require.NoError(t, err) + + require.Equal(t, "011060000000000000000000000000000000164242424242424242200000", string(packed)) + }) +} diff --git a/field/numeric.go b/field/numeric.go index 18e41936..dcf5d306 100644 --- a/field/numeric.go +++ b/field/numeric.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "strconv" "github.com/moov-io/iso8583/utils" @@ -143,6 +144,31 @@ func (f *Numeric) Unmarshal(v interface{}) error { } func (f *Numeric) SetData(data interface{}) error { + if v, ok := data.(reflect.Value); ok { + switch v.Kind() { + case reflect.Int: + f.value = int(v.Int()) + case reflect.String: + val, err := strconv.Atoi(v.String()) + if err != nil { + return utils.NewSafeError(err, "failed to convert into number") + } + f.value = val + default: + return fmt.Errorf("data does not match required *Numeric type") + } + return nil + } + + // if v.Kind() == reflect.Int { + // } + // if v, ok := data.(reflect.Value); ok { + // if v.CanSet() { + // v.Set(reflect.ValueOf(f.value)) + // return nil + // } + // } + if data == nil { return nil } diff --git a/field/string.go b/field/string.go index 94a1a8a4..f68501b8 100644 --- a/field/string.go +++ b/field/string.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "github.com/moov-io/iso8583/utils" ) @@ -129,6 +130,20 @@ func (f *String) Unmarshal(v interface{}) error { } func (f *String) SetData(data interface{}) error { + if v, ok := data.(reflect.Value); ok { + switch v.Kind() { + case reflect.String: + f.value = v.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + f.value = fmt.Sprintf("%d", v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + f.value = fmt.Sprintf("%d", v.Uint()) + default: + return fmt.Errorf("unsupported type %v", v.Kind()) + } + return nil + } + if data == nil { return nil } diff --git a/message.go b/message.go index 51c7a474..cfc2d363 100644 --- a/message.go +++ b/message.go @@ -376,7 +376,9 @@ func (m *Message) Marshal(v interface{}) error { continue } - err = messageField.Marshal(dataField.Interface()) + err = messageField.Marshal(dataField) + + // err = messageField.Marshal(dataField.Interface()) if err != nil { return fmt.Errorf("failed to set value to field %d: %w", fieldIndex, err) } From 9fdc9b6a32460bee091084f3d33dc5eef46afc6c Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Thu, 7 Sep 2023 21:30:12 +0200 Subject: [PATCH 2/7] remove "magical linking" with SetData it's undocumented side effect from the past. We promote using Marshal and Unmarshal which set/get values and do not link fields. --- field/numeric_test.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/field/numeric_test.go b/field/numeric_test.go index c2ac8f87..4715440e 100644 --- a/field/numeric_test.go +++ b/field/numeric_test.go @@ -151,18 +151,9 @@ func TestNumericFieldZeroLeftPaddedZero(t *testing.T) { } func TestNumericSetBytesSetsDataOntoDataStruct(t *testing.T) { - numeric := NewNumeric(&Spec{ - Length: 1, - Description: "Field", - Enc: encoding.ASCII, - Pref: prefix.ASCII.Fixed, - }) - data := &Numeric{} - err := numeric.SetData(data) - require.NoError(t, err) - err = numeric.SetBytes([]byte("9")) + err := data.SetBytes([]byte("9")) require.NoError(t, err) require.Equal(t, 9, data.Value()) From cb6628dd0af2d85cae7e1184504d92cd7c7b45fc Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Thu, 7 Sep 2023 21:38:35 +0200 Subject: [PATCH 3/7] fix composite test (changed error message) --- field/composite_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/field/composite_test.go b/field/composite_test.go index 874273af..83e0ea76 100644 --- a/field/composite_test.go +++ b/field/composite_test.go @@ -595,7 +595,7 @@ func TestCompositePacking(t *testing.T) { }) require.Error(t, err) - require.EqualError(t, err, "failed to set data from field 1: data does not match required *String type") + require.Contains(t, err.Error(), "failed to set data from field 1: data does not match required *String") }) t.Run("Pack returns error on failure of subfield packing", func(t *testing.T) { From b65ea6380a46d21af53481c7dd479e71dccf7802 Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Thu, 7 Sep 2023 23:27:56 +0200 Subject: [PATCH 4/7] add field tag and support different types for String, Numeric --- exp_test.go | 3 +- field/composite.go | 13 ++++-- field/numeric.go | 64 +++++++++++------------------ field/numeric_test.go | 9 ----- field/string.go | 48 +++++++++------------- field/string_test.go | 18 +-------- field_tag.go | 93 +++++++++++++++++++++++++++++++++++++++++++ message.go | 31 +++++++++------ 8 files changed, 166 insertions(+), 113 deletions(-) create mode 100644 field_tag.go diff --git a/exp_test.go b/exp_test.go index 4c750ca7..f6d45b1b 100644 --- a/exp_test.go +++ b/exp_test.go @@ -13,6 +13,7 @@ func TestStructWithTypes(t *testing.T) { MTI string `index:"0"` PrimaryAccountNumber string `index:"2"` ProcessingCode int `index:"3"` + TransactionAmount int `index:"4,keepzero"` // we will set message field value to 0 } t.Run("pack", func(t *testing.T) { @@ -29,6 +30,6 @@ func TestStructWithTypes(t *testing.T) { packed, err := message.Pack() require.NoError(t, err) - require.Equal(t, "011060000000000000000000000000000000164242424242424242200000", string(packed)) + require.Equal(t, "011070000000000000000000000000000000164242424242424242200000000000000000", string(packed)) }) } diff --git a/field/composite.go b/field/composite.go index 2edeea52..d3fc9b3a 100644 --- a/field/composite.go +++ b/field/composite.go @@ -623,12 +623,17 @@ var fieldNameTagRe = regexp.MustCompile(`^F.+$`) // field name. If it does not match F.+ pattern, it checks value of `index` // tag. If empty string, then index/tag was not found for the field. func getFieldIndexOrTag(field reflect.StructField) (string, error) { - dataFieldName := field.Name - - if fieldIndex := field.Tag.Get("index"); fieldIndex != "" { - return fieldIndex, nil + var fieldIndex string + // keep the order of tags for now, when index tag is deprecated we can + // change the order + for _, tag := range []string{"index", "iso8583"} { + if fieldIndex = field.Tag.Get(tag); fieldIndex != "" { + return fieldIndex, nil + } } + dataFieldName := field.Name + if len(dataFieldName) > 0 && fieldNameTagRe.MatchString(dataFieldName) { return dataFieldName[1:], nil } diff --git a/field/numeric.go b/field/numeric.go index dcf5d306..3cf04d7e 100644 --- a/field/numeric.go +++ b/field/numeric.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "reflect" + "math" "strconv" "github.com/moov-io/iso8583/utils" @@ -17,7 +17,6 @@ var _ json.Unmarshaler = (*Numeric)(nil) type Numeric struct { value int spec *Spec - data *Numeric } func NewNumeric(spec *Spec) *Numeric { @@ -55,9 +54,6 @@ func (f *Numeric) SetBytes(b []byte) error { f.value = val } - if f.data != nil { - *(f.data) = *f - } return nil } @@ -144,44 +140,32 @@ func (f *Numeric) Unmarshal(v interface{}) error { } func (f *Numeric) SetData(data interface{}) error { - if v, ok := data.(reflect.Value); ok { - switch v.Kind() { - case reflect.Int: - f.value = int(v.Int()) - case reflect.String: - val, err := strconv.Atoi(v.String()) - if err != nil { - return utils.NewSafeError(err, "failed to convert into number") - } - f.value = val - default: - return fmt.Errorf("data does not match required *Numeric type") + switch v := data.(type) { + case *Numeric: + if v == nil { + return nil } - return nil - } - - // if v.Kind() == reflect.Int { - // } - // if v, ok := data.(reflect.Value); ok { - // if v.CanSet() { - // v.Set(reflect.ValueOf(f.value)) - // return nil - // } - // } - - if data == nil { - return nil - } - - num, ok := data.(*Numeric) - if !ok { - return fmt.Errorf("data does not match required *Numeric type") + f.value = v.value + case int: + f.value = v + case int32: + if v >= math.MinInt32 && v <= math.MaxInt32 { + f.value = int(v) + } else { + return fmt.Errorf("int32 value out of range for int") + } + case int64: + if v >= math.MinInt64 && v <= math.MaxInt64 { + f.value = int(v) + } else { + return fmt.Errorf("int64 value out of range for int") + } + case []byte: + return f.SetBytes(v) + default: + return fmt.Errorf("data does not match require *Numeric or supported numeric types (int, int32, int64)") } - f.data = num - if num.value != 0 { - f.value = num.value - } return nil } diff --git a/field/numeric_test.go b/field/numeric_test.go index 4715440e..05e236d2 100644 --- a/field/numeric_test.go +++ b/field/numeric_test.go @@ -42,17 +42,8 @@ func TestNumericField(t *testing.T) { require.NoError(t, err) require.Equal(t, " 9876", string(packed)) - numeric = NewNumeric(spec) - data := NewNumericValue(0) - numeric.SetData(data) - length, err = numeric.Unpack([]byte(" 9876")) - require.NoError(t, err) - require.Equal(t, 10, length) - require.Equal(t, 9876, data.Value()) - numeric = NewNumeric(spec) numeric.SetValue(9876) - require.Equal(t, 9876, numeric.Value()) } diff --git a/field/string.go b/field/string.go index f68501b8..29489e03 100644 --- a/field/string.go +++ b/field/string.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "reflect" + "strconv" "github.com/moov-io/iso8583/utils" ) @@ -16,7 +16,6 @@ var _ json.Unmarshaler = (*String)(nil) type String struct { value string spec *Spec - data *String } func NewString(spec *Spec) *String { @@ -41,9 +40,6 @@ func (f *String) SetSpec(spec *Spec) { func (f *String) SetBytes(b []byte) error { f.value = string(b) - if f.data != nil { - *(f.data) = *f - } return nil } @@ -130,33 +126,27 @@ func (f *String) Unmarshal(v interface{}) error { } func (f *String) SetData(data interface{}) error { - if v, ok := data.(reflect.Value); ok { - switch v.Kind() { - case reflect.String: - f.value = v.String() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - f.value = fmt.Sprintf("%d", v.Int()) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - f.value = fmt.Sprintf("%d", v.Uint()) - default: - return fmt.Errorf("unsupported type %v", v.Kind()) + switch v := data.(type) { + case *String: + if v == nil { + return nil } - return nil - } - - if data == nil { - return nil - } - - str, ok := data.(*String) - if !ok { - return fmt.Errorf("data does not match required *String type") + f.value = v.value + case string: + if v == "" { + return nil + } + f.value = v + case int: + f.value = strconv.FormatInt(int64(v), 10) + case int32: + f.value = strconv.FormatInt(int64(v), 10) + case int64: + f.value = strconv.FormatInt(v, 10) + default: + return fmt.Errorf("data does not match required *String or string type") } - f.data = str - if str.value != "" { - f.value = str.value - } return nil } diff --git a/field/string_test.go b/field/string_test.go index f2537d32..d0b995b8 100644 --- a/field/string_test.go +++ b/field/string_test.go @@ -43,24 +43,8 @@ func TestStringField(t *testing.T) { require.Equal(t, " hello", string(packed)) str = NewString(spec) - data := NewStringValue("") - str.SetData(data) - length, err = str.Unpack([]byte(" olleh")) - require.NoError(t, err) - require.Equal(t, 10, length) - require.Equal(t, "olleh", data.Value()) - - str = NewString(spec) - data = &String{} - str.SetData(data) - err = str.SetBytes([]byte("hello")) - require.NoError(t, err) - require.Equal(t, "hello", data.Value()) - - str = NewString(spec) - str.SetValue("hello") - require.Equal(t, "hello", data.Value()) + require.Equal(t, "hello", str.Value()) } func TestStringNil(t *testing.T) { diff --git a/field_tag.go b/field_tag.go new file mode 100644 index 00000000..4601352a --- /dev/null +++ b/field_tag.go @@ -0,0 +1,93 @@ +package iso8583 + +import ( + "reflect" + "strconv" + "strings" +) + +type FieldTag struct { + Id int // is -1 if index is not a number + Index string + + // KeepZero tells the marshaler to use zero value and set bitmap bit to + // 1 for this field. Default behavior is to omit the field from the + // message if it's zero value. + KeepZero bool +} + +func NewFieldTag(field reflect.StructField) FieldTag { + // value of the key "index" in the tag + var value string + + // keep the order of tags for now, when index tag is deprecated we can + // change the order + for _, tag := range []string{"index", "iso8583"} { + if value = field.Tag.Get(tag); value != "" { + break + } + } + + // format of the value is "id[,keep_zero_value]" + // id is the id of the field + // let's parse it + if value != "" { + index, keepZero := parseValue(value) + + id, err := strconv.Atoi(index) + if err != nil { + id = -1 + } + + return FieldTag{ + Id: id, + Index: index, + KeepZero: keepZero, + } + } + + dataFieldName := field.Name + if len(dataFieldName) > 0 && fieldNameIndexRe.MatchString(dataFieldName) { + indexStr := dataFieldName[1:] + fieldIndex, err := strconv.Atoi(indexStr) + if err != nil { + return FieldTag{ + Id: -1, + Index: indexStr, + } + } + + return FieldTag{ + Id: fieldIndex, + Index: indexStr, + } + } + + return FieldTag{ + Id: -1, + } +} + +func parseValue(value string) (index string, keepZero bool) { + if value == "" { + return + } + + // split the value by comma + values := strings.Split(value, ",") + + // the first value is the index + index = values[0] + + // if there is only one value, return + if len(values) == 1 { + return + } + + // if the second value is "keep_zero_value", set the flag + if values[1] == "keepzero" { + keepZero = true + } + + return +} diff --git a/message.go b/message.go index cfc2d363..7cd406c0 100644 --- a/message.go +++ b/message.go @@ -354,36 +354,41 @@ func (m *Message) Marshal(v interface{}) error { // iterate over struct fields for i := 0; i < dataStruct.NumField(); i++ { - fieldIndex, err := getFieldIndex(dataStruct.Type().Field(i)) - if err != nil { - return fmt.Errorf("getting field %d index: %w", i, err) - } + fieldTag := NewFieldTag(dataStruct.Type().Field(i)) - // skip field without index - if fieldIndex < 0 { + // skip field without index or if index in tag is not defined + if fieldTag.Id < 0 { continue } - messageField := m.GetField(fieldIndex) + messageField := m.GetField(fieldTag.Id) // if struct field we are usgin to populate value expects to // set index of the field that is not described by spec if messageField == nil { - return fmt.Errorf("no message field defined by spec with index: %d", fieldIndex) + return fmt.Errorf("no message field defined by spec with index: %d", fieldTag.Id) } dataField := dataStruct.Field(i) - if dataField.IsNil() { + + // for pointer fields we need to check if they are nil + // and if they are we need to skip them, to not set bitmap bit + // and not to set value to the field + if dataField.Kind() == reflect.Pointer && dataField.IsZero() { continue } - err = messageField.Marshal(dataField) + // for non pointer fields we need to check if they are zero + // and we want to skip them (as specified in the field tag) + if dataField.IsZero() && !fieldTag.KeepZero { + continue + } - // err = messageField.Marshal(dataField.Interface()) + err := messageField.Marshal(dataField.Interface()) if err != nil { - return fmt.Errorf("failed to set value to field %d: %w", fieldIndex, err) + return fmt.Errorf("failed to set value to field %d: %w", fieldTag.Id, err) } - m.fieldsMap[fieldIndex] = struct{}{} + m.fieldsMap[fieldTag.Id] = struct{}{} } return nil From b657e60fefd51d21adf8dc95898149009798576a Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Fri, 8 Sep 2023 10:56:22 +0200 Subject: [PATCH 5/7] set String and Numeric field data using struct with basic types --- exp_test.go | 161 +++++++++++++++++++++++++++++++++++++++++++---- field/numeric.go | 40 +++++++----- field/string.go | 14 +++-- 3 files changed, 185 insertions(+), 30 deletions(-) diff --git a/exp_test.go b/exp_test.go index f6d45b1b..01424613 100644 --- a/exp_test.go +++ b/exp_test.go @@ -13,23 +13,162 @@ func TestStructWithTypes(t *testing.T) { MTI string `index:"0"` PrimaryAccountNumber string `index:"2"` ProcessingCode int `index:"3"` - TransactionAmount int `index:"4,keepzero"` // we will set message field value to 0 + TransactionAmount *int `index:"4,keepzero"` // we will set message field value to 0 } t.Run("pack", func(t *testing.T) { - authRequest := &authRequestData{ - MTI: "0110", - PrimaryAccountNumber: "4242424242424242", - ProcessingCode: 200000, + panInt := 4242424242424242 + panStr := "4242424242424242" + + tests := []struct { + name string + input interface{} + expectedPackedString string + }{ + // Tests for string type + { + name: "struct with string type and value set", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber string `index:"2"` + }{ + MTI: "0110", + PrimaryAccountNumber: panStr, + }, + expectedPackedString: "011040000000000000000000000000000000164242424242424242", + }, + { + name: "struct with string type and no value", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber string `index:"2"` + }{ + MTI: "0110", + }, + expectedPackedString: "011000000000000000000000000000000000", + }, + { + name: "struct with string type, no value and keepzero tag - length prefix is set to 0 and no value is following", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber string `index:"2,keepzero"` + }{ + MTI: "0110", + }, + expectedPackedString: "01104000000000000000000000000000000000", + }, + + // Tests for *string type + { + name: "struct with *string type and value set", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber *string `index:"2"` + }{ + MTI: "0110", + PrimaryAccountNumber: &panStr, + }, + expectedPackedString: "011040000000000000000000000000000000164242424242424242", + }, + { + name: "struct with *string type and no value", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber *string `index:"2"` + }{ + MTI: "0110", + }, + expectedPackedString: "011000000000000000000000000000000000", + }, + { + name: "struct with *string type, no value and keepzero tag", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber *string `index:"2,keepzero"` + }{ + MTI: "0110", + }, + expectedPackedString: "011000000000000000000000000000000000", + }, + + // Tests for int type + { + name: "struct with int type and value set", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber int `index:"2"` + }{ + MTI: "0110", + PrimaryAccountNumber: panInt, + }, + expectedPackedString: "011040000000000000000000000000000000164242424242424242", + }, + { + name: "struct with int type and no value", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber int `index:"2"` + }{ + MTI: "0110", + }, + expectedPackedString: "011000000000000000000000000000000000", + }, + { + name: "struct with int type, no value and keepzero tag", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber int `index:"2,keepzero"` + }{ + MTI: "0110", + }, + expectedPackedString: "011040000000000000000000000000000000010", + }, + + // Tests for *int type + { + name: "struct with *int type and value set", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber *int `index:"2"` + }{ + MTI: "0110", + PrimaryAccountNumber: &panInt, + }, + expectedPackedString: "011040000000000000000000000000000000164242424242424242", + }, + { + name: "struct with *int type and no value", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber *int `index:"2"` + }{ + MTI: "0110", + }, + expectedPackedString: "011000000000000000000000000000000000", + }, + { + name: "struct with *int type, no value and keepzero tag", + input: struct { + MTI string `index:"0"` + PrimaryAccountNumber *int `index:"2,keepzero"` + }{ + MTI: "0110", + }, + expectedPackedString: "011000000000000000000000000000000000", + }, } - message := iso8583.NewMessage(specs.Spec87ASCII) - err := message.Marshal(authRequest) - require.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := iso8583.NewMessage(specs.Spec87ASCII) + err := message.Marshal(tt.input) + require.NoError(t, err) - packed, err := message.Pack() - require.NoError(t, err) + packed, err := message.Pack() + require.NoError(t, err) - require.Equal(t, "011070000000000000000000000000000000164242424242424242200000000000000000", string(packed)) + require.Equal(t, tt.expectedPackedString, string(packed)) + }) + } }) } diff --git a/field/numeric.go b/field/numeric.go index 3cf04d7e..4e11992f 100644 --- a/field/numeric.go +++ b/field/numeric.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "strconv" "github.com/moov-io/iso8583/utils" @@ -148,22 +147,35 @@ func (f *Numeric) SetData(data interface{}) error { f.value = v.value case int: f.value = v - case int32: - if v >= math.MinInt32 && v <= math.MaxInt32 { - f.value = int(v) - } else { - return fmt.Errorf("int32 value out of range for int") + case *int: + if v == nil { + f.value = 0 + return nil } - case int64: - if v >= math.MinInt64 && v <= math.MaxInt64 { - f.value = int(v) - } else { - return fmt.Errorf("int64 value out of range for int") + f.value = *v + case string: + if v == "" { + f.value = 0 + return nil } - case []byte: - return f.SetBytes(v) + val, err := strconv.Atoi(v) + if err != nil { + return utils.NewSafeError(err, "failed to convert sting value into number") + } + f.value = val + case *string: + if v == nil { + f.value = 0 + return nil + } + + val, err := strconv.Atoi(*v) + if err != nil { + return utils.NewSafeError(err, "failed to convert sting value into number") + } + f.value = val default: - return fmt.Errorf("data does not match require *Numeric or supported numeric types (int, int32, int64)") + return fmt.Errorf("data does not match require *Numeric or supported numeric types (int, *int, string, *string)") } return nil diff --git a/field/string.go b/field/string.go index 29489e03..0640390a 100644 --- a/field/string.go +++ b/field/string.go @@ -137,14 +137,18 @@ func (f *String) SetData(data interface{}) error { return nil } f.value = v + case *string: + if v == nil { + f.value = "" + return nil + } + f.value = *v case int: f.value = strconv.FormatInt(int64(v), 10) - case int32: - f.value = strconv.FormatInt(int64(v), 10) - case int64: - f.value = strconv.FormatInt(v, 10) + case *int: + f.value = strconv.FormatInt(int64(*v), 10) default: - return fmt.Errorf("data does not match required *String or string type") + return fmt.Errorf("data does not match required *String or (string, *string, int, *int) type") } return nil From 255001f99e8885de0c223838555b6898faa8b3e2 Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Fri, 8 Sep 2023 14:58:14 +0200 Subject: [PATCH 6/7] remove unused type --- exp_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/exp_test.go b/exp_test.go index 01424613..970358ce 100644 --- a/exp_test.go +++ b/exp_test.go @@ -9,13 +9,6 @@ import ( ) func TestStructWithTypes(t *testing.T) { - type authRequestData struct { - MTI string `index:"0"` - PrimaryAccountNumber string `index:"2"` - ProcessingCode int `index:"3"` - TransactionAmount *int `index:"4,keepzero"` // we will set message field value to 0 - } - t.Run("pack", func(t *testing.T) { panInt := 4242424242424242 panStr := "4242424242424242" From 2f44c9f5c9bf72927313d35e4dfaa478711943fb Mon Sep 17 00:00:00 2001 From: Pavel Gabriel Date: Mon, 11 Sep 2023 16:05:48 +0200 Subject: [PATCH 7/7] implement unmarshal --- exp_test.go | 39 +++++++++++++++++++++++++++++++++++++++ field/composite_test.go | 2 +- field/string.go | 37 +++++++++++++++++++++++++++---------- message.go | 16 ++++++++++++---- 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/exp_test.go b/exp_test.go index 970358ce..4e88c610 100644 --- a/exp_test.go +++ b/exp_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/moov-io/iso8583" + "github.com/moov-io/iso8583/field" "github.com/moov-io/iso8583/specs" "github.com/stretchr/testify/require" ) @@ -164,4 +165,42 @@ func TestStructWithTypes(t *testing.T) { }) } }) + + t.Run("unpack", func(t *testing.T) { + type authRequest struct { + MTI string `index:"0"` + PrimaryAccountNumber string `index:"2"` + } + + packed := []byte("011040000000000000000000000000000000164242424242424242") + + message := iso8583.NewMessage(specs.Spec87ASCII) + err := message.Unpack(packed) + require.NoError(t, err) + + data := authRequest{} + err = message.Unmarshal(&data) + require.NoError(t, err) + require.Equal(t, "0110", data.MTI) + require.Equal(t, "4242424242424242", data.PrimaryAccountNumber) + }) + + t.Run("unpack2", func(t *testing.T) { + type authRequest struct { + MTI *string `index:"0"` + PrimaryAccountNumber *field.String `index:"2"` + } + + packed := []byte("011040000000000000000000000000000000164242424242424242") + + message := iso8583.NewMessage(specs.Spec87ASCII) + err := message.Unpack(packed) + require.NoError(t, err) + + data := authRequest{} + err = message.Unmarshal(&data) + require.NoError(t, err) + require.Equal(t, "0110", *data.MTI) + require.Equal(t, "4242424242424242", data.PrimaryAccountNumber.Value()) + }) } diff --git a/field/composite_test.go b/field/composite_test.go index 83e0ea76..f789adb4 100644 --- a/field/composite_test.go +++ b/field/composite_test.go @@ -745,7 +745,7 @@ func TestCompositePacking(t *testing.T) { err = composite.Unmarshal(data) require.Error(t, err) - require.EqualError(t, err, "failed to get data from field 1: data does not match required *String type") + require.Contains(t, err.Error(), "failed to get data from field 1: data does not match required *String") }) t.Run("Unpack returns an error on failure of subfield to unpack bytes", func(t *testing.T) { diff --git a/field/string.go b/field/string.go index 0640390a..598712dd 100644 --- a/field/string.go +++ b/field/string.go @@ -2,8 +2,8 @@ package field import ( "encoding/json" - "errors" "fmt" + "reflect" "strconv" "github.com/moov-io/iso8583/utils" @@ -111,17 +111,34 @@ func (f *String) Unpack(data []byte) (int, error) { } func (f *String) Unmarshal(v interface{}) error { - if v == nil { - return nil - } - - str, ok := v.(*String) - if !ok { - return errors.New("data does not match required *String type") + switch val := v.(type) { + case reflect.Value: + switch val.Kind() { + case reflect.String: + val.SetString(f.value) + case reflect.Int: + i, err := strconv.Atoi(f.value) + if err != nil { + return fmt.Errorf("failed to convert string to int: %w", err) + } + val.SetInt(int64(i)) + default: + return fmt.Errorf("data does not match required reflect.Value type") + } + case *string: + *val = f.value + case *int: + i, err := strconv.Atoi(f.value) + if err != nil { + return fmt.Errorf("failed to convert string to int: %w", err) + } + *val = i + case *String: + val.value = f.value + default: + return fmt.Errorf("data does not match required *String or *string type") } - str.value = f.value - return nil } diff --git a/message.go b/message.go index 7cd406c0..59bb8846 100644 --- a/message.go +++ b/message.go @@ -433,11 +433,19 @@ func (m *Message) Unmarshal(v interface{}) error { } dataField := dataStruct.Field(i) - if dataField.IsNil() { - dataField.Set(reflect.New(dataField.Type().Elem())) - } - err = messageField.Unmarshal(dataField.Interface()) + // if field is pointer we will pass pointer to the field + if dataField.Kind() == reflect.Ptr { + if dataField.IsZero() { + fmt.Println("is zero") + dataField.Set(reflect.New(dataField.Type().Elem())) + } + + err = messageField.Unmarshal(dataField.Interface()) + } else { + // if field is not pointer we will pass the field as reflect.Value + err = messageField.Unmarshal(dataField) + } if err != nil { return fmt.Errorf("failed to get value from field %d: %w", fieldIndex, err) }