From 3717ffbb212199476ddeeefef43bace35a1df96f Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 25 Mar 2024 12:37:01 -0300 Subject: [PATCH 1/2] 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 364c25f..c47d0bc 100644 --- a/strings.go +++ b/strings.go @@ -25,3 +25,61 @@ func ToUpper(b string) string { return UnsafeString(res) } + +// 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 37abc96..f837b2d 100644 --- a/strings_test.go +++ b/strings_test.go @@ -30,6 +30,18 @@ func Benchmark_ToUpper(b *testing.B) { } require.Equal(b, upperStr, 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(largeStr) @@ -55,6 +67,18 @@ func Benchmark_ToLower(b *testing.B) { } require.Equal(b, lowerStr, 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(largeStr) @@ -62,3 +86,42 @@ func Benchmark_ToLower(b *testing.B) { require.Equal(b, lowerStr, 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 edefd68a320ac6903deb2012ebb5568f3cfd44dd Mon Sep 17 00:00:00 2001 From: Jason McNeil Date: Mon, 25 Mar 2024 13:06:15 -0300 Subject: [PATCH 2/2] refactor: DRY IfToLower and IfToUpper --- strings.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/strings.go b/strings.go index c47d0bc..47af92d 100644 --- a/strings.go +++ b/strings.go @@ -46,13 +46,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. @@ -75,11 +69,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) }