diff --git a/ctx.go b/ctx.go index 5849501c260..a7b47c925fd 100644 --- a/ctx.go +++ b/ctx.go @@ -644,9 +644,14 @@ func (c *DefaultCtx) Get(key string, defaultValue ...string) string { // GetReqHeader returns the HTTP request header specified by filed. // This function is generic and can handle different headers type values. +// If the generic type cannot be matched to a supported type, the function +// returns the default value (if provided) or the zero value of type V. func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V { - var v V - return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...) + v, err := genericParseType[V](c.App().getString(c.Request().Header.Peek(key))) + if err != nil && len(defaultValue) > 0 { + return defaultValue[0] + } + return v } // GetRespHeader returns the HTTP response header specified by field. @@ -1103,6 +1108,8 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { // Params is used to get the route parameters. // This function is generic and can handle different route parameters type values. +// If the generic type cannot be matched to a supported type, the function +// returns the default value (if provided) or the zero value of type V. // // Example: // @@ -1115,8 +1122,11 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { // http://example.com/id/:number -> http://example.com/id/john // Params[int](c, "number", 0) -> returns 0 because can't parse 'john' as integer. func Params[V GenericType](c Ctx, key string, defaultValue ...V) V { - var v V - return genericParseType(c.Params(key), v, defaultValue...) + v, err := genericParseType[V](c.Params(key)) + if err != nil && len(defaultValue) > 0 { + return defaultValue[0] + } + return v } // Path returns the path part of the request URL. @@ -1238,10 +1248,12 @@ func (c *DefaultCtx) Queries() map[string]string { // age := Query[int](c, "age") // Returns 8 // unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found func Query[V GenericType](c Ctx, key string, defaultValue ...V) V { - var v V q := c.App().getString(c.RequestCtx().QueryArgs().Peek(key)) - - return genericParseType[V](q, v, defaultValue...) + v, err := genericParseType[V](q) + if err != nil && len(defaultValue) > 0 { + return defaultValue[0] + } + return v } // Range returns a struct containing the type and a slice of ranges. diff --git a/ctx_test.go b/ctx_test.go index e314f81d32b..044f2826b5d 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -17,7 +17,6 @@ import ( "errors" "fmt" "io" - "math" "mime/multipart" "net" "net/http/httptest" @@ -2494,6 +2493,7 @@ func Test_Ctx_Params(t *testing.T) { }) app.Get("/test4/:optional?", func(c Ctx) error { require.Equal(t, "", c.Params("optional")) + require.Equal(t, "default", Params(c, "optional", "default")) return nil }) app.Get("/test5/:id/:Id", func(c Ctx) error { @@ -5250,762 +5250,118 @@ func Benchmark_Ctx_GetReqHeaders(b *testing.B) { }, headers) } -// go test -run Test_GenericParseTypeInts -func Test_GenericParseTypeInts(t *testing.T) { - t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } - - ints := []genericTypes[int]{ - { - value: 0, - str: "0", - }, - { - value: 1, - str: "1", - }, - { - value: 2, - str: "2", - }, - { - value: 3, - str: "3", - }, - { - value: 4, - str: "4", - }, - { - value: 2147483647, - str: "2147483647", - }, - { - value: -2147483648, - str: "-2147483648", - }, - { - value: -1, - str: "-1", - }, - } - - for _, test := range ints { - var v int - tt := test - t.Run("test_genericParseTypeInts", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[int](tt.str, v)) - }) - } -} - -// go test -run Test_GenericParseTypeInt8s -func Test_GenericParseTypeInt8s(t *testing.T) { +// go test -run Test_Ctx_Drop -v +func Test_Ctx_Drop(t *testing.T) { t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } - - int8s := []genericTypes[int8]{ - { - value: int8(0), - str: "0", - }, - { - value: int8(1), - str: "1", - }, - { - value: int8(2), - str: "2", - }, - { - value: int8(3), - str: "3", - }, - { - value: int8(4), - str: "4", - }, - { - value: int8(math.MaxInt8), - str: strconv.Itoa(math.MaxInt8), - }, - { - value: int8(math.MinInt8), - str: strconv.Itoa(math.MinInt8), - }, - } + app := New() - for _, test := range int8s { - var v int8 - tt := test - t.Run("test_genericParseTypeInt8s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[int8](tt.str, v)) - }) - } -} + // Handler that calls Drop + app.Get("/block-me", func(c Ctx) error { + return c.Drop() + }) -// go test -run Test_GenericParseTypeInt16s -func Test_GenericParseTypeInt16s(t *testing.T) { - t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } + // Additional handler that just calls return + app.Get("/no-response", func(_ Ctx) error { + return nil + }) - int16s := []genericTypes[int16]{ - { - value: int16(0), - str: "0", - }, - { - value: int16(1), - str: "1", - }, - { - value: int16(2), - str: "2", - }, - { - value: int16(3), - str: "3", - }, - { - value: int16(4), - str: "4", - }, - { - value: int16(math.MaxInt16), - str: strconv.Itoa(math.MaxInt16), - }, - { - value: int16(math.MinInt16), - str: strconv.Itoa(math.MinInt16), - }, - } + // Test the Drop method + resp, err := app.Test(httptest.NewRequest(MethodGet, "/block-me", nil)) + require.ErrorIs(t, err, ErrTestGotEmptyResponse) + require.Nil(t, resp) - for _, test := range int16s { - var v int16 - tt := test - t.Run("test_genericParseTypeInt16s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[int16](tt.str, v)) - }) - } + // Test the no-response handler + resp, err = app.Test(httptest.NewRequest(MethodGet, "/no-response", nil)) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, StatusOK, resp.StatusCode) + require.Equal(t, "0", resp.Header.Get("Content-Length")) } -// go test -run Test_GenericParseTypeInt32s -func Test_GenericParseTypeInt32s(t *testing.T) { +// go test -run Test_Ctx_DropWithMiddleware -v +func Test_Ctx_DropWithMiddleware(t *testing.T) { t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } - - int32s := []genericTypes[int32]{ - { - value: int32(0), - str: "0", - }, - { - value: int32(1), - str: "1", - }, - { - value: int32(2), - str: "2", - }, - { - value: int32(3), - str: "3", - }, - { - value: int32(4), - str: "4", - }, - { - value: int32(math.MaxInt32), - str: strconv.Itoa(math.MaxInt32), - }, - { - value: int32(math.MinInt32), - str: strconv.Itoa(math.MinInt32), - }, - } - for _, test := range int32s { - var v int32 - tt := test - t.Run("test_genericParseTypeInt32s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[int32](tt.str, v)) - }) - } -} + app := New() -// go test -run Test_GenericParseTypeInt64s -func Test_GenericParseTypeInt64s(t *testing.T) { - t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } + // Middleware that calls Drop + app.Use(func(c Ctx) error { + err := c.Next() + c.Set("X-Test", "test") + return err + }) - int64s := []genericTypes[int64]{ - { - value: int64(0), - str: "0", - }, - { - value: int64(1), - str: "1", - }, - { - value: int64(2), - str: "2", - }, - { - value: int64(3), - str: "3", - }, - { - value: int64(4), - str: "4", - }, - { - value: int64(math.MaxInt64), - str: strconv.Itoa(math.MaxInt64), - }, - { - value: int64(math.MinInt64), - str: strconv.Itoa(math.MinInt64), - }, - } + // Handler that calls Drop + app.Get("/block-me", func(c Ctx) error { + return c.Drop() + }) - for _, test := range int64s { - var v int64 - tt := test - t.Run("test_genericParseTypeInt64s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[int64](tt.str, v)) - }) - } + // Test the Drop method + resp, err := app.Test(httptest.NewRequest(MethodGet, "/block-me", nil)) + require.ErrorIs(t, err, ErrTestGotEmptyResponse) + require.Nil(t, resp) } -// go test -run Test_GenericParseTypeUints -func Test_GenericParseTypeUints(t *testing.T) { - t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } +// go test -run Test_Ctx_End +func Test_Ctx_End(t *testing.T) { + app := New() - uints := []genericTypes[uint]{ - { - value: uint(0), - str: "0", - }, - { - value: uint(1), - str: "1", - }, - { - value: uint(2), - str: "2", - }, - { - value: uint(3), - str: "3", - }, - { - value: uint(4), - str: "4", - }, - { - value: ^uint(0), - str: strconv.FormatUint(uint64(^uint(0)), 10), - }, - } + app.Get("/", func(c Ctx) error { + c.SendString("Hello, World!") //nolint:errcheck // unnecessary to check error + return c.End() + }) - for _, test := range uints { - var v uint - tt := test - t.Run("test_genericParseTypeUints", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[uint](tt.str, v)) - }) - } + resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, StatusOK, resp.StatusCode) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err, "io.ReadAll(resp.Body)") + require.Equal(t, "Hello, World!", string(body)) } -// go test -run Test_GenericParseTypeUints -func Test_GenericParseTypeUint8s(t *testing.T) { - t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } +// go test -run Test_Ctx_End_after_timeout +func Test_Ctx_End_after_timeout(t *testing.T) { + app := New() - uint8s := []genericTypes[uint8]{ - { - value: uint8(0), - str: "0", - }, - { - value: uint8(1), - str: "1", - }, - { - value: uint8(2), - str: "2", - }, - { - value: uint8(3), - str: "3", - }, - { - value: uint8(4), - str: "4", - }, - { - value: uint8(math.MaxUint8), - str: strconv.Itoa(math.MaxUint8), - }, - } + // Early flushing handler + app.Get("/", func(c Ctx) error { + time.Sleep(2 * time.Second) + return c.End() + }) - for _, test := range uint8s { - var v uint8 - tt := test - t.Run("test_genericParseTypeUint8s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[uint8](tt.str, v)) - }) - } + resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) + require.ErrorIs(t, err, os.ErrDeadlineExceeded) + require.Nil(t, resp) } -// go test -run Test_GenericParseTypeUint16s -func Test_GenericParseTypeUint16s(t *testing.T) { - t.Parallel() +// go test -run Test_Ctx_End_with_drop_middleware +func Test_Ctx_End_with_drop_middleware(t *testing.T) { + app := New() - type genericTypes[v GenericType] struct { - value v - str string - } + // Middleware that will drop connections + // that persist after c.Next() + app.Use(func(c Ctx) error { + c.Next() //nolint:errcheck // unnecessary to check error + return c.Drop() + }) - uint16s := []genericTypes[uint16]{ - { - value: uint16(0), - str: "0", - }, - { - value: uint16(1), - str: "1", - }, - { - value: uint16(2), - str: "2", - }, - { - value: uint16(3), - str: "3", - }, - { - value: uint16(4), - str: "4", - }, - { - value: uint16(math.MaxUint16), - str: strconv.Itoa(math.MaxUint16), - }, - } + // Early flushing handler + app.Get("/", func(c Ctx) error { + c.SendStatus(StatusOK) //nolint:errcheck // unnecessary to check error + return c.End() + }) - for _, test := range uint16s { - var v uint16 - tt := test - t.Run("test_genericParseTypeUint16s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[uint16](tt.str, v)) - }) - } + resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, StatusOK, resp.StatusCode) } -// go test -run Test_GenericParseTypeUint32s -func Test_GenericParseTypeUint32s(t *testing.T) { - t.Parallel() - - type genericTypes[v GenericType] struct { - value v - str string - } - - uint32s := []genericTypes[uint32]{ - { - value: uint32(0), - str: "0", - }, - { - value: uint32(1), - str: "1", - }, - { - value: uint32(2), - str: "2", - }, - { - value: uint32(3), - str: "3", - }, - { - value: uint32(4), - str: "4", - }, - { - value: uint32(math.MaxUint32), - str: strconv.Itoa(math.MaxUint32), - }, - } - - for _, test := range uint32s { - var v uint32 - tt := test - t.Run("test_genericParseTypeUint32s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[uint32](tt.str, v)) - }) - } -} - -// go test -run Test_GenericParseTypeUint64s -func Test_GenericParseTypeUint64s(t *testing.T) { - t.Parallel() - type genericTypes[v GenericType] struct { - value v - str string - } - - uint64s := []genericTypes[uint64]{ - { - value: uint64(0), - str: "0", - }, - { - value: uint64(1), - str: "1", - }, - { - value: uint64(2), - str: "2", - }, - { - value: uint64(3), - str: "3", - }, - { - value: uint64(4), - str: "4", - }, - } - - for _, test := range uint64s { - var v uint64 - tt := test - t.Run("test_genericParseTypeUint64s", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v)) - require.Equal(t, tt.value, genericParseType[uint64](tt.str, v)) - }) - } -} - -// go test -run Test_GenericParseTypeFloat32s -func Test_GenericParseTypeFloat32s(t *testing.T) { - t.Parallel() - - type genericTypes[v GenericType] struct { - value v - str string - } - - float32s := []genericTypes[float32]{ - { - value: float32(3.1415), - str: "3.1415", - }, - { - value: float32(1.234), - str: "1.234", - }, - { - value: float32(2), - str: "2", - }, - { - value: float32(3), - str: "3", - }, - } - - for _, test := range float32s { - var v float32 - tt := test - t.Run("test_genericParseTypeFloat32s", func(t *testing.T) { - t.Parallel() - require.InEpsilon(t, tt.value, genericParseType(tt.str, v), epsilon) - require.InEpsilon(t, tt.value, genericParseType[float32](tt.str, v), epsilon) - }) - } -} - -// go test -run Test_GenericParseTypeFloat64s -func Test_GenericParseTypeFloat64s(t *testing.T) { - t.Parallel() - - type genericTypes[v GenericType] struct { - value v - str string - } - - float64s := []genericTypes[float64]{ - { - value: float64(3.1415), - str: "3.1415", - }, - { - value: float64(1.234), - str: "1.234", - }, - { - value: float64(2), - str: "2", - }, - { - value: float64(3), - str: "3", - }, - } - - for _, test := range float64s { - var v float64 - tt := test - t.Run("test_genericParseTypeFloat64s", func(t *testing.T) { - t.Parallel() - require.InEpsilon(t, tt.value, genericParseType(tt.str, v), epsilon) - require.InEpsilon(t, tt.value, genericParseType[float64](tt.str, v), epsilon) - }) - } -} - -// go test -run Test_GenericParseTypeArrayBytes -func Test_GenericParseTypeArrayBytes(t *testing.T) { - t.Parallel() - - type genericTypes[v GenericType] struct { - value v - str string - } - - arrBytes := []genericTypes[[]byte]{ - { - value: []byte("alex"), - str: "alex", - }, - { - value: []byte("32.23"), - str: "32.23", - }, - { - value: []byte(nil), - str: "", - }, - { - value: []byte("john"), - str: "john", - }, - } - - for _, test := range arrBytes { - var v []byte - tt := test - t.Run("test_genericParseTypeArrayBytes", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt.value, genericParseType(tt.str, v, []byte(nil))) - require.Equal(t, tt.value, genericParseType[[]byte](tt.str, v, []byte(nil))) - }) - } -} - -// go test -run Test_GenericParseTypeBoolean -func Test_GenericParseTypeBoolean(t *testing.T) { - t.Parallel() - - type genericTypes[v GenericType] struct { - value v - str string - } - - bools := []genericTypes[bool]{ - { - str: "True", - value: true, - }, - { - str: "False", - value: false, - }, - { - str: "true", - value: true, - }, - { - str: "false", - value: false, - }, - } - - for _, test := range bools { - var v bool - tt := test - t.Run("test_genericParseTypeBoolean", func(t *testing.T) { - t.Parallel() - if tt.value { - require.True(t, genericParseType(tt.str, v)) - require.True(t, genericParseType[bool](tt.str, v)) - } else { - require.False(t, genericParseType(tt.str, v)) - require.False(t, genericParseType[bool](tt.str, v)) - } - }) - } -} - -// go test -run Test_Ctx_Drop -v -func Test_Ctx_Drop(t *testing.T) { - t.Parallel() - - app := New() - - // Handler that calls Drop - app.Get("/block-me", func(c Ctx) error { - return c.Drop() - }) - - // Additional handler that just calls return - app.Get("/no-response", func(_ Ctx) error { - return nil - }) - - // Test the Drop method - resp, err := app.Test(httptest.NewRequest(MethodGet, "/block-me", nil)) - require.ErrorIs(t, err, ErrTestGotEmptyResponse) - require.Nil(t, resp) - - // Test the no-response handler - resp, err = app.Test(httptest.NewRequest(MethodGet, "/no-response", nil)) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, StatusOK, resp.StatusCode) - require.Equal(t, "0", resp.Header.Get("Content-Length")) -} - -// go test -run Test_Ctx_DropWithMiddleware -v -func Test_Ctx_DropWithMiddleware(t *testing.T) { - t.Parallel() - - app := New() - - // Middleware that calls Drop - app.Use(func(c Ctx) error { - err := c.Next() - c.Set("X-Test", "test") - return err - }) - - // Handler that calls Drop - app.Get("/block-me", func(c Ctx) error { - return c.Drop() - }) - - // Test the Drop method - resp, err := app.Test(httptest.NewRequest(MethodGet, "/block-me", nil)) - require.ErrorIs(t, err, ErrTestGotEmptyResponse) - require.Nil(t, resp) -} - -// go test -run Test_Ctx_End -func Test_Ctx_End(t *testing.T) { - app := New() - - app.Get("/", func(c Ctx) error { - c.SendString("Hello, World!") //nolint:errcheck // unnecessary to check error - return c.End() - }) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, StatusOK, resp.StatusCode) - body, err := io.ReadAll(resp.Body) - require.NoError(t, err, "io.ReadAll(resp.Body)") - require.Equal(t, "Hello, World!", string(body)) -} - -// go test -run Test_Ctx_End_after_timeout -func Test_Ctx_End_after_timeout(t *testing.T) { - app := New() - - // Early flushing handler - app.Get("/", func(c Ctx) error { - time.Sleep(2 * time.Second) - return c.End() - }) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - require.ErrorIs(t, err, os.ErrDeadlineExceeded) - require.Nil(t, resp) -} - -// go test -run Test_Ctx_End_with_drop_middleware -func Test_Ctx_End_with_drop_middleware(t *testing.T) { - app := New() - - // Middleware that will drop connections - // that persist after c.Next() - app.Use(func(c Ctx) error { - c.Next() //nolint:errcheck // unnecessary to check error - return c.Drop() - }) - - // Early flushing handler - app.Get("/", func(c Ctx) error { - c.SendStatus(StatusOK) //nolint:errcheck // unnecessary to check error - return c.End() - }) - - resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil)) - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, StatusOK, resp.StatusCode) -} - -// go test -run Test_Ctx_End_after_drop -func Test_Ctx_End_after_drop(t *testing.T) { - app := New() +// go test -run Test_Ctx_End_after_drop +func Test_Ctx_End_after_drop(t *testing.T) { + app := New() // Middleware that ends the request // after c.Next() @@ -6024,561 +5380,6 @@ func Test_Ctx_End_after_drop(t *testing.T) { require.Nil(t, resp) } -// go test -run Test_GenericParseTypeString -func Test_GenericParseTypeString(t *testing.T) { - t.Parallel() - - tests := []string{"john", "doe", "hello", "fiber"} - - for _, test := range tests { - var v string - tt := test - t.Run("test_genericParseTypeString", func(t *testing.T) { - t.Parallel() - require.Equal(t, tt, genericParseType(tt, v)) - require.Equal(t, tt, genericParseType[string](tt, v)) - }) - } -} - -// go test -v -run=^$ -bench=Benchmark_GenericParseTypeInts -benchmem -count=4 -func Benchmark_GenericParseTypeInts(b *testing.B) { - type genericTypes[v GenericType] struct { - value v - str string - } - - ints := []genericTypes[int]{ - { - value: 0, - str: "0", - }, - { - value: 1, - str: "1", - }, - { - value: 2, - str: "2", - }, - { - value: 3, - str: "3", - }, - { - value: 4, - str: "4", - }, - } - - int8s := []genericTypes[int8]{ - { - value: int8(0), - str: "0", - }, - { - value: int8(1), - str: "1", - }, - { - value: int8(2), - str: "2", - }, - { - value: int8(3), - str: "3", - }, - { - value: int8(4), - str: "4", - }, - } - - int16s := []genericTypes[int16]{ - { - value: int16(0), - str: "0", - }, - { - value: int16(1), - str: "1", - }, - { - value: int16(2), - str: "2", - }, - { - value: int16(3), - str: "3", - }, - { - value: int16(4), - str: "4", - }, - } - - int32s := []genericTypes[int32]{ - { - value: int32(0), - str: "0", - }, - { - value: int32(1), - str: "1", - }, - { - value: int32(2), - str: "2", - }, - { - value: int32(3), - str: "3", - }, - { - value: int32(4), - str: "4", - }, - } - - int64s := []genericTypes[int64]{ - { - value: int64(0), - str: "0", - }, - { - value: int64(1), - str: "1", - }, - { - value: int64(2), - str: "2", - }, - { - value: int64(3), - str: "3", - }, - { - value: int64(4), - str: "4", - }, - } - - for _, test := range ints { - b.Run("bench_genericParseTypeInts", func(b *testing.B) { - var res int - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range int8s { - b.Run("benchmark_genericParseTypeInt8s", func(b *testing.B) { - var res int8 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range int16s { - b.Run("benchmark_genericParseTypeInt16s", func(b *testing.B) { - var res int16 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range int32s { - b.Run("benchmark_genericParseType32Ints", func(b *testing.B) { - var res int32 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range int64s { - b.Run("benchmark_genericParseTypeInt64s", func(b *testing.B) { - var res int64 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } -} - -// go test -v -run=^$ -bench=Benchmark_GenericParseTypeUints -benchmem -count=4 -func Benchmark_GenericParseTypeUints(b *testing.B) { - type genericTypes[v GenericType] struct { - value v - str string - } - - uints := []genericTypes[uint]{ - { - value: uint(0), - str: "0", - }, - { - value: uint(1), - str: "1", - }, - { - value: uint(2), - str: "2", - }, - { - value: uint(3), - str: "3", - }, - { - value: uint(4), - str: "4", - }, - } - - uint8s := []genericTypes[uint8]{ - { - value: uint8(0), - str: "0", - }, - { - value: uint8(1), - str: "1", - }, - { - value: uint8(2), - str: "2", - }, - { - value: uint8(3), - str: "3", - }, - { - value: uint8(4), - str: "4", - }, - } - - uint16s := []genericTypes[uint16]{ - { - value: uint16(0), - str: "0", - }, - { - value: uint16(1), - str: "1", - }, - { - value: uint16(2), - str: "2", - }, - { - value: uint16(3), - str: "3", - }, - { - value: uint16(4), - str: "4", - }, - } - - uint32s := []genericTypes[uint32]{ - { - value: uint32(0), - str: "0", - }, - { - value: uint32(1), - str: "1", - }, - { - value: uint32(2), - str: "2", - }, - { - value: uint32(3), - str: "3", - }, - { - value: uint32(4), - str: "4", - }, - } - - uint64s := []genericTypes[uint64]{ - { - value: uint64(0), - str: "0", - }, - { - value: uint64(1), - str: "1", - }, - { - value: uint64(2), - str: "2", - }, - { - value: uint64(3), - str: "3", - }, - { - value: uint64(4), - str: "4", - }, - } - - for _, test := range uints { - b.Run("benchamark_genericParseTypeUints", func(b *testing.B) { - var res uint - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range uint8s { - b.Run("benchamark_genericParseTypeUint8s", func(b *testing.B) { - var res uint8 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range uint16s { - b.Run("benchamark_genericParseTypeUint16s", func(b *testing.B) { - var res uint16 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range uint32s { - b.Run("benchamark_genericParseTypeUint32s", func(b *testing.B) { - var res uint32 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } - - for _, test := range uint64s { - b.Run("benchamark_genericParseTypeUint64s", func(b *testing.B) { - var res uint64 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.Equal(b, test.value, res) - }) - } -} - -// go test -v -run=^$ -bench=Benchmark_GenericParseTypeFloats -benchmem -count=4 -func Benchmark_GenericParseTypeFloats(b *testing.B) { - type genericTypes[v GenericType] struct { - value v - str string - } - - float32s := []genericTypes[float32]{ - { - value: float32(3.1415), - str: "3.1415", - }, - { - value: float32(1.234), - str: "1.234", - }, - { - value: float32(2), - str: "2", - }, - { - value: float32(3), - str: "3", - }, - } - - float64s := []genericTypes[float64]{ - { - value: float64(3.1415), - str: "3.1415", - }, - { - value: float64(1.234), - str: "1.234", - }, - { - value: float64(2), - str: "2", - }, - { - value: float64(3), - str: "3", - }, - } - - for _, test := range float32s { - b.Run("benchmark_genericParseTypeFloat32s", func(b *testing.B) { - var res float32 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.InEpsilon(b, test.value, res, epsilon) - }) - } - - for _, test := range float64s { - b.Run("benchmark_genericParseTypeFloat32s", func(b *testing.B) { - var res float64 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - require.InEpsilon(b, test.value, res, epsilon) - }) - } -} - -// go test -v -run=^$ -bench=Benchmark_GenericParseTypeArrayBytes -benchmem -count=4 -func Benchmark_GenericParseTypeArrayBytes(b *testing.B) { - type genericTypes[v GenericType] struct { - value v - str string - } - - arrBytes := []genericTypes[[]byte]{ - { - value: []byte("alex"), - str: "alex", - }, - { - value: []byte("32.23"), - str: "32.23", - }, - { - value: []byte(nil), - str: "", - }, - { - value: []byte("john"), - str: "john", - }, - } - - for _, test := range arrBytes { - b.Run("Benchmark_GenericParseTypeArrayBytes", func(b *testing.B) { - var res []byte - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res, []byte(nil)) - } - require.Equal(b, test.value, res) - }) - } -} - -// go test -v -run=^$ -bench=Benchmark_GenericParseTypeBoolean -benchmem -count=4 -func Benchmark_GenericParseTypeBoolean(b *testing.B) { - type genericTypes[v GenericType] struct { - value v - str string - } - - bools := []genericTypes[bool]{ - { - str: "True", - value: true, - }, - { - str: "False", - value: false, - }, - { - str: "true", - value: true, - }, - { - str: "false", - value: false, - }, - } - - for _, test := range bools { - b.Run("Benchmark_GenericParseTypeBoolean", func(b *testing.B) { - var res bool - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test.str, res) - } - if test.value { - require.True(b, res) - } else { - require.False(b, res) - } - }) - } -} - -// go test -v -run=^$ -bench=Benchmark_GenericParseTypeString -benchmem -count=4 -func Benchmark_GenericParseTypeString(b *testing.B) { - tests := []string{"john", "doe", "hello", "fiber"} - - b.ReportAllocs() - b.ResetTimer() - for _, test := range tests { - b.Run("benchmark_genericParseTypeString", func(b *testing.B) { - var res string - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = genericParseType(test, res) - } - - require.Equal(b, test, res) - }) - } -} - // go test -v -run=^$ -bench=Benchmark_Ctx_IsProxyTrusted -benchmem -count=4 func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { // Scenario without trusted proxy check diff --git a/helpers.go b/helpers.go index e80e89c1e6a..2cfb09c9fca 100644 --- a/helpers.go +++ b/helpers.go @@ -753,90 +753,105 @@ func Convert[T any](value string, convertor func(string) (T, error), defaultValu return converted, nil } -// assertValueType asserts the type of the result to the type of the value -func assertValueType[V GenericType, T any](result T) V { - v, ok := any(result).(V) - if !ok { - panic(fmt.Errorf("failed to type-assert to %T", v)) - } - return v -} +var ( + errParsedEmptyString = errors.New("parsed result is empty string") + errParsedEmptyBytes = errors.New("parsed result is empty bytes") + errParsedType = errors.New("unsupported generic type") +) -func genericParseDefault[V GenericType](err error, parser func() V, defaultValue ...V) V { +func genericParseType[V GenericType](str string) (V, error) { var v V - if err != nil { - if len(defaultValue) > 0 { - return defaultValue[0] - } - return v - } - return parser() -} - -func genericParseInt[V GenericType](str string, bitSize int, parser func(int64) V, defaultValue ...V) V { - result, err := strconv.ParseInt(str, 10, bitSize) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseUint[V GenericType](str string, bitSize int, parser func(uint64) V, defaultValue ...V) V { - result, err := strconv.ParseUint(str, 10, bitSize) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseFloat[V GenericType](str string, bitSize int, parser func(float64) V, defaultValue ...V) V { - result, err := strconv.ParseFloat(str, bitSize) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseBool[V GenericType](str string, parser func(bool) V, defaultValue ...V) V { - result, err := strconv.ParseBool(str) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -//nolint:gosec // Casting in this function is not a concern -func genericParseType[V GenericType](str string, v V, defaultValue ...V) V { switch any(v).(type) { case int: - return genericParseInt[V](str, 0, func(i int64) V { return assertValueType[V, int](int(i)) }, defaultValue...) + result, err := strconv.ParseInt(str, 10, 0) + if err != nil { + return v, fmt.Errorf("failed to parse int: %w", err) + } + return any(int(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case int8: - return genericParseInt[V](str, 8, func(i int64) V { return assertValueType[V, int8](int8(i)) }, defaultValue...) + result, err := strconv.ParseInt(str, 10, 8) + if err != nil { + return v, fmt.Errorf("failed to parse int8: %w", err) + } + return any(int8(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case int16: - return genericParseInt[V](str, 16, func(i int64) V { return assertValueType[V, int16](int16(i)) }, defaultValue...) + result, err := strconv.ParseInt(str, 10, 16) + if err != nil { + return v, fmt.Errorf("failed to parse int16: %w", err) + } + return any(int16(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case int32: - return genericParseInt[V](str, 32, func(i int64) V { return assertValueType[V, int32](int32(i)) }, defaultValue...) + result, err := strconv.ParseInt(str, 10, 32) + if err != nil { + return v, fmt.Errorf("failed to parse int32: %w", err) + } + return any(int32(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case int64: - return genericParseInt[V](str, 64, func(i int64) V { return assertValueType[V, int64](i) }, defaultValue...) + result, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return v, fmt.Errorf("failed to parse int64: %w", err) + } + return any(result).(V), nil //nolint:errcheck,forcetypeassert // not needed case uint: - return genericParseUint[V](str, 0, func(i uint64) V { return assertValueType[V, uint](uint(i)) }, defaultValue...) + result, err := strconv.ParseUint(str, 10, 0) + if err != nil { + return v, fmt.Errorf("failed to parse uint: %w", err) + } + return any(uint(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case uint8: - return genericParseUint[V](str, 8, func(i uint64) V { return assertValueType[V, uint8](uint8(i)) }, defaultValue...) + result, err := strconv.ParseUint(str, 10, 8) + if err != nil { + return v, fmt.Errorf("failed to parse uint8: %w", err) + } + return any(uint8(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case uint16: - return genericParseUint[V](str, 16, func(i uint64) V { return assertValueType[V, uint16](uint16(i)) }, defaultValue...) + result, err := strconv.ParseUint(str, 10, 16) + if err != nil { + return v, fmt.Errorf("failed to parse uint16: %w", err) + } + return any(uint16(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case uint32: - return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint32](uint32(i)) }, defaultValue...) + result, err := strconv.ParseUint(str, 10, 32) + if err != nil { + return v, fmt.Errorf("failed to parse uint32: %w", err) + } + return any(uint32(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case uint64: - return genericParseUint[V](str, 64, func(i uint64) V { return assertValueType[V, uint64](i) }, defaultValue...) + result, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return v, fmt.Errorf("failed to parse uint64: %w", err) + } + return any(result).(V), nil //nolint:errcheck,forcetypeassert // not needed case float32: - return genericParseFloat[V](str, 32, func(i float64) V { return assertValueType[V, float32](float32(i)) }, defaultValue...) + result, err := strconv.ParseFloat(str, 32) + if err != nil { + return v, fmt.Errorf("failed to parse float32: %w", err) + } + return any(float32(result)).(V), nil //nolint:errcheck,forcetypeassert // not needed case float64: - return genericParseFloat[V](str, 64, func(i float64) V { return assertValueType[V, float64](i) }, defaultValue...) + result, err := strconv.ParseFloat(str, 64) + if err != nil { + return v, fmt.Errorf("failed to parse float64: %w", err) + } + return any(result).(V), nil //nolint:errcheck,forcetypeassert // not needed case bool: - return genericParseBool[V](str, func(b bool) V { return assertValueType[V, bool](b) }, defaultValue...) + result, err := strconv.ParseBool(str) + if err != nil { + return v, fmt.Errorf("failed to parse bool: %w", err) + } + return any(result).(V), nil //nolint:errcheck,forcetypeassert // not needed case string: - if str == "" && len(defaultValue) > 0 { - return defaultValue[0] + if str == "" { + return v, errParsedEmptyString } - return assertValueType[V, string](str) + return any(str).(V), nil //nolint:errcheck,forcetypeassert // not needed case []byte: - if str == "" && len(defaultValue) > 0 { - return defaultValue[0] + if str == "" { + return v, errParsedEmptyBytes } - return assertValueType[V, []byte]([]byte(str)) + return any([]byte(str)).(V), nil //nolint:errcheck,forcetypeassert // not needed default: - if len(defaultValue) > 0 { - return defaultValue[0] - } - return v + return v, errParsedType } } diff --git a/helpers_test.go b/helpers_test.go index ee2c25ab4b0..0e222a5ffe5 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -5,9 +5,12 @@ package fiber import ( + "math" + "strconv" "strings" "testing" "time" + "unsafe" "github.com/gofiber/utils/v2" "github.com/stretchr/testify/require" @@ -666,3 +669,643 @@ func Benchmark_SlashRecognition(b *testing.B) { require.True(b, result) }) } + +type testGenericParseTypeIntCase struct { + value int64 + bits int +} + +// go test -run Test_GenericParseTypeInts +func Test_GenericParseTypeInts(t *testing.T) { + t.Parallel() + ints := []testGenericParseTypeIntCase{ + { + value: 0, + bits: 8, + }, + { + value: 1, + bits: 8, + }, + { + value: 2, + bits: 8, + }, + { + value: 3, + bits: 8, + }, + { + value: 4, + bits: 8, + }, + { + value: -1, + bits: 8, + }, + { + value: math.MaxInt8, + bits: 8, + }, + { + value: math.MinInt8, + bits: 8, + }, + { + value: math.MaxInt16, + bits: 16, + }, + { + value: math.MinInt16, + bits: 16, + }, + { + value: math.MaxInt32, + bits: 32, + }, + { + value: math.MinInt32, + bits: 32, + }, + { + value: math.MaxInt64, + bits: 64, + }, + { + value: math.MinInt64, + bits: 64, + }, + } + + testGenericTypeInt[int8](t, "test_genericParseTypeInt8s", ints) + testGenericTypeInt[int16](t, "test_genericParseTypeInt16s", ints) + testGenericTypeInt[int32](t, "test_genericParseTypeInt32s", ints) + testGenericTypeInt[int64](t, "test_genericParseTypeInt64s", ints) + testGenericTypeInt[int](t, "test_genericParseTypeInts", ints) +} + +func testGenericTypeInt[V GenericTypeInteger](t *testing.T, name string, cases []testGenericParseTypeIntCase) { + t.Helper() + t.Run(name, func(t *testing.T) { + t.Parallel() + for _, test := range cases { + v, err := genericParseType[V](strconv.FormatInt(test.value, 10)) + if test.bits <= int(unsafe.Sizeof(V(0)))*8 { + require.NoError(t, err) + require.Equal(t, V(test.value), v) + } else { + require.ErrorIs(t, err, strconv.ErrRange) + } + } + testGenericParseError[V](t) + }) +} + +type testGenericParseTypeUintCase struct { + value uint64 + bits int +} + +// go test -run Test_GenericParseTypeUints +func Test_GenericParseTypeUints(t *testing.T) { + t.Parallel() + uints := []testGenericParseTypeUintCase{ + { + value: 0, + bits: 8, + }, + { + value: 1, + bits: 8, + }, + { + value: 2, + bits: 8, + }, + { + value: 3, + bits: 8, + }, + { + value: 4, + bits: 8, + }, + { + value: math.MaxUint8, + bits: 8, + }, + { + value: math.MaxUint16, + bits: 16, + }, + { + value: math.MaxUint32, + bits: 32, + }, + { + value: math.MaxUint64, + bits: 64, + }, + } + + testGenericTypeUint[uint8](t, "test_genericParseTypeUint8s", uints) + testGenericTypeUint[uint16](t, "test_genericParseTypeUint16s", uints) + testGenericTypeUint[uint32](t, "test_genericParseTypeUint32s", uints) + testGenericTypeUint[uint64](t, "test_genericParseTypeUint64s", uints) + testGenericTypeUint[uint](t, "test_genericParseTypeUints", uints) +} + +func testGenericTypeUint[V GenericTypeInteger](t *testing.T, name string, cases []testGenericParseTypeUintCase) { + t.Helper() + t.Run(name, func(t *testing.T) { + t.Parallel() + for _, test := range cases { + v, err := genericParseType[V](strconv.FormatUint(test.value, 10)) + if test.bits <= int(unsafe.Sizeof(V(0)))*8 { + require.NoError(t, err) + require.Equal(t, V(test.value), v) + } else { + require.ErrorIs(t, err, strconv.ErrRange) + } + } + testGenericParseError[V](t) + }) +} + +// go test -run Test_GenericParseTypeFloats +func Test_GenericParseTypeFloats(t *testing.T) { + t.Parallel() + + floats := []struct { + str string + value float64 + }{ + { + value: 3.1415, + str: "3.1415", + }, + { + value: 1.234, + str: "1.234", + }, + { + value: 2, + str: "2", + }, + { + value: 3, + str: "3", + }, + } + + t.Run("test_genericParseTypeFloat32s", func(t *testing.T) { + t.Parallel() + for _, test := range floats { + v, err := genericParseType[float32](test.str) + require.NoError(t, err) + require.InEpsilon(t, float32(test.value), v, epsilon) + } + testGenericParseError[float32](t) + }) + + t.Run("test_genericParseTypeFloat64s", func(t *testing.T) { + t.Parallel() + for _, test := range floats { + v, err := genericParseType[float64](test.str) + require.NoError(t, err) + require.InEpsilon(t, test.value, v, epsilon) + } + testGenericParseError[float64](t) + }) +} + +// go test -run Test_GenericParseTypeBytes +func Test_GenericParseTypeBytes(t *testing.T) { + t.Parallel() + + cases := []struct { + str string + err error + value []byte + }{ + { + value: []byte("alex"), + str: "alex", + }, + { + value: []byte("32.23"), + str: "32.23", + }, + { + value: []byte("john"), + str: "john", + }, + { + value: []byte(nil), + str: "", + err: errParsedEmptyBytes, + }, + } + + t.Run("test_genericParseTypeBytes", func(t *testing.T) { + t.Parallel() + for _, test := range cases { + v, err := genericParseType[[]byte](test.str) + if test.err == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, test.err) + } + require.Equal(t, test.value, v) + } + }) +} + +// go test -run Test_GenericParseTypeString +func Test_GenericParseTypeString(t *testing.T) { + t.Parallel() + + tests := []string{"john", "doe", "hello", "fiber"} + + for _, test := range tests { + t.Run("test_genericParseTypeString", func(t *testing.T) { + t.Parallel() + v, err := genericParseType[string](test) + require.NoError(t, err) + require.Equal(t, test, v) + }) + } +} + +// go test -run Test_GenericParseTypeBoolean +func Test_GenericParseTypeBoolean(t *testing.T) { + t.Parallel() + + bools := []struct { + str string + value bool + }{ + { + str: "True", + value: true, + }, + { + str: "False", + value: false, + }, + { + str: "true", + value: true, + }, + { + str: "false", + value: false, + }, + } + + t.Run("test_genericParseTypeBoolean", func(t *testing.T) { + t.Parallel() + for _, test := range bools { + v, err := genericParseType[bool](test.str) + require.NoError(t, err) + if test.value { + require.True(t, v) + } else { + require.False(t, v) + } + } + testGenericParseError[bool](t) + }) +} + +func testGenericParseError[V GenericType](t *testing.T) { + t.Helper() + var expected V + v, err := genericParseType[V]("invalid-string") + require.Error(t, err) + require.Equal(t, expected, v) +} + +// go test -v -run=^$ -bench=Benchmark_GenericParseTypeInts -benchmem -count=4 +func Benchmark_GenericParseTypeInts(b *testing.B) { + ints := []testGenericParseTypeIntCase{ + { + value: 0, + bits: 8, + }, + { + value: 1, + bits: 8, + }, + { + value: 2, + bits: 8, + }, + { + value: 3, + bits: 8, + }, + { + value: 4, + bits: 8, + }, + { + value: -1, + bits: 8, + }, + { + value: math.MaxInt8, + bits: 8, + }, + { + value: math.MinInt8, + bits: 8, + }, + { + value: math.MaxInt16, + bits: 16, + }, + { + value: math.MinInt16, + bits: 16, + }, + { + value: math.MaxInt32, + bits: 32, + }, + { + value: math.MinInt32, + bits: 32, + }, + { + value: math.MaxInt64, + bits: 64, + }, + { + value: math.MinInt64, + bits: 64, + }, + } + for _, test := range ints { + benchGenericParseTypeInt[int8](b, "bench_genericParseTypeInt8s", test) + benchGenericParseTypeInt[int16](b, "bench_genericParseTypeInt16s", test) + benchGenericParseTypeInt[int32](b, "bench_genericParseTypeInt32s", test) + benchGenericParseTypeInt[int64](b, "bench_genericParseTypeInt64s", test) + benchGenericParseTypeInt[int](b, "bench_genericParseTypeInts", test) + } +} + +func benchGenericParseTypeInt[V GenericTypeInteger](b *testing.B, name string, test testGenericParseTypeIntCase) { + b.Helper() + b.Run(name, func(t *testing.B) { + var v V + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[V](strconv.FormatInt(test.value, 10)) + } + if test.bits <= int(unsafe.Sizeof(V(0)))*8 { + require.NoError(t, err) + require.Equal(t, V(test.value), v) + } else { + require.ErrorIs(t, err, strconv.ErrRange) + } + }) +} + +// go test -v -run=^$ -bench=Benchmark_GenericParseTypeUints -benchmem -count=4 +func Benchmark_GenericParseTypeUints(b *testing.B) { + uints := []struct { + value uint64 + bits int + }{ + { + value: 0, + bits: 8, + }, + { + value: 1, + bits: 8, + }, + { + value: 2, + bits: 8, + }, + { + value: 3, + bits: 8, + }, + { + value: 4, + bits: 8, + }, + { + value: math.MaxUint8, + bits: 8, + }, + { + value: math.MaxUint16, + bits: 16, + }, + { + value: math.MaxUint16, + bits: 16, + }, + { + value: math.MaxUint32, + bits: 32, + }, + { + value: math.MaxUint64, + bits: 64, + }, + } + + for _, test := range uints { + benchGenericParseTypeUInt[uint8](b, "benchmark_genericParseTypeUint8s", test) + benchGenericParseTypeUInt[uint16](b, "benchmark_genericParseTypeUint16s", test) + benchGenericParseTypeUInt[uint32](b, "benchmark_genericParseTypeUint32s", test) + benchGenericParseTypeUInt[uint64](b, "benchmark_genericParseTypeUint64s", test) + benchGenericParseTypeUInt[uint](b, "benchmark_genericParseTypeUints", test) + } +} + +func benchGenericParseTypeUInt[V GenericTypeInteger](b *testing.B, name string, test testGenericParseTypeUintCase) { + b.Helper() + b.Run(name, func(t *testing.B) { + var v V + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[V](strconv.FormatUint(test.value, 10)) + } + if test.bits <= int(unsafe.Sizeof(V(0)))*8 { + require.NoError(t, err) + require.Equal(t, V(test.value), v) + } else { + require.ErrorIs(t, err, strconv.ErrRange) + } + }) +} + +// go test -v -run=^$ -bench=Benchmark_GenericParseTypeFloats -benchmem -count=4 +func Benchmark_GenericParseTypeFloats(b *testing.B) { + floats := []struct { + str string + value float64 + }{ + { + value: 3.1415, + str: "3.1415", + }, + { + value: 1.234, + str: "1.234", + }, + { + value: 2, + str: "2", + }, + { + value: 3, + str: "3", + }, + } + + for _, test := range floats { + b.Run("benchmark_genericParseTypeFloat32s", func(t *testing.B) { + var v float32 + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[float32](test.str) + } + require.NoError(t, err) + require.InEpsilon(t, float32(test.value), v, epsilon) + }) + } + + for _, test := range floats { + b.Run("benchmark_genericParseTypeFloat64s", func(t *testing.B) { + var v float64 + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[float64](test.str) + } + require.NoError(t, err) + require.InEpsilon(t, test.value, v, epsilon) + }) + } +} + +// go test -v -run=^$ -bench=Benchmark_GenericParseTypeBytes -benchmem -count=4 +func Benchmark_GenericParseTypeBytes(b *testing.B) { + cases := []struct { + str string + err error + value []byte + }{ + { + value: []byte("alex"), + str: "alex", + }, + { + value: []byte("32.23"), + str: "32.23", + }, + { + value: []byte("john"), + str: "john", + }, + { + value: []byte(nil), + str: "", + err: errParsedEmptyBytes, + }, + } + + for _, test := range cases { + b.Run("benchmark_genericParseTypeBytes", func(b *testing.B) { + var v []byte + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[[]byte](test.str) + } + if test.err == nil { + require.NoError(b, err) + } else { + require.ErrorIs(b, err, test.err) + } + require.Equal(b, test.value, v) + }) + } +} + +// go test -v -run=^$ -bench=Benchmark_GenericParseTypeString -benchmem -count=4 +func Benchmark_GenericParseTypeString(b *testing.B) { + tests := []string{"john", "doe", "hello", "fiber"} + + for _, test := range tests { + b.Run("benchmark_genericParseTypeString", func(b *testing.B) { + var v string + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[string](test) + } + require.NoError(b, err) + require.Equal(b, test, v) + }) + } +} + +// go test -v -run=^$ -bench=Benchmark_GenericParseTypeBoolean -benchmem -count=4 +func Benchmark_GenericParseTypeBoolean(b *testing.B) { + bools := []struct { + str string + value bool + }{ + { + str: "True", + value: true, + }, + { + str: "False", + value: false, + }, + { + str: "true", + value: true, + }, + { + str: "false", + value: false, + }, + } + + for _, test := range bools { + b.Run("benchmark_genericParseTypeBoolean", func(b *testing.B) { + var v bool + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + v, err = genericParseType[bool](test.str) + } + require.NoError(b, err) + if test.value { + require.True(b, v) + } else { + require.False(b, v) + } + }) + } +}