diff --git a/setter/setter.go b/setter/setter.go index 0e446a7..30e062a 100644 --- a/setter/setter.go +++ b/setter/setter.go @@ -53,7 +53,23 @@ func SetDefault(dest interface{}, defaultValue ...interface{}) { } } -func SetDefaultNew[T comparable](dest *T, defaultValues ...T) { +// Default assigns the first non-zero default value to `dest` +// if `dest`, itself, is the zero value of type T. +// To work with slices: use DefaultSlice +// +// var config struct { +// Verbose *bool +// Foo string +// Bar int +// } +// holster.Default(&config.Foo, "default") +// holster.Default(&config.Bar, 200) +// +// Supply additional default values and SetDefault will +// choose the first default that is not of zero value +// +// holster.SetDefault(&config.Foo, os.Getenv("FOO"), "default") +func Default[T comparable](dest *T, defaultValues ...T) { if IsZeroNew(*dest) { for _, value := range defaultValues { if !IsZeroNew(value) { @@ -64,6 +80,22 @@ func SetDefaultNew[T comparable](dest *T, defaultValues ...T) { } } +// DefaultSlice assigns the first non-empty default value to `dest` if +// `dest`, itself, is an empty slice. +func DefaultSlice[T comparable](dest *[]T, defaultValues ...[]T) { + if len(*dest) == 0 { + for _, value := range defaultValues { + if len(value) != 0 { + *dest = make([]T, len(value)) + copy(*dest, value) + return + } + } + } +} + +// IsZeroNew compares the given value to its Golang-specified zero value. +// It works for any type T that satisfies comparable. func IsZeroNew[T comparable](value T) bool { var zero T return value == zero diff --git a/setter/setter_test.go b/setter/setter_test.go index 770d2d3..f80a632 100644 --- a/setter/setter_test.go +++ b/setter/setter_test.go @@ -20,6 +20,7 @@ import ( "github.com/mailgun/holster/v4/setter" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIfEmpty(t *testing.T) { @@ -148,24 +149,46 @@ func TestIsNil(t *testing.T) { // --------------------------------------------------------- -var newRes string +var newStrRes string func BenchmarkSetterNew(b *testing.B) { var r string for i := 0; i < b.N; i++ { - setter.SetDefaultNew(&r, "", "", "42") + setter.Default(&r, "", "", "42") } - newRes = r + newStrRes = r } -var oldRes string +var oldStrRes string func BenchmarkSetter(b *testing.B) { var r string for i := 0; i < b.N; i++ { setter.SetDefault(&r, "", "", "42") } - oldRes = r + oldStrRes = r +} + +var newSliceRs []string + +func BenchmarkSetterNew_Slice(b *testing.B) { + r := make([]string, 0, 3) + b.ResetTimer() + for i := 0; i < b.N; i++ { + setter.DefaultSlice(&r, []string{}, []string{"welcome all", "to a benchmark", "of SILLY proportions"}) + } + newSliceRs = r +} + +var oldSliceRs []string + +func BenchmarkSetter_Slice(b *testing.B) { + r := make([]string, 0, 3) + b.ResetTimer() + for i := 0; i < b.N; i++ { + setter.SetDefault(&r, []string{""}, []string{"welcome all", "to a benchmark", "of SILLY proportions"}) + } + oldSliceRs = r } func TestSetterNew_IfEmpty(t *testing.T) { @@ -177,8 +200,8 @@ func TestSetterNew_IfEmpty(t *testing.T) { assert.Equal(t, 0, conf.Bar) // Should apply the default values - setter.SetDefaultNew(&conf.Foo, "default") - setter.SetDefaultNew(&conf.Bar, 200) + setter.Default(&conf.Foo, "default") + setter.Default(&conf.Bar, 200) assert.Equal(t, "default", conf.Foo) assert.Equal(t, 200, conf.Bar) @@ -187,13 +210,30 @@ func TestSetterNew_IfEmpty(t *testing.T) { conf.Bar = 500 // Should NOT apply the default values - setter.SetDefaultNew(&conf.Foo, "default") - setter.SetDefaultNew(&conf.Bar, 200) + setter.Default(&conf.Foo, "default") + setter.Default(&conf.Bar, 200) assert.Equal(t, "thrawn", conf.Foo) assert.Equal(t, 500, conf.Bar) } +func TestSetterNew_Slices(t *testing.T) { + var foo []string + require.Len(t, foo, 0) + + // Should apply the default values + setter.DefaultSlice(&foo, []string{"default"}) + require.Len(t, foo, 1) + assert.Equal(t, "default", foo[0]) + + foo = []string{"thrawn"} + + // Should NOT apply the default values + setter.DefaultSlice(&foo, []string{"default"}) + require.Len(t, foo, 1) + assert.Equal(t, "thrawn", foo[0]) +} + func TestSetterNew_IfDefaultPrecedence(t *testing.T) { var conf struct { Foo string @@ -204,12 +244,12 @@ func TestSetterNew_IfDefaultPrecedence(t *testing.T) { // Should use the final default value envValue := "" - setter.SetDefaultNew(&conf.Foo, envValue, "default") + setter.Default(&conf.Foo, envValue, "default") assert.Equal(t, "default", conf.Foo) // Should use envValue envValue = "bar" - setter.SetDefaultNew(&conf.Bar, envValue, "default") + setter.Default(&conf.Bar, envValue, "default") assert.Equal(t, "bar", conf.Bar) }