Skip to content

Commit e4fed12

Browse files
committed
feat(unset): support unset value to zero. (#42)
1 parent 5220e08 commit e4fed12

File tree

4 files changed

+235
-0
lines changed

4 files changed

+235
-0
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/creasty/defaults
22

33
go 1.14
4+
5+
require github.com/stretchr/testify v1.7.0

go.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
11+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

unset.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package defaults
2+
3+
import (
4+
"reflect"
5+
)
6+
7+
const (
8+
unsetFlag = "unset"
9+
// unsetRecursion means that the field will be unset recursively
10+
unsetRecursion = "walk"
11+
)
12+
13+
func MustUnset(ptr interface{}) {
14+
if err := Unset(ptr); err != nil {
15+
panic(err)
16+
}
17+
}
18+
func Unset(obj interface{}) error {
19+
v := indirect(reflect.ValueOf(obj))
20+
if v.Kind() != reflect.Struct {
21+
return errInvalidType
22+
}
23+
unsetStruct(v)
24+
return nil
25+
}
26+
func unsetStruct(obj reflect.Value) {
27+
t := obj.Type()
28+
for i := 0; i < t.NumField(); i++ {
29+
unsetVal := t.Field(i).Tag.Get(unsetFlag)
30+
if unsetVal == "-" {
31+
continue
32+
}
33+
unsetField(obj.Field(i), unsetVal == unsetRecursion)
34+
}
35+
}
36+
37+
func indirect(v reflect.Value) reflect.Value {
38+
finalValue := v
39+
for finalValue.Kind() == reflect.Ptr {
40+
finalValue = finalValue.Elem()
41+
}
42+
return finalValue
43+
}
44+
45+
func unsetField(field reflect.Value, unsetWalk bool) {
46+
if !field.CanSet() {
47+
return
48+
}
49+
50+
isInitial := isInitialValue(field)
51+
if isInitial {
52+
return
53+
}
54+
if !unsetWalk {
55+
field.Set(reflect.Zero(field.Type()))
56+
return
57+
}
58+
59+
switch field.Kind() {
60+
default:
61+
field.Set(reflect.Zero(field.Type()))
62+
case reflect.Ptr:
63+
unsetField(field.Elem(), true)
64+
case reflect.Struct:
65+
unsetStruct(field)
66+
case reflect.Slice:
67+
for j := 0; j < field.Len(); j++ {
68+
unsetField(field.Index(j), true)
69+
}
70+
case reflect.Map:
71+
for _, e := range field.MapKeys() {
72+
var mapValue = field.MapIndex(e)
73+
switch mapValue.Kind() {
74+
case reflect.Ptr:
75+
unsetField(mapValue.Elem(), true)
76+
case reflect.Struct, reflect.Slice, reflect.Map:
77+
ref := reflect.New(mapValue.Type())
78+
ref.Elem().Set(mapValue)
79+
unsetField(ref.Elem(), true)
80+
field.SetMapIndex(e, ref.Elem().Convert(mapValue.Type()))
81+
default:
82+
field.SetMapIndex(e, reflect.Zero(mapValue.Type()))
83+
}
84+
}
85+
}
86+
}

unset_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package defaults
2+
3+
import (
4+
"github.com/stretchr/testify/require"
5+
"testing"
6+
)
7+
8+
func TestUnset(t *testing.T) {
9+
t.Run("sample unset", func(t *testing.T) {
10+
s := &Sample{}
11+
MustSet(s)
12+
err := Unset(s)
13+
require.NoError(t, err)
14+
require.Equal(t, s, &Sample{})
15+
})
16+
17+
t.Run("errInvalidType", func(t *testing.T) {
18+
tmp := 8
19+
require.Equal(t, Unset(&tmp), errInvalidType)
20+
require.Panics(t, func() { MustUnset(&tmp) })
21+
})
22+
23+
t.Run("not reset by -", func(t *testing.T) {
24+
s := &struct {
25+
IgnoreMe string `default:"-" unset:"-"`
26+
}{
27+
IgnoreMe: "test",
28+
}
29+
MustUnset(s)
30+
require.Equal(t, s.IgnoreMe, "test")
31+
})
32+
33+
t.Run("sampleUnset test", func(t *testing.T) {
34+
s := &SampleUnset{}
35+
MustSet(s)
36+
var testNumPtr *int = nil
37+
s.SliceOfIntPtrPtr[1] = &testNumPtr
38+
s.private = StructUnset{WithDefault: "test"}
39+
MustUnset(s)
40+
41+
// struct
42+
require.Equal(t, s.private.WithDefault, "test")
43+
require.Equal(t, 0, s.Struct.Foo)
44+
require.Equal(t, 0, s.Struct.Bar)
45+
require.Nil(t, s.Struct.BarPtr)
46+
require.Equal(t, 0, *s.Struct.BarPtrWithWalk)
47+
require.Empty(t, s.StructPtrNoWalk)
48+
require.Equal(t, "foo", s.StructPtr.EmbeddedUnset.String)
49+
require.Equal(t, 0, s.StructPtr.EmbeddedUnset.Int)
50+
require.Equal(t, "foo", s.StructPtr.Struct.String)
51+
require.Equal(t, 0, s.StructPtr.Struct.Int)
52+
53+
// slice
54+
require.Equal(t, 0, s.SliceOfInt[0])
55+
require.Equal(t, 0, *s.SliceOfIntPtr[0])
56+
require.Equal(t, (*int)(nil), *s.SliceOfIntPtrPtr[1])
57+
require.Equal(t, 0, s.SliceOfStruct[0].Foo)
58+
require.Equal(t, 0, s.SliceOfStructPtr[0].Foo)
59+
require.Equal(t, 0, s.SliceOfSliceInt[0][0])
60+
require.Equal(t, 0, *s.SliceOfSliceIntPtr[0][0])
61+
require.Equal(t, 0, s.SliceOfSliceStruct[0][0].Foo)
62+
require.Equal(t, 0, s.SliceOfMapOfInt[0]["int1"])
63+
require.Equal(t, 0, s.SliceOfMapOfStruct[0]["Struct3"].Foo)
64+
require.Equal(t, 0, s.SliceOfMapOfStructPtr[0]["Struct3"].Foo)
65+
require.Nil(t, s.SliceSetNil)
66+
67+
// map
68+
require.Equal(t, 0, s.MapOfInt["int1"])
69+
require.Equal(t, 0, *s.MapOfIntPtr["int1"])
70+
require.Equal(t, 0, s.MapOfStruct["Struct3"].Foo)
71+
require.Equal(t, 0, s.MapOfStructPtr["Struct3"].Foo)
72+
require.Equal(t, 0, s.MapOfSliceInt["slice1"][0])
73+
require.Equal(t, 0, *s.MapOfSliceIntPtr["slice1"][0])
74+
require.Equal(t, 0, s.MapOfSliceStruct["slice1"][0].Foo)
75+
require.Equal(t, 0, s.MapOfMapOfInt["map1"]["int1"])
76+
require.Equal(t, 0, s.MapOfMapOfInt["map1"]["int1"])
77+
require.Equal(t, 0, s.MapOfMapOfStruct["map1"]["Struct3"].Foo, 0)
78+
require.Nil(t, s.MapSetNil)
79+
80+
// map embed
81+
require.Equal(t, "foo", s.MapOfStruct["Struct3"].String)
82+
require.Equal(t, "foo", s.MapOfStructPtr["Struct3"].String)
83+
require.Equal(t, "foo", s.MapOfSliceStruct["slice1"][0].String)
84+
require.Equal(t, "foo", s.MapOfMapOfStruct["map1"]["Struct3"].String)
85+
86+
})
87+
}
88+
89+
type EmbeddedUnset struct {
90+
Int int `default:"1"`
91+
String string `default:"foo" unset:"-"`
92+
}
93+
94+
type StructUnset struct {
95+
EmbeddedUnset `default:"{}" unset:"walk"`
96+
Foo int `default:"1"`
97+
Bar int `default:"1"`
98+
BarPtr *int `default:"1"`
99+
BarPtrWithWalk *int `default:"1" unset:"walk"`
100+
WithDefault string `default:"foo"`
101+
Struct EmbeddedUnset ` unset:"walk"`
102+
}
103+
104+
type SampleUnset struct {
105+
private StructUnset `default:"{}" unset:"walk"`
106+
Struct StructUnset `default:"{}" unset:"walk"`
107+
StructPtr *StructUnset `default:"{}" unset:"walk"`
108+
StructPtrNoWalk *StructUnset `default:"{}"`
109+
110+
SliceOfInt []int `default:"[1,2,3]" unset:"walk"`
111+
SliceOfIntPtr []*int `default:"[1,2,3]" unset:"walk"`
112+
SliceOfIntPtrPtr []**int `default:"[1,2,3]" unset:"walk"`
113+
SliceOfStruct []StructUnset `default:"[{\"Foo\":123}]" unset:"walk"`
114+
SliceOfStructPtr []*StructUnset `default:"[{\"Foo\":123}]" unset:"walk"`
115+
SliceOfMapOfInt []map[string]int `default:"[{\"int1\": 1}]" unset:"walk"`
116+
SliceOfMapOfStruct []map[string]StructUnset `default:"[{\"Struct3\": {\"Foo\":123}}]" unset:"walk"`
117+
SliceOfMapOfStructPtr []map[string]*StructUnset `default:"[{\"Struct3\": {\"Foo\":123}}]" unset:"walk"`
118+
SliceOfSliceInt [][]int `default:"[[1,2,3]]" unset:"walk"`
119+
SliceOfSliceIntPtr [][]*int `default:"[[1,2,3]]" unset:"walk"`
120+
SliceOfSliceStruct [][]StructUnset `default:"[[{\"Foo\":123}]]" unset:"walk"`
121+
SliceOfSliceStructPtr [][]*StructUnset `default:"[[{\"Foo\":123}]]" unset:"walk"`
122+
SliceSetNil []StructUnset `default:"[{\"Foo\":123}]"`
123+
124+
MapOfInt map[string]int `default:"{\"int1\": 1}" unset:"walk"`
125+
MapOfIntPtr map[string]*int `default:"{\"int1\": 1}" unset:"walk"`
126+
MapOfStruct map[string]StructUnset `default:"{\"Struct3\": {\"Foo\":123}}" unset:"walk" `
127+
MapOfStructPtr map[string]*StructUnset `default:"{\"Struct3\": {\"Foo\":123}}" unset:"walk"`
128+
MapOfSliceInt map[string][]int `default:"{\"slice1\": [1,2,3]}" unset:"walk"`
129+
MapOfSliceIntPtr map[string][]*int `default:"{\"slice1\": [1,2,3]}" unset:"walk"`
130+
MapOfSliceStruct map[string][]StructUnset `default:"{\"slice1\": [{\"Foo\":123}]}" unset:"walk"`
131+
MapOfSliceStructPtr map[string][]*StructUnset `default:"{\"slice1\": [{\"Foo\":123}]}" unset:"walk"`
132+
MapOfMapOfInt map[string]map[string]int `default:"{\"map1\": {\"int1\": 1}}" unset:"walk"`
133+
MapOfMapOfStruct map[string]map[string]StructUnset `default:"{\"map1\": {\"Struct3\": {\"Foo\":123}}}" unset:"walk"`
134+
135+
MapSetNil map[string]StructUnset `default:"{\"Struct3\": {\"Foo\":123}}"`
136+
}

0 commit comments

Comments
 (0)