Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion libs/structs/structaccess/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,17 @@ func setValueAtNode(parentVal reflect.Value, node *structpath.PathNode, value an
// Dereference parent if it's a pointer
for parentVal.Kind() == reflect.Pointer {
if parentVal.IsNil() {
return errors.New("parent is nil pointer")
return nilParentError(node)
}
parentVal = parentVal.Elem()
}

// getValue returns an invalid value for a nil parent omitted via json omitempty.
// Writing into a nil map would panic.
if !parentVal.IsValid() || ((parentVal.Kind() == reflect.Map || parentVal.Kind() == reflect.Slice) && parentVal.IsNil()) {
return nilParentError(node)
}

valueVal := reflect.ValueOf(value)

if idx, isIndex := node.Index(); isIndex {
Expand All @@ -101,6 +107,11 @@ func setValueAtNode(parentVal reflect.Value, node *structpath.PathNode, value an
return errors.New("unsupported path node type")
}

// nilParentError reports that the parent of node is nil. Missing parents are not allocated.
func nilParentError(node *structpath.PathNode) error {
return fmt.Errorf("cannot set %s: parent %s is nil", node.String(), node.Parent().String())
}

// setArrayElement sets an element in an array or slice
func setArrayElement(parentVal reflect.Value, index int, valueVal reflect.Value) error {
kind := parentVal.Kind()
Expand Down
40 changes: 40 additions & 0 deletions libs/structs/structaccess/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,43 @@ func TestGetSet_DashIsAFieldNameWhenTheTagHasOptions(t *testing.T) {
assert.Equal(t, "set", target.Named, "Set must write to Named")
assert.Equal(t, "s", target.Skipped, "json:\"-\" field must stay out of reach")
}

func TestSet_NilParent(t *testing.T) {
type Leaf struct {
N int `json:"n,omitempty"`
}
type Mid struct {
C *Leaf `json:"c,omitempty"`
}
type T struct {
M map[string]string `json:"m,omitempty"`
P *Leaf `json:"p,omitempty"`
A *Mid `json:"a,omitempty"`
S []string `json:"s,omitempty"`
NM map[string]string `json:"nm"`
NP *Leaf `json:"np"`
NS []string `json:"ns"`
}

tests := []struct {
path string
errorMsg string
}{
{"m.k", "cannot set m.k: parent m is nil"},
{"p.n", "cannot set p.n: parent p is nil"},
{"a.c.n", "cannot set a.c.n: parent a.c is nil"},
{"s[0]", "cannot set s[0]: parent s is nil"},
{"nm.k", "cannot set nm.k: parent nm is nil"},
{"np.n", "cannot set np.n: parent np is nil"},
{"ns[0]", "cannot set ns[0]: parent ns is nil"},
}

for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
target := &T{A: &Mid{}}
err := structaccess.SetByString(target, tt.path, "5")
assert.EqualError(t, err, tt.errorMsg)
assert.Equal(t, &T{A: &Mid{}}, target)
})
}
}
Loading