Skip to content

Commit

Permalink
Proposal for generics-based Setter
Browse files Browse the repository at this point in the history
  • Loading branch information
medge-mailgun committed Sep 18, 2024
1 parent c6a54bc commit dd8f762
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
15 changes: 15 additions & 0 deletions setter/setter.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ func SetDefault(dest interface{}, defaultValue ...interface{}) {
}
}

func SetDefaultNew[T comparable](dest *T, defaultValues ...T) {
if IsZeroNew(*dest) {
for _, value := range defaultValues {
if !IsZeroNew(value) {
*dest = value
}
}
}
}

func IsZeroNew[T comparable](value T) bool {
var zero T
return value == zero
}

// Assign the first value that is not empty or of zero value.
// This panics if the value is not a pointer or if value and
// default value are not of the same type.
Expand Down
109 changes: 109 additions & 0 deletions setter/setter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,112 @@ func TestIsNil(t *testing.T) {
assert.True(t, setter.IsNil(thing))
assert.False(t, setter.IsNil(&MyImplementation{}))
}

// ---------------------------------------------------------

var newRes string

func BenchmarkSetterNew(b *testing.B) {
var r string
for i := 0; i < b.N; i++ {
setter.SetDefaultNew(&r, "", "", "42")
}
newRes = r
}

var oldRes string

func BenchmarkSetter(b *testing.B) {
var r string
for i := 0; i < b.N; i++ {
setter.SetDefault(&r, "", "", "42")
}
oldRes = r
}

func TestSetterNew_IfEmpty(t *testing.T) {
var conf struct {
Foo string
Bar int
}
assert.Equal(t, "", conf.Foo)
assert.Equal(t, 0, conf.Bar)

// Should apply the default values
setter.SetDefaultNew(&conf.Foo, "default")
setter.SetDefaultNew(&conf.Bar, 200)

assert.Equal(t, "default", conf.Foo)
assert.Equal(t, 200, conf.Bar)

conf.Foo = "thrawn"
conf.Bar = 500

// Should NOT apply the default values
setter.SetDefaultNew(&conf.Foo, "default")
setter.SetDefaultNew(&conf.Bar, 200)

assert.Equal(t, "thrawn", conf.Foo)
assert.Equal(t, 500, conf.Bar)
}

func TestSetterNew_IfDefaultPrecedence(t *testing.T) {
var conf struct {
Foo string
Bar string
}
assert.Equal(t, "", conf.Foo)
assert.Equal(t, "", conf.Bar)

// Should use the final default value
envValue := ""
setter.SetDefaultNew(&conf.Foo, envValue, "default")
assert.Equal(t, "default", conf.Foo)

// Should use envValue
envValue = "bar"
setter.SetDefaultNew(&conf.Bar, envValue, "default")
assert.Equal(t, "bar", conf.Bar)
}

func TestSetterNew_IsEmpty(t *testing.T) {
var count64 int64
var thing string

// Should return true
assert.Equal(t, true, setter.IsZeroNew(count64))
assert.Equal(t, true, setter.IsZeroNew(thing))

thing = "thrawn"
count64 = int64(1)
assert.Equal(t, false, setter.IsZeroNew(count64))
assert.Equal(t, false, setter.IsZeroNew(thing))
}

// Not possible now given compiler warnings
// func TestSetterNew_IfEmptyTypePanic(t *testing.T) {
// defer func() {
// if r := recover(); r != nil {
// assert.Equal(t, "reflect.Set: value of type int is not assignable to type string", r)
// }
// }()

// var thing string
// // Should panic
// setter.SetDefaultNew(&thing, 1)
// assert.Fail(t, "Should have caught panic")
// }

// Not possible now given argument is now a pointer to T
// func TestSetterNew_IfEmptyNonPtrPanic(t *testing.T) {
// defer func() {
// if r := recover(); r != nil {
// assert.Equal(t, "setter.SetDefault: Expected first argument to be of type reflect.Ptr", r)
// }
// }()

// var thing string
// // Should panic
// setter.SetDefaultNew(thing, "thrawn")
// assert.Fail(t, "Should have caught panic")
// }

0 comments on commit dd8f762

Please sign in to comment.