Skip to content

Commit 4f68b04

Browse files
authored
Tiny fixes (#331)
* do not skip fields in composite if keepzero is set * fix tag parsing * allow to use field name and tag options together in case when you don't want to duplicate the field name in the iso8583 tag, you can just skip it and set the rest of the options like this: ```go F2 *field.String `iso8583:",keepzero"` ``` then the field id will be derived from the field name and keepzero option will be used as well * add test for resetting field values * remove unnecessary test
1 parent 12a215c commit 4f68b04

File tree

4 files changed

+138
-47
lines changed

4 files changed

+138
-47
lines changed

field/composite.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,17 +234,23 @@ func (f *Composite) Marshal(v interface{}) error {
234234
defer f.mu.Unlock()
235235

236236
rv := reflect.ValueOf(v)
237-
if rv.Kind() != reflect.Ptr || rv.IsNil() {
238-
return errors.New("data is not a pointer or nil")
237+
if rv.Kind() != reflect.Ptr {
238+
return errors.New("data is not a pointer")
239239
}
240240

241-
// get the struct from the pointer
242-
dataStruct := rv.Elem()
241+
elemType := rv.Type().Elem()
242+
if elemType.Kind() != reflect.Struct {
243+
return errors.New("data must be a pointer to struct")
244+
}
243245

244-
if dataStruct.Kind() != reflect.Struct {
245-
return errors.New("data is not a struct")
246+
// If nil, create a new instance of the struct
247+
if rv.IsNil() {
248+
rv = reflect.New(elemType)
246249
}
247250

251+
// get the struct from the pointer
252+
dataStruct := rv.Elem()
253+
248254
// iterate over struct fields
249255
for i := 0; i < dataStruct.NumField(); i++ {
250256
indexTag := NewIndexTag(dataStruct.Type().Field(i))
@@ -258,7 +264,7 @@ func (f *Composite) Marshal(v interface{}) error {
258264
}
259265

260266
dataField := dataStruct.Field(i)
261-
if dataField.IsZero() {
267+
if dataField.IsZero() && !indexTag.KeepZero {
262268
continue
263269
}
264270

field/composite_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,23 @@ type SubConstructedTLVTestData struct {
351351
}
352352

353353
func TestCompositeField_Marshal(t *testing.T) {
354+
t.Run("Marshal returns error on nil value", func(t *testing.T) {
355+
composite := NewComposite(compositeTestSpec)
356+
err := composite.Marshal(nil)
357+
require.EqualError(t, err, "data is not a pointer")
358+
})
359+
360+
t.Run("Marshal doesn't return an error when nil pointer is of struct type", func(t *testing.T) {
361+
composite := NewComposite(compositeTestSpec)
362+
var data *CompositeTestData
363+
err := composite.Marshal(data)
364+
require.NoError(t, err)
365+
})
366+
354367
t.Run("Marshal returns an error on provision of primitive type data", func(t *testing.T) {
355368
composite := NewComposite(compositeTestSpec)
356369
err := composite.Marshal("primitive str")
357-
require.EqualError(t, err, "data is not a pointer or nil")
370+
require.EqualError(t, err, "data is not a pointer")
358371
})
359372

360373
t.Run("Marshal skips fields without index tag", func(t *testing.T) {

field/index_tag.go

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package field
33
import (
44
"reflect"
55
"regexp"
6+
"slices"
67
"strconv"
78
"strings"
89
)
@@ -20,6 +21,18 @@ type IndexTag struct {
2021
}
2122

2223
func NewIndexTag(field reflect.StructField) IndexTag {
24+
indexTag := extractTagInfo(field)
25+
26+
if indexTag.Tag == "" {
27+
id, tag := extractIdAndTagFromName(field.Name)
28+
indexTag.ID = id
29+
indexTag.Tag = tag
30+
}
31+
32+
return indexTag
33+
}
34+
35+
func extractTagInfo(field reflect.StructField) IndexTag {
2336
// value of the key "index" in the tag
2437
var value string
2538

@@ -34,66 +47,52 @@ func NewIndexTag(field reflect.StructField) IndexTag {
3447
// format of the value is "id[,keep_zero_value]"
3548
// id is the id of the field
3649
// let's parse it
37-
if value != "" {
38-
tag, opts := parseTag(value)
39-
40-
id, err := strconv.Atoi(tag)
41-
if err != nil {
42-
id = -1
43-
}
44-
50+
if value == "" {
4551
return IndexTag{
46-
ID: id,
47-
Tag: tag,
48-
KeepZero: opts.Contains("keepzero"),
52+
ID: -1,
4953
}
5054
}
5155

52-
dataFieldName := field.Name
53-
if len(dataFieldName) > 0 && fieldNameIndexRe.MatchString(dataFieldName) {
54-
indexStr := dataFieldName[1:]
56+
tag, opts := parseTag(value)
57+
id, err := strconv.Atoi(tag)
58+
if err != nil {
59+
id = -1
60+
}
61+
62+
return IndexTag{
63+
ID: id,
64+
Tag: tag,
65+
KeepZero: opts.Contains("keepzero"),
66+
}
67+
}
68+
69+
func extractIdAndTagFromName(fieldName string) (int, string) {
70+
if len(fieldName) > 0 && fieldNameIndexRe.MatchString(fieldName) {
71+
indexStr := fieldName[1:]
5572
fieldIndex, err := strconv.Atoi(indexStr)
5673
if err != nil {
57-
return IndexTag{
58-
ID: -1,
59-
Tag: indexStr,
60-
}
74+
return -1, indexStr
6175
}
6276

63-
return IndexTag{
64-
ID: fieldIndex,
65-
Tag: indexStr,
66-
}
77+
return fieldIndex, indexStr
6778
}
6879

69-
return IndexTag{
70-
ID: -1,
71-
}
80+
return -1, ""
7281
}
7382

74-
type tagOptions string
83+
type tagOptions []string
7584

7685
// parseTag splits a struct field's index tag into its id and
7786
// comma-separated options.
7887
func parseTag(tag string) (string, tagOptions) {
7988
tag, opt, _ := strings.Cut(tag, ",")
80-
return tag, tagOptions(opt)
89+
90+
return tag, tagOptions(strings.Split(opt, ","))
8191
}
8292

8393
// Contains reports whether a comma-separated list of options
8494
// contains a particular substr flag. substr must be surrounded by a
8595
// string boundary or commas.
8696
func (o tagOptions) Contains(optionName string) bool {
87-
if len(o) == 0 {
88-
return false
89-
}
90-
s := string(o)
91-
for s != "" {
92-
var name string
93-
name, s, _ = strings.Cut(s, ",")
94-
if name == optionName {
95-
return true
96-
}
97-
}
98-
return false
97+
return slices.Contains(o, optionName)
9998
}

message_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,79 @@ func TestMessage(t *testing.T) {
367367
wantMsg := []byte("01007000000000000000164242424242424242123456000000000100")
368368
require.Equal(t, wantMsg, rawMsg)
369369
})
370+
371+
t.Run("Clone, set zero values and reset fields", func(t *testing.T) {
372+
type TestISOF3Data struct {
373+
F1 *field.String
374+
F2 *field.String
375+
F3 *field.String
376+
}
377+
378+
type ISO87Data struct {
379+
F0 *field.String
380+
F2 *field.String
381+
F3 *TestISOF3Data
382+
F4 *field.String
383+
}
384+
385+
message := NewMessage(spec)
386+
err := message.Marshal(&ISO87Data{
387+
F0: field.NewStringValue("0100"),
388+
F2: field.NewStringValue("4242424242424242"),
389+
F3: &TestISOF3Data{
390+
F1: field.NewStringValue("12"),
391+
F2: field.NewStringValue("34"),
392+
F3: field.NewStringValue("56"),
393+
},
394+
F4: field.NewStringValue("100"),
395+
})
396+
require.NoError(t, err)
397+
398+
// clone the message and reset some fields
399+
clone, err := message.Clone()
400+
require.NoError(t, err)
401+
402+
// reset the fields
403+
// first, check that the fields are set
404+
data := &ISO87Data{}
405+
require.NoError(t, clone.Unmarshal(data))
406+
407+
require.Equal(t, "0100", data.F0.Value())
408+
require.Equal(t, "4242424242424242", data.F2.Value())
409+
require.Equal(t, "12", data.F3.F1.Value())
410+
require.Equal(t, "34", data.F3.F2.Value())
411+
require.Equal(t, "56", data.F3.F3.Value())
412+
require.Equal(t, "100", data.F4.Value())
413+
414+
// reset the fields
415+
err = clone.Marshal(&struct {
416+
F2 *field.String `iso8583:",keepzero"`
417+
F3 *struct {
418+
F2 *field.String `iso8583:",keepzero"`
419+
} `iso8583:",keepzero"`
420+
}{})
421+
require.NoError(t, err)
422+
423+
// check that the field values are set to zero values
424+
data = &ISO87Data{}
425+
require.NoError(t, clone.Unmarshal(data))
426+
427+
// check that fields are set
428+
require.NotNil(t, data.F2)
429+
require.NotNil(t, data.F3)
430+
require.NotNil(t, data.F3.F2)
431+
432+
// check the zero values
433+
require.Equal(t, "", data.F2.Value())
434+
require.Equal(t, "", data.F3.F2.Value())
435+
436+
// check the reset fields in the message
437+
require.Equal(t, "0100", data.F0.Value())
438+
require.Equal(t, "12", data.F3.F1.Value())
439+
require.Equal(t, "56", data.F3.F3.Value())
440+
require.Equal(t, "100", data.F4.Value())
441+
})
442+
370443
}
371444

372445
func TestPackUnpack(t *testing.T) {

0 commit comments

Comments
 (0)