From dd8f762cf06ec4a462d154bd02487aa8950887e6 Mon Sep 17 00:00:00 2001 From: Matthew Edge Date: Wed, 18 Sep 2024 12:03:45 -0400 Subject: [PATCH] Proposal for generics-based Setter --- setter/setter.go | 15 ++++++ setter/setter_test.go | 109 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/setter/setter.go b/setter/setter.go index b643ad0..fee658f 100644 --- a/setter/setter.go +++ b/setter/setter.go @@ -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. diff --git a/setter/setter_test.go b/setter/setter_test.go index c01aac8..770d2d3 100644 --- a/setter/setter_test.go +++ b/setter/setter_test.go @@ -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") +// }