Skip to content

Commit 2a15358

Browse files
struct: stop trying to map empty nil pointers, correctly map most slice pointers
1 parent 8462f28 commit 2a15358

File tree

2 files changed

+127
-10
lines changed

2 files changed

+127
-10
lines changed

struct.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,15 @@ func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bo
278278
// mapToField maps the given value to the matching field of the given section.
279279
// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
280280
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
281+
typ := val.Type()
282+
// Early normalization of structs
281283
if val.Kind() == reflect.Ptr {
284+
typ = val.Type().Elem()
285+
if val.IsNil() {
286+
val.Set(reflect.New(typ))
287+
}
282288
val = val.Elem()
283289
}
284-
typ := val.Type()
285290

286291
for i := 0; i < typ.NumField(); i++ {
287292
field := val.Field(i)
@@ -324,11 +329,6 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int,
324329
if len(secs) <= sectionIndex {
325330
return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
326331
}
327-
// Only set the field to non-nil struct value if we have a section for it.
328-
// Otherwise, we end up with a non-nil struct ptr even though there is no data.
329-
if isStructPtr && field.IsNil() {
330-
field.Set(reflect.New(tpField.Type.Elem()))
331-
}
332332
if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
333333
return fmt.Errorf("map to field %q: %v", fieldName, err)
334334
}
@@ -367,12 +367,11 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (
367367

368368
typ := val.Type().Elem()
369369
for i, sec := range secs {
370-
elem := reflect.New(typ)
370+
val = reflect.Append(val, reflect.Zero(typ))
371+
elem := val.Index(val.Len() - 1)
371372
if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
372373
return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
373374
}
374-
375-
val = reflect.Append(val, elem.Elem())
376375
}
377376
return val, nil
378377
}
@@ -381,7 +380,10 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (
381380
func (s *Section) mapTo(v interface{}, isStrict bool) error {
382381
typ := reflect.TypeOf(v)
383382
val := reflect.ValueOf(v)
384-
if typ.Kind() == reflect.Ptr {
383+
isPtr := typ.Kind() == reflect.Ptr
384+
if isPtr && val.IsNil() {
385+
return fmt.Errorf("cannot decode to nil value of %q", typ)
386+
} else if isPtr {
385387
typ = typ.Elem()
386388
val = val.Elem()
387389
} else {

struct_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ type testNonUniqueSectionsStruct struct {
9191
Interface testInterface
9292
Peer []testPeer `ini:",nonunique"`
9393
}
94+
type testPeerPtr struct {
95+
PublicKey string
96+
PresharedKey string
97+
AllowedIPs []*string `delim:","`
98+
}
99+
100+
type testNonUniqueSectionsPtr struct {
101+
Interface testInterface
102+
Peer []*testPeerPtr `ini:",nonunique"`
103+
}
94104

95105
type BaseStruct struct {
96106
Base bool
@@ -291,6 +301,15 @@ func Test_MapToStruct(t *testing.T) {
291301
assert.Error(t, f.MapTo(testStruct{}))
292302
})
293303

304+
t.Run("map to nil target pointer", func(t *testing.T) {
305+
f, err := Load([]byte(confDataStruct))
306+
require.NoError(t, err)
307+
require.NotNil(t, f)
308+
309+
var ts *testStruct // nil pointer
310+
assert.Error(t, f.MapTo(ts))
311+
})
312+
294313
t.Run("map to unsupported type", func(t *testing.T) {
295314
f, err := Load([]byte(confDataStruct))
296315
require.NoError(t, err)
@@ -481,6 +500,102 @@ FieldInSection = 6
481500
})
482501
})
483502
}
503+
func Test_MapToStructNonUniquePtr(t *testing.T) {
504+
t.Run("map to struct non unique", func(t *testing.T) {
505+
t.Run("map file to struct non unique", func(t *testing.T) {
506+
f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
507+
require.NoError(t, err)
508+
ts := new(testNonUniqueSectionsPtr)
509+
510+
assert.NoError(t, f.MapTo(ts))
511+
512+
assert.Equal(t, "10.2.0.1/24", ts.Interface.Address)
513+
assert.Equal(t, 34777, ts.Interface.ListenPort)
514+
assert.Equal(t, "privServerKey", ts.Interface.PrivateKey)
515+
516+
assert.Equal(t, "pubClientKey", ts.Peer[0].PublicKey)
517+
assert.Equal(t, "psKey", ts.Peer[0].PresharedKey)
518+
assert.Equal(t, "10.2.0.2/32", ts.Peer[0].AllowedIPs[0])
519+
assert.Equal(t, "fd00:2::2/128", ts.Peer[0].AllowedIPs[1])
520+
521+
assert.Equal(t, "pubClientKey2", ts.Peer[1].PublicKey)
522+
assert.Equal(t, "psKey2", ts.Peer[1].PresharedKey)
523+
assert.Equal(t, "10.2.0.3/32", ts.Peer[1].AllowedIPs[0])
524+
assert.Equal(t, "fd00:2::3/128", ts.Peer[1].AllowedIPs[1])
525+
})
526+
527+
t.Run("map non unique section to struct", func(t *testing.T) {
528+
newPeer := new(testPeerPtr)
529+
newPeerSlice := make([]testPeerPtr, 0)
530+
531+
f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(confNonUniqueSectionDataStruct))
532+
require.NoError(t, err)
533+
534+
// try only first one
535+
assert.NoError(t, f.Section("Peer").MapTo(newPeer))
536+
assert.Equal(t, "pubClientKey", newPeer.PublicKey)
537+
assert.Equal(t, "psKey", newPeer.PresharedKey)
538+
assert.Equal(t, "10.2.0.2/32", newPeer.AllowedIPs[0])
539+
assert.Equal(t, "fd00:2::2/128", newPeer.AllowedIPs[1])
540+
541+
// try all
542+
assert.NoError(t, f.Section("Peer").MapTo(&newPeerSlice))
543+
assert.Equal(t, "pubClientKey", newPeerSlice[0].PublicKey)
544+
assert.Equal(t, "psKey", newPeerSlice[0].PresharedKey)
545+
assert.Equal(t, "10.2.0.2/32", newPeerSlice[0].AllowedIPs[0])
546+
assert.Equal(t, "fd00:2::2/128", newPeerSlice[0].AllowedIPs[1])
547+
548+
assert.Equal(t, "pubClientKey2", newPeerSlice[1].PublicKey)
549+
assert.Equal(t, "psKey2", newPeerSlice[1].PresharedKey)
550+
assert.Equal(t, "10.2.0.3/32", newPeerSlice[1].AllowedIPs[0])
551+
assert.Equal(t, "fd00:2::3/128", newPeerSlice[1].AllowedIPs[1])
552+
})
553+
554+
t.Run("map non unique sections with subsections to struct", func(t *testing.T) {
555+
iniFile, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, strings.NewReader(`
556+
[Section]
557+
FieldInSubSection = 1
558+
FieldInSubSection2 = 2
559+
FieldInSection = 3
560+
561+
[Section]
562+
FieldInSubSection = 4
563+
FieldInSubSection2 = 5
564+
FieldInSection = 6
565+
`))
566+
require.NoError(t, err)
567+
568+
type SubSection struct {
569+
FieldInSubSection string `ini:"FieldInSubSection"`
570+
}
571+
type SubSection2 struct {
572+
FieldInSubSection2 string `ini:"FieldInSubSection2"`
573+
}
574+
575+
type Section struct {
576+
SubSection `ini:"Section"`
577+
SubSection2 `ini:"Section"`
578+
FieldInSection string `ini:"FieldInSection"`
579+
}
580+
581+
type File struct {
582+
Sections []*Section `ini:"Section,nonunique"`
583+
}
584+
585+
f := new(File)
586+
err = iniFile.MapTo(f)
587+
require.NoError(t, err)
588+
589+
assert.Equal(t, "1", f.Sections[0].FieldInSubSection)
590+
assert.Equal(t, "2", f.Sections[0].FieldInSubSection2)
591+
assert.Equal(t, "3", f.Sections[0].FieldInSection)
592+
593+
assert.Equal(t, "4", f.Sections[1].FieldInSubSection)
594+
assert.Equal(t, "5", f.Sections[1].FieldInSubSection2)
595+
assert.Equal(t, "6", f.Sections[1].FieldInSection)
596+
})
597+
})
598+
}
484599

485600
func Test_ReflectFromStruct(t *testing.T) {
486601
t.Run("reflect from struct", func(t *testing.T) {

0 commit comments

Comments
 (0)