Skip to content

Commit

Permalink
feat(unset): support unset value to zero. (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
zonewave committed Apr 20, 2023
1 parent 5220e08 commit 7367b28
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/creasty/defaults

go 1.14

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

import (
"reflect"
)

const (
unsetFlag = "unset"
// unsetRecursion means that the field will be unset recursively
unsetRecursion = "walk"
)

func MustUnset(ptr interface{}) {
if err := Unset(ptr); err != nil {
panic(err)
}
}
func Unset(ptr interface{}) error {
if reflect.TypeOf(ptr).Kind() != reflect.Ptr {
return errInvalidType
}

v := reflect.ValueOf(ptr).Elem()
t := v.Type()

if t.Kind() != reflect.Struct {
return errInvalidType
}

for i := 0; i < t.NumField(); i++ {
unset := t.Field(i).Tag.Get(unsetFlag)
if unset == "-" {
continue
}
if err := unsetField(v.Field(i), unset == unsetRecursion); err != nil {
return err
}
}
return nil
}

func unsetField(field reflect.Value, unsetWalk bool) error {
if !field.CanSet() {
return nil
}

isInitial := isInitialValue(field)
if isInitial {
return nil
}
if !unsetWalk {
field.Set(reflect.Zero(field.Type()))
return nil
}

switch field.Kind() {
default:
field.Set(reflect.Zero(field.Type()))
case reflect.Ptr:
if field.Elem().Kind() == reflect.Struct {
if err := Unset(field.Interface()); err != nil {
return err
}
} else {
field.Elem().Set(reflect.Zero(field.Elem().Type()))
}
case reflect.Struct:
if err := Unset(field.Addr().Interface()); err != nil {
return err
}
case reflect.Slice:
for j := 0; j < field.Len(); j++ {
if err := unsetField(field.Index(j), true); err != nil {
return err
}
}
case reflect.Map:
for _, e := range field.MapKeys() {
var v = field.MapIndex(e)

switch v.Kind() {
case reflect.Ptr:
switch v.Elem().Kind() {
case reflect.Struct, reflect.Slice, reflect.Map:
if err := unsetField(v.Elem(), true); err != nil {
return err
}
}
case reflect.Struct, reflect.Slice, reflect.Map:
ref := reflect.New(v.Type())
ref.Elem().Set(v)
if err := unsetField(ref.Elem(), true); err != nil {
return err
}
field.SetMapIndex(e, ref.Elem().Convert(v.Type()))
default:
field.Set(reflect.Zero(field.Type()))
}
}
}
return nil
}
93 changes: 93 additions & 0 deletions unset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package defaults

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestUnset(t *testing.T) {
t.Run("sample unset", func(t *testing.T) {
s := &Sample{}
MustSet(s)
err := Unset(s)
require.NoError(t, err)
require.Equal(t, s, &Sample{})
})

t.Run("errInvalidType", func(t *testing.T) {
require.Equal(t, Unset(struct{}{}), errInvalidType)

tmp := 8
require.Equal(t, Unset(&tmp), errInvalidType)
require.Panics(t, func() { MustUnset(struct{}{}) })
})

t.Run("not reset by -", func(t *testing.T) {
s := &struct {
IgnoreMe string `default:"-" unset:"-"`
}{
IgnoreMe: "test",
}
MustUnset(s)
require.Equal(t, s.IgnoreMe, "test")
})

t.Run("sampleUnset test", func(t *testing.T) {
s := &SampleUnset{}
MustSet(s)
MustUnset(s)

// struct
require.Equal(t, s.Struct.Foo, 0)
require.Equal(t, s.Struct.Bar, 0)
require.Nil(t, s.Struct.BarPtr)
require.Equal(t, *s.Struct.BarPtrWithWalk, 0)

require.Empty(t, s.StructPtrNoWalk)
require.Equal(t, s.StructPtr.EmbeddedUnset.String, "foo")

// map
require.Equal(t, s.MapOfInt["int1"], 0)
require.Equal(t, s.MapOfStruct["Struct3"].Foo, 0)
require.Equal(t, s.MapOfStructPtr["Struct3"].Foo, 0)
require.Equal(t, s.MapOfSliceInt["slice1"][0], 0)
require.Equal(t, s.MapOfSliceStruct["slice1"][0].Foo, 0)
require.Equal(t, s.MapOfMapOfStruct["map1"]["Struct3"].Foo, 0)
require.Nil(t, s.MapSetNil)

// map embed
require.Equal(t, s.MapOfStruct["Struct3"].String, "foo")
require.Equal(t, s.MapOfStructPtr["Struct3"].String, "foo")
require.Equal(t, s.MapOfSliceStruct["slice1"][0].String, "foo")
require.Equal(t, s.MapOfMapOfStruct["map1"]["Struct3"].String, "foo")

})
}

type EmbeddedUnset struct {
Int int `default:"1"`
String string `default:"foo" unset:"-"`
}
type StructUnset struct {
EmbeddedUnset `default:"{}" unset:"walk"`
Foo int `default:"1"`
Bar int `default:"1"`
BarPtr *int `default:"1"`
BarPtrWithWalk *int `default:"1" unset:"walk"`
WithDefault string `default:"foo"`
}

type SampleUnset struct {
Struct StructUnset `default:"{}" unset:"walk"`
StructPtr *StructUnset `default:"{}" unset:"walk"`
StructPtrNoWalk *StructUnset `default:"{}"`

MapOfInt map[string]int `default:"{\"int1\": 1}" unset:"walk"`
MapOfStruct map[string]StructUnset `default:"{\"Struct3\": {\"Foo\":123}}" unset:"walk" `
MapOfStructPtr map[string]*StructUnset `default:"{\"Struct3\": {\"Foo\":123}}" unset:"walk"`
MapOfSliceInt map[string][]int `default:"{\"slice1\": [1,2,3]}" unset:"walk"`
MapOfSliceStruct map[string][]StructUnset `default:"{\"slice1\": [{\"Foo\":123}]}" unset:"walk"`
MapOfMapOfStruct map[string]map[string]StructUnset `default:"{\"map1\": {\"Struct3\": {\"Foo\":123}}}" unset:"walk"`

MapSetNil map[string]StructUnset `default:"{\"Struct3\": {\"Foo\":123}}"`
}

0 comments on commit 7367b28

Please sign in to comment.