From 6fcf9cd2390965211e617d5a3f11eb88209f38bf Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 25 Mar 2024 12:37:01 -0300 Subject: [PATCH 1/3] feat: Add IfToLower and IfToUpper functions for converting ASCII strings to lowercase and uppercase respectively --- strings.go | 58 +++++++++++++++++++++++++++++++++++++++++++++ strings_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/strings.go b/strings.go index 0436f03..2165898 100644 --- a/strings.go +++ b/strings.go @@ -68,3 +68,61 @@ func DefaultString(value string, defaultValue string) string { } return value } + +// IfToUpper returns an lowercase version of the input ASCII string. +// +// It first checks if the string contains any uppercase characters before converting it. +// +// For strings that are already lowercase,this function will be faster than `ToLower`. +// +// In the case of mixed-case or uppercase strings, this function will be slightly slower than `ToLower`. +func IfToLower(s string) string { + hasUpper := false + for i := 0; i < len(s); i++ { + c := s[i] + if toLowerTable[c] != c { + hasUpper = true + break + } + } + + if !hasUpper { + return s + } + res := make([]byte, len(s)) + copy(res, s) + for i := 0; i < len(res); i++ { + res[i] = toLowerTable[res[i]] + } + + return UnsafeString(res) +} + +// IfToUpper returns an uppercase version of the input ASCII string. +// +// It first checks if the string contains any lowercase characters before converting it. +// +// For strings that are already uppercase,this function will be faster than `ToUpper`. +// +// In the case of mixed-case or lowercase strings, this function will be slightly slower than `ToUpper`. +func IfToUpper(s string) string { + hasLower := false + for i := 0; i < len(s); i++ { + c := s[i] + if toUpperTable[c] != c { + hasLower = true + break + } + } + + if !hasLower { + return s + } + res := make([]byte, len(s)) + copy(res, s) + for i := 0; i < len(res); i++ { + res[i] = toUpperTable[res[i]] + } + + return UnsafeString(res) +} diff --git a/strings_test.go b/strings_test.go index c65142b..a30bbff 100644 --- a/strings_test.go +++ b/strings_test.go @@ -25,6 +25,18 @@ func Benchmark_ToUpper(b *testing.B) { } AssertEqual(b, "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS", res) }) + b.Run("IfToUpper-Upper", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToUpper(upperStr) + } + require.Equal(b, upperStr, res) + }) + b.Run("IfToUpper-Mixed", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToUpper(largeStr) + } + require.Equal(b, upperStr, res) + }) b.Run("default", func(b *testing.B) { for n := 0; n < b.N; n++ { res = strings.ToUpper(path) @@ -56,6 +68,18 @@ func Benchmark_ToLower(b *testing.B) { } AssertEqual(b, "/repos/gofiber/fiber/issues/187643/comments", res) }) + b.Run("IfToLower-Lower", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower(lowerStr) + } + require.Equal(b, lowerStr, res) + }) + b.Run("IfToLower-Mixed", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower(largeStr) + } + require.Equal(b, lowerStr, res) + }) b.Run("default", func(b *testing.B) { for n := 0; n < b.N; n++ { res = strings.ToLower(path) @@ -147,3 +171,42 @@ func Benchmark_Trim(b *testing.B) { AssertEqual(b, "foobar", res) }) } + +func Test_IfToUpper(t *testing.T) { + t.Parallel() + require.Equal(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase + require.Equal(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase + require.Equal(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case +} + +func Test_IfToLower(t *testing.T) { + t.Parallel() + require.Equal(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase + require.Equal(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case + require.Equal(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL + require.Equal(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase +} + +// Benchmark_IfToLower_HeadersOrigin benchmarks the IfToLower function with an origin header type URL. +// These headers are typically lowercase, so the function should return the input string without modification. +func Benchmark_IfToToLower_HeadersOrigin(b *testing.B) { + var res string + b.Run("fiber", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = ToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) + b.Run("IfToLower-Lower", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = IfToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) + b.Run("default", func(b *testing.B) { + for n := 0; n < b.N; n++ { + res = strings.ToLower("https://gofiber.io") + } + require.Equal(b, "https://gofiber.io", res) + }) +} From 5a92a79d04aa7d8e1d4bbd3f3820cdbcb34cf19f Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 25 Mar 2024 13:06:15 -0300 Subject: [PATCH 2/3] refactor: DRY IfToLower and IfToUpper --- strings.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/strings.go b/strings.go index 2165898..bf59f9a 100644 --- a/strings.go +++ b/strings.go @@ -89,13 +89,7 @@ func IfToLower(s string) string { if !hasUpper { return s } - res := make([]byte, len(s)) - copy(res, s) - for i := 0; i < len(res); i++ { - res[i] = toLowerTable[res[i]] - } - - return UnsafeString(res) + return ToLower(s) } // IfToUpper returns an uppercase version of the input ASCII string. @@ -118,11 +112,5 @@ func IfToUpper(s string) string { if !hasLower { return s } - res := make([]byte, len(s)) - copy(res, s) - for i := 0; i < len(res); i++ { - res[i] = toUpperTable[res[i]] - } - - return UnsafeString(res) + return ToUpper(s) } From f0cce84d688a8f7a0dcc4c0df789f9a94e1bc258 Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 25 Mar 2024 13:18:04 -0300 Subject: [PATCH 3/3] feat: Backport IfToLower and IfToUpper functions from v2 --- strings_test.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/strings_test.go b/strings_test.go index a30bbff..5b0fece 100644 --- a/strings_test.go +++ b/strings_test.go @@ -27,15 +27,15 @@ func Benchmark_ToUpper(b *testing.B) { }) b.Run("IfToUpper-Upper", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = IfToUpper(upperStr) + res = IfToUpper("/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS") } - require.Equal(b, upperStr, res) + AssertEqual(b, "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS", res) }) b.Run("IfToUpper-Mixed", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = IfToUpper(largeStr) + res = IfToUpper(path) } - require.Equal(b, upperStr, res) + AssertEqual(b, "/REPOS/GOFIBER/FIBER/ISSUES/187643/COMMENTS", res) }) b.Run("default", func(b *testing.B) { for n := 0; n < b.N; n++ { @@ -70,15 +70,15 @@ func Benchmark_ToLower(b *testing.B) { }) b.Run("IfToLower-Lower", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = IfToLower(lowerStr) + res = IfToLower("/repos/gofiber/fiber/issues/187643/comments") } - require.Equal(b, lowerStr, res) + AssertEqual(b, "/repos/gofiber/fiber/issues/187643/comments", res) }) b.Run("IfToLower-Mixed", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = IfToLower(largeStr) + res = IfToLower(path) } - require.Equal(b, lowerStr, res) + AssertEqual(b, "/repos/gofiber/fiber/issues/187643/comments", res) }) b.Run("default", func(b *testing.B) { for n := 0; n < b.N; n++ { @@ -174,17 +174,17 @@ func Benchmark_Trim(b *testing.B) { func Test_IfToUpper(t *testing.T) { t.Parallel() - require.Equal(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase - require.Equal(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase - require.Equal(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case + AssertEqual(t, "MYNAMEISPARAM", IfToUpper("MYNAMEISPARAM")) // already uppercase + AssertEqual(t, "MYNAMEISPARAM", IfToUpper("mynameisparam")) // lowercase to uppercase + AssertEqual(t, "MYNAMEISPARAM", IfToUpper("MyNameIsParam")) // mixed case } func Test_IfToLower(t *testing.T) { t.Parallel() - require.Equal(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase - require.Equal(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case - require.Equal(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL - require.Equal(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase + AssertEqual(t, "mynameisparam", IfToLower("mynameisparam")) // already lowercase + AssertEqual(t, "mynameisparam", IfToLower("myNameIsParam")) // mixed case + AssertEqual(t, "https://gofiber.io", IfToLower("https://gofiber.io")) // Origin Header Type URL + AssertEqual(t, "mynameisparam", IfToLower("MYNAMEISPARAM")) // uppercase to lowercase } // Benchmark_IfToLower_HeadersOrigin benchmarks the IfToLower function with an origin header type URL. @@ -195,18 +195,18 @@ func Benchmark_IfToToLower_HeadersOrigin(b *testing.B) { for n := 0; n < b.N; n++ { res = ToLower("https://gofiber.io") } - require.Equal(b, "https://gofiber.io", res) + AssertEqual(b, "https://gofiber.io", res) }) b.Run("IfToLower-Lower", func(b *testing.B) { for n := 0; n < b.N; n++ { res = IfToLower("https://gofiber.io") } - require.Equal(b, "https://gofiber.io", res) + AssertEqual(b, "https://gofiber.io", res) }) b.Run("default", func(b *testing.B) { for n := 0; n < b.N; n++ { res = strings.ToLower("https://gofiber.io") } - require.Equal(b, "https://gofiber.io", res) + AssertEqual(b, "https://gofiber.io", res) }) }