From 9724604bc6648a2295b2c8b489ea43783c75df1b Mon Sep 17 00:00:00 2001 From: Tom Kay Date: Mon, 19 May 2025 15:33:43 +0100 Subject: [PATCH 1/3] add Coalesce function --- strings.go | 11 +++++++++++ strings_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/strings.go b/strings.go index 45e6645..53fda75 100644 --- a/strings.go +++ b/strings.go @@ -3,6 +3,7 @@ package helpers import ( "io" "net/url" + "reflect" "regexp" "strings" @@ -32,6 +33,16 @@ func FirstStr(input ...string) string { return "" } +func Coalesce[T any](input ...T) T { + var check T + for _, check = range input { + if r := reflect.ValueOf(check); r.IsValid() && (r.Kind() != reflect.Pointer || !r.IsNil()) && !r.IsZero() { + return check + } + } + return check +} + func TruncateString(input string, length int) string { if len(input) > length { return input[:length] diff --git a/strings_test.go b/strings_test.go index 0027b01..6cb4d72 100644 --- a/strings_test.go +++ b/strings_test.go @@ -64,6 +64,31 @@ func TestFirstStr(t *testing.T) { } } +func TestCoalesce(t *testing.T) { + tests := []struct { + input []any + output any + }{ + {[]any{"", "", "hello"}, "hello"}, + {[]any{"", "world", ""}, "world"}, + {[]any{"", "", ""}, ""}, + {[]any{0, 0, 1}, 1}, + {[]any{0, 2, 1}, 2}, + {[]any{0, 0, 0}, 0}, + {[]any{nil, nil, "hello"}, "hello"}, + {[]any{nil, "world", nil}, "world"}, + {[]any{nil, "", nil}, nil}, + {[]any{nil, nil, nil}, nil}, + {[]any{nil, 2, nil}, 2}, + {[]any{nil, 0, nil}, nil}, + } + + for _, test := range tests { + result := Coalesce(test.input...) + assert.Equal(t, test.output, result) + } +} + func TestQueryEscape(t *testing.T) { result := QueryEscape("a key", "a value") if result != "a+key=a+value" { From a12dd627f5ad9ff3e5002b4a8be8e644f16d8dc1 Mon Sep 17 00:00:00 2001 From: Tom Kay Date: Tue, 2 Dec 2025 11:04:07 +0000 Subject: [PATCH 2/3] shift isZero --- strings.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/strings.go b/strings.go index 53fda75..d857f25 100644 --- a/strings.go +++ b/strings.go @@ -25,24 +25,27 @@ func BufferToString(buf io.Reader) string { } func FirstStr(input ...string) string { - for _, s := range input { - if s != "" { - return s - } - } - return "" + return Coalesce(input...) } func Coalesce[T any](input ...T) T { var check T for _, check = range input { - if r := reflect.ValueOf(check); r.IsValid() && (r.Kind() != reflect.Pointer || !r.IsNil()) && !r.IsZero() { + if !IsZero(check) { return check } } return check } +func IsZero(input any) bool { + if input == nil { + return true + } + r := reflect.ValueOf(input) + return !r.IsValid() || (r.Kind() == reflect.Pointer && r.IsNil()) || r.IsZero() +} + func TruncateString(input string, length int) string { if len(input) > length { return input[:length] From 4495bbe1750fa3736039881af421cc6ffe86490c Mon Sep 17 00:00:00 2001 From: Tom Kay Date: Tue, 2 Dec 2025 11:09:20 +0000 Subject: [PATCH 3/3] move coalesce to generic --- generic.go | 20 ++++++++++++++++++++ generic_test.go | 25 +++++++++++++++++++++++++ strings.go | 19 ------------------- strings_test.go | 25 ------------------------- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/generic.go b/generic.go index 33715fb..7ae99fe 100644 --- a/generic.go +++ b/generic.go @@ -1,5 +1,7 @@ package helpers +import "reflect" + func Ref[T any](in T) *T { return &in } @@ -30,3 +32,21 @@ func If[T any](cond bool, vtrue, vfalse T) T { } return vfalse } + +func Coalesce[T any](input ...T) T { + var check T + for _, check = range input { + if !IsZero(check) { + return check + } + } + return check +} + +func IsZero(input any) bool { + if input == nil { + return true + } + r := reflect.ValueOf(input) + return !r.IsValid() || (r.Kind() == reflect.Pointer && r.IsNil()) || r.IsZero() +} diff --git a/generic_test.go b/generic_test.go index 85a22df..ce4d1c7 100644 --- a/generic_test.go +++ b/generic_test.go @@ -23,3 +23,28 @@ func TestIf(t *testing.T) { assert.Equal(t, test.output, result) } } + +func TestCoalesce(t *testing.T) { + tests := []struct { + input []any + output any + }{ + {[]any{"", "", "hello"}, "hello"}, + {[]any{"", "world", ""}, "world"}, + {[]any{"", "", ""}, ""}, + {[]any{0, 0, 1}, 1}, + {[]any{0, 2, 1}, 2}, + {[]any{0, 0, 0}, 0}, + {[]any{nil, nil, "hello"}, "hello"}, + {[]any{nil, "world", nil}, "world"}, + {[]any{nil, "", nil}, nil}, + {[]any{nil, nil, nil}, nil}, + {[]any{nil, 2, nil}, 2}, + {[]any{nil, 0, nil}, nil}, + } + + for _, test := range tests { + result := Coalesce(test.input...) + assert.Equal(t, test.output, result) + } +} diff --git a/strings.go b/strings.go index d857f25..63d1936 100644 --- a/strings.go +++ b/strings.go @@ -3,7 +3,6 @@ package helpers import ( "io" "net/url" - "reflect" "regexp" "strings" @@ -28,24 +27,6 @@ func FirstStr(input ...string) string { return Coalesce(input...) } -func Coalesce[T any](input ...T) T { - var check T - for _, check = range input { - if !IsZero(check) { - return check - } - } - return check -} - -func IsZero(input any) bool { - if input == nil { - return true - } - r := reflect.ValueOf(input) - return !r.IsValid() || (r.Kind() == reflect.Pointer && r.IsNil()) || r.IsZero() -} - func TruncateString(input string, length int) string { if len(input) > length { return input[:length] diff --git a/strings_test.go b/strings_test.go index 6cb4d72..0027b01 100644 --- a/strings_test.go +++ b/strings_test.go @@ -64,31 +64,6 @@ func TestFirstStr(t *testing.T) { } } -func TestCoalesce(t *testing.T) { - tests := []struct { - input []any - output any - }{ - {[]any{"", "", "hello"}, "hello"}, - {[]any{"", "world", ""}, "world"}, - {[]any{"", "", ""}, ""}, - {[]any{0, 0, 1}, 1}, - {[]any{0, 2, 1}, 2}, - {[]any{0, 0, 0}, 0}, - {[]any{nil, nil, "hello"}, "hello"}, - {[]any{nil, "world", nil}, "world"}, - {[]any{nil, "", nil}, nil}, - {[]any{nil, nil, nil}, nil}, - {[]any{nil, 2, nil}, 2}, - {[]any{nil, 0, nil}, nil}, - } - - for _, test := range tests { - result := Coalesce(test.input...) - assert.Equal(t, test.output, result) - } -} - func TestQueryEscape(t *testing.T) { result := QueryEscape("a key", "a value") if result != "a+key=a+value" {