diff --git a/ctx.go b/ctx.go index 7f41048de3..4be0d4f49f 100644 --- a/ctx.go +++ b/ctx.go @@ -550,7 +550,14 @@ func (c *DefaultCtx) Fresh() bool { // Returned value is only valid within the handler. Do not store any references. // Make copies or use the Immutable setting instead. func (c *DefaultCtx) Get(key string, defaultValue ...string) string { - return defaultString(c.app.getString(c.fasthttp.Request.Header.Peek(key)), defaultValue) + return GetReqHeader(c, key, defaultValue...) +} + +// GetReqHeader returns the HTTP request header specified by filed. +// This function is generic and can handle differnet headers type values. +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...) } // GetRespHeader returns the HTTP response header specified by field. @@ -973,22 +980,22 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string { return defaultString("", defaultValue) } -// ParamsInt is used to get an integer from the route parameters -// it defaults to zero if the parameter is not found or if the -// parameter cannot be converted to an integer -// If a default value is given, it will return that value in case the param -// doesn't exist or cannot be converted to an integer -func (c *DefaultCtx) ParamsInt(key string, defaultValue ...int) (int, error) { - // Use Atoi to convert the param to an int or return zero and an error - value, err := strconv.Atoi(c.Params(key)) - if err != nil { - if len(defaultValue) > 0 { - return defaultValue[0], nil - } - return 0, fmt.Errorf("failed to convert: %w", err) - } - - return value, nil +// Params is used to get the route parameters. +// This function is generic and can handle differnet route parameters type values. +// +// Example: +// +// http://example.com/user/:user -> http://example.com/user/john +// Params[string](c, "user") -> returns john +// +// http://example.com/id/:id -> http://example.com/user/114 +// Params[int](c, "id") -> returns 114 as integer. +// +// 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...) } // Path returns the path part of the request URL. @@ -1108,73 +1115,11 @@ func (c *DefaultCtx) Queries() map[string]string { // name := Query[string](c, "search") // Returns "john" // 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 QueryType](c Ctx, key string, defaultValue ...V) V { +func Query[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V q := c.App().getString(c.Context().QueryArgs().Peek(key)) - switch any(v).(type) { - case int: - return queryParseInt[V](q, 32, func(i int64) V { return assertValueType[V, int](int(i)) }, defaultValue...) - case int8: - return queryParseInt[V](q, 8, func(i int64) V { return assertValueType[V, int8](int8(i)) }, defaultValue...) - case int16: - return queryParseInt[V](q, 16, func(i int64) V { return assertValueType[V, int16](int16(i)) }, defaultValue...) - case int32: - return queryParseInt[V](q, 32, func(i int64) V { return assertValueType[V, int32](int32(i)) }, defaultValue...) - case int64: - return queryParseInt[V](q, 64, func(i int64) V { return assertValueType[V, int64](i) }, defaultValue...) - case uint: - return queryParseUint[V](q, 32, func(i uint64) V { return assertValueType[V, uint](uint(i)) }, defaultValue...) - case uint8: - return queryParseUint[V](q, 8, func(i uint64) V { return assertValueType[V, uint8](uint8(i)) }, defaultValue...) - case uint16: - return queryParseUint[V](q, 16, func(i uint64) V { return assertValueType[V, uint16](uint16(i)) }, defaultValue...) - case uint32: - return queryParseUint[V](q, 32, func(i uint64) V { return assertValueType[V, uint32](uint32(i)) }, defaultValue...) - case uint64: - return queryParseUint[V](q, 64, func(i uint64) V { return assertValueType[V, uint64](i) }, defaultValue...) - case float32: - return queryParseFloat[V](q, 32, func(i float64) V { return assertValueType[V, float32](float32(i)) }, defaultValue...) - case float64: - return queryParseFloat[V](q, 64, func(i float64) V { return assertValueType[V, float64](i) }, defaultValue...) - case bool: - return queryParseBool[V](q, func(b bool) V { return assertValueType[V, bool](b) }, defaultValue...) - case string: - if q == "" && len(defaultValue) > 0 { - return defaultValue[0] - } - return assertValueType[V, string](q) - case []byte: - if q == "" && len(defaultValue) > 0 { - return defaultValue[0] - } - return assertValueType[V, []byte](c.App().getBytes(q)) - default: - if len(defaultValue) > 0 { - return defaultValue[0] - } - return v - } -} - -type QueryType interface { - QueryTypeInteger | QueryTypeFloat | bool | string | []byte -} - -type QueryTypeInteger interface { - QueryTypeIntegerSigned | QueryTypeIntegerUnsigned -} - -type QueryTypeIntegerSigned interface { - int | int8 | int16 | int32 | int64 -} - -type QueryTypeIntegerUnsigned interface { - uint | uint8 | uint16 | uint32 | uint64 -} - -type QueryTypeFloat interface { - float32 | float64 + return genericParseType[V](q, v, defaultValue...) } // Range returns a struct containing the type and a slice of ranges. @@ -1761,3 +1706,17 @@ func (c *DefaultCtx) Bind() *Bind { } return c.bind } + +// Converts a string value to a specified type, handling errors and optional default values. +func Convert[T any](value string, convertor func(string) (T, error), defaultValue ...T) (T, error) { + converted, err := convertor(value) + if err != nil { + if len(defaultValue) > 0 { + return defaultValue[0], nil + } + + return converted, fmt.Errorf("failed to convert: %w", err) + } + + return converted, nil +} diff --git a/ctx_interface.go b/ctx_interface.go index 54cf6012fa..cb867ff2c0 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -224,13 +224,6 @@ type Ctx interface { // Make copies or use the Immutable setting to use the value outside the Handler. Params(key string, defaultValue ...string) string - // ParamsInt is used to get an integer from the route parameters - // it defaults to zero if the parameter is not found or if the - // parameter cannot be converted to an integer - // If a default value is given, it will return that value in case the param - // doesn't exist or cannot be converted to an integer - ParamsInt(key string, defaultValue ...int) (int, error) - // Path returns the path part of the request URL. // Optionally, you could override the path. Path(override ...string) string diff --git a/ctx_test.go b/ctx_test.go index d52ceda5c4..6ecb3f0200 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -15,6 +15,7 @@ import ( "errors" "fmt" "io" + "math" "mime/multipart" "net" "net/http/httptest" @@ -1469,6 +1470,18 @@ func Test_Ctx_Get(t *testing.T) { require.Equal(t, "default", c.Get("unknown", "default")) } +// go test -run Test_Ctx_GetReqHeader +func Test_Ctx_GetReqHeader(t *testing.T) { + t.Parallel() + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + c.Request().Header.Set("foo", "bar") + c.Request().Header.Set("id", "123") + require.Equal(t, 123, GetReqHeader[int](c, "id")) + require.Equal(t, "bar", GetReqHeader[string](c, "foo")) +} + // go test -run Test_Ctx_Host func Test_Ctx_Host(t *testing.T) { t.Parallel() @@ -2316,8 +2329,11 @@ func Test_Ctx_Params(t *testing.T) { }) app.Get("/test3/*/blafasel/*", func(c Ctx) error { require.Equal(t, "1111", c.Params("*1")) + require.Equal(t, 1111, Params(c, "*1", 0)) require.Equal(t, "2222", c.Params("*2")) + require.Equal(t, 2222, Params(c, "*2", 0)) require.Equal(t, "1111", c.Params("*")) + require.Equal(t, 1111, Params(c, "*", 0)) return nil }) app.Get("/test4/:optional?", func(c Ctx) error { @@ -2631,433 +2647,6 @@ func Benchmark_Ctx_Query(b *testing.B) { require.Equal(b, "john", res) } -// go test -run Test_Ctx_QuerySignedInt -func Test_Ctx_QuerySignedInt(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("search=john&age=8") - // int - require.Equal(t, 0, Query[int](c, "foo")) - require.Equal(t, 8, Query[int](c, "age", 12)) - require.Equal(t, 0, Query[int](c, "search")) - require.Equal(t, 1, Query[int](c, "search", 1)) - require.Equal(t, 0, Query[int](c, "id")) - require.Equal(t, 2, Query[int](c, "id", 2)) - - // int8 - require.Equal(t, int8(0), Query[int8](c, "foo")) - require.Equal(t, int8(8), Query[int8](c, "age", 12)) - require.Equal(t, int8(0), Query[int8](c, "search")) - require.Equal(t, int8(1), Query[int8](c, "search", 1)) - require.Equal(t, int8(0), Query[int8](c, "id")) - require.Equal(t, int8(2), Query[int8](c, "id", 2)) - - // int16 - require.Equal(t, int16(0), Query[int16](c, "foo")) - require.Equal(t, int16(8), Query[int16](c, "age", 12)) - require.Equal(t, int16(0), Query[int16](c, "search")) - require.Equal(t, int16(1), Query[int16](c, "search", 1)) - require.Equal(t, int16(0), Query[int16](c, "id")) - require.Equal(t, int16(2), Query[int16](c, "id", 2)) - - // int32 - require.Equal(t, int32(0), Query[int32](c, "foo")) - require.Equal(t, int32(8), Query[int32](c, "age", 12)) - require.Equal(t, int32(0), Query[int32](c, "search")) - require.Equal(t, int32(1), Query[int32](c, "search", 1)) - require.Equal(t, int32(0), Query[int32](c, "id")) - require.Equal(t, int32(2), Query[int32](c, "id", 2)) - - // int64 - require.Equal(t, int64(0), Query[int64](c, "foo")) - require.Equal(t, int64(8), Query[int64](c, "age", 12)) - require.Equal(t, int64(0), Query[int64](c, "search")) - require.Equal(t, int64(1), Query[int64](c, "search", 1)) - require.Equal(t, int64(0), Query[int64](c, "id")) - require.Equal(t, int64(2), Query[int64](c, "id", 2)) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QuerySignedInt -benchmem -count=4 -func Benchmark_Ctx_QuerySignedInt(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res int - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[int](c, "age") - } - require.Equal(b, 8, res) -} - -// go test -run Test_Ctx_QueryBoundarySignedInt -func Test_Ctx_QueryBoundarySignedInt(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - var q string - - // int - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "2147483647", "-2147483648", "-2147483649", "2147483648") - c.Request().URI().SetQueryString(q) - require.Equal(t, 2147483647, Query[int](c, "minus")) - require.Equal(t, -2147483648, Query[int](c, "plus")) - require.Equal(t, 0, Query[int](c, "minus_over")) - require.Equal(t, 0, Query[int](c, "plus_over")) - - // int8 - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "127", "-128", "-129", "128") - c.Request().URI().SetQueryString(q) - require.Equal(t, int8(127), Query[int8](c, "minus")) - require.Equal(t, int8(-128), Query[int8](c, "plus")) - require.Equal(t, int8(0), Query[int8](c, "minus_over")) - require.Equal(t, int8(0), Query[int8](c, "plus_over")) - - // int16 - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "32767", "-32768", "-32769", "32768") - c.Request().URI().SetQueryString(q) - require.Equal(t, int16(32767), Query[int16](c, "minus")) - require.Equal(t, int16(-32768), Query[int16](c, "plus")) - require.Equal(t, int16(0), Query[int16](c, "minus_over")) - require.Equal(t, int16(0), Query[int16](c, "plus_over")) - - // int32 - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "2147483647", "-2147483648", "-2147483649", "2147483648") - c.Request().URI().SetQueryString(q) - require.Equal(t, int32(2147483647), Query[int32](c, "minus")) - require.Equal(t, int32(-2147483648), Query[int32](c, "plus")) - require.Equal(t, int32(0), Query[int32](c, "minus_over")) - require.Equal(t, int32(0), Query[int32](c, "plus_over")) - - // int64 - q = fmt.Sprintf("minus=%s&plus=%s", "-9223372036854775808", "9223372036854775807") - c.Request().URI().SetQueryString(q) - require.Equal(t, int64(-9223372036854775808), Query[int64](c, "minus")) - require.Equal(t, int64(9223372036854775807), Query[int64](c, "plus")) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryBoundarySignedInt -benchmem -count=4 -func Benchmark_Ctx_QueryBoundarySignedInt(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res int - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[int](c, "age") - } - require.Equal(b, 8, res) -} - -// go test -run Test_Ctx_QueryUnsignedInt -func Test_Ctx_QueryUnsignedInt(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("search=john&age=8") - // uint - require.Equal(t, uint(0), Query[uint](c, "foo")) - require.Equal(t, uint(8), Query[uint](c, "age", 12)) - require.Equal(t, uint(0), Query[uint](c, "search")) - require.Equal(t, uint(1), Query[uint](c, "search", 1)) - require.Equal(t, uint(0), Query[uint](c, "id")) - require.Equal(t, uint(2), Query[uint](c, "id", 2)) - - // uint8 - require.Equal(t, uint8(0), Query[uint8](c, "foo")) - require.Equal(t, uint8(8), Query[uint8](c, "age", 12)) - require.Equal(t, uint8(0), Query[uint8](c, "search")) - require.Equal(t, uint8(1), Query[uint8](c, "search", 1)) - require.Equal(t, uint8(0), Query[uint8](c, "id")) - require.Equal(t, uint8(2), Query[uint8](c, "id", 2)) - - // uint16 - require.Equal(t, uint16(0), Query[uint16](c, "foo")) - require.Equal(t, uint16(8), Query[uint16](c, "age", 12)) - require.Equal(t, uint16(0), Query[uint16](c, "search")) - require.Equal(t, uint16(1), Query[uint16](c, "search", 1)) - require.Equal(t, uint16(0), Query[uint16](c, "id")) - require.Equal(t, uint16(2), Query[uint16](c, "id", 2)) - - // uint32 - require.Equal(t, uint32(0), Query[uint32](c, "foo")) - require.Equal(t, uint32(8), Query[uint32](c, "age", 12)) - require.Equal(t, uint32(0), Query[uint32](c, "search")) - require.Equal(t, uint32(1), Query[uint32](c, "search", 1)) - require.Equal(t, uint32(0), Query[uint32](c, "id")) - require.Equal(t, uint32(2), Query[uint32](c, "id", 2)) - - // uint64 - require.Equal(t, uint64(0), Query[uint64](c, "foo")) - require.Equal(t, uint64(8), Query[uint64](c, "age", 12)) - require.Equal(t, uint64(0), Query[uint64](c, "search")) - require.Equal(t, uint64(1), Query[uint64](c, "search", 1)) - require.Equal(t, uint64(0), Query[uint64](c, "id")) - require.Equal(t, uint64(2), Query[uint64](c, "id", 2)) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryUnsignedInt -benchmem -count=4 -func Benchmark_Ctx_QueryUnsignedInt(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res uint - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[uint](c, "age") - } - require.Equal(b, uint(8), res) -} - -// go test -run Test_Ctx_QueryBoundaryUnsignedInt -func Test_Ctx_QueryBoundaryUnsignedInt(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - var q string - - // uint - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "0", "4294967295", "4294967296", "4294967297") - c.Request().URI().SetQueryString(q) - require.Equal(t, uint(0), Query[uint](c, "minus")) - require.Equal(t, uint(4294967295), Query[uint](c, "plus")) - require.Equal(t, uint(0), Query[uint](c, "minus_over")) - require.Equal(t, uint(0), Query[uint](c, "plus_over")) - - // uint8 - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "0", "255", "256", "257") - c.Request().URI().SetQueryString(q) - require.Equal(t, uint8(0), Query[uint8](c, "minus")) - require.Equal(t, uint8(255), Query[uint8](c, "plus")) - require.Equal(t, uint8(0), Query[uint8](c, "minus_over")) - require.Equal(t, uint8(0), Query[uint8](c, "plus_over")) - - // uint16 - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "0", "65535", "65536", "65537") - c.Request().URI().SetQueryString(q) - require.Equal(t, uint16(0), Query[uint16](c, "minus")) - require.Equal(t, uint16(65535), Query[uint16](c, "plus")) - require.Equal(t, uint16(0), Query[uint16](c, "minus_over")) - require.Equal(t, uint16(0), Query[uint16](c, "plus_over")) - - // uint32 - q = fmt.Sprintf("minus=%s&plus=%s&minus_over=%s&plus_over=%s", "0", "4294967295", "4294967296", "4294967297") - c.Request().URI().SetQueryString(q) - require.Equal(t, uint32(0), Query[uint32](c, "minus")) - require.Equal(t, uint32(4294967295), Query[uint32](c, "plus")) - require.Equal(t, uint32(0), Query[uint32](c, "minus_over")) - require.Equal(t, uint32(0), Query[uint32](c, "plus_over")) - - // uint64 - q = fmt.Sprintf("minus=%s&plus=%s", "0", "18446744073709551615") - c.Request().URI().SetQueryString(q) - require.Equal(t, uint64(0), Query[uint64](c, "minus")) - require.Equal(t, uint64(18446744073709551615), Query[uint64](c, "plus")) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryBoundaryUnsignedInt -benchmem -count=4 -func Benchmark_Ctx_QueryBoundaryUnsignedInt(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res uint - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[uint](c, "age") - } - require.Equal(b, uint(8), res) -} - -// go test -run Test_Ctx_QueryFloat -func Test_Ctx_QueryFloat(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("name=alex&amount=32.23&id=") - - // float32 - require.InEpsilon(t, float32(32.23), Query[float32](c, "amount"), epsilon) - require.InEpsilon(t, float32(32.23), Query[float32](c, "amount", 3.123), epsilon) - require.InEpsilon(t, float32(87.123), Query[float32](c, "name", 87.123), epsilon) - require.InDelta(t, float32(0), Query[float32](c, "name"), 0) - require.InEpsilon(t, float32(12.87), Query[float32](c, "id", 12.87), epsilon) - require.InDelta(t, float32(0), Query[float32](c, "id"), 0) - - // float64 - require.InEpsilon(t, 32.23, Query[float64](c, "amount"), epsilon) - require.InEpsilon(t, 32.23, Query[float64](c, "amount", 3.123), epsilon) - require.InEpsilon(t, 87.123, Query[float64](c, "name", 87.123), epsilon) - require.InDelta(t, float64(0), Query[float64](c, "name"), 0) - require.InEpsilon(t, 12.87, Query[float64](c, "id", 12.87), epsilon) - require.InDelta(t, float64(0), Query[float64](c, "id"), 0) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryFloat -benchmem -count=4 -func Benchmark_Ctx_QueryFloat(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res float32 - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[float32](c, "age") - } - require.InEpsilon(b, float32(8), res, epsilon) -} - -// go test -run Test_Ctx_QueryBool -func Test_Ctx_QueryBool(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("name=alex&want_pizza=false&id=") - - require.False(t, Query[bool](c, "want_pizza")) - require.False(t, Query[bool](c, "want_pizza", true)) - require.False(t, Query[bool](c, "name")) - require.True(t, Query[bool](c, "name", true)) - require.False(t, Query[bool](c, "id")) - require.True(t, Query[bool](c, "id", true)) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryBool -benchmem -count=4 -func Benchmark_Ctx_QueryBool(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res bool - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[bool](c, "age") - } - require.False(b, res) -} - -// go test -run Test_Ctx_QueryString -func Test_Ctx_QueryString(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("name=alex&amount=32.23&id=") - - require.Equal(t, "alex", Query[string](c, "name")) - require.Equal(t, "alex", Query[string](c, "name", "john")) - require.Equal(t, "32.23", Query[string](c, "amount")) - require.Equal(t, "32.23", Query[string](c, "amount", "3.123")) - require.Equal(t, "", Query[string](c, "id")) - require.Equal(t, "12.87", Query[string](c, "id", "12.87")) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryString -benchmem -count=4 -func Benchmark_Ctx_QueryString(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res string - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[string](c, "age") - } - require.Equal(b, "8", res) -} - -// go test -run Test_Ctx_QueryBytes -func Test_Ctx_QueryBytes(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("name=alex&amount=32.23&id=") - - require.Equal(t, []byte("alex"), Query[[]byte](c, "name")) - require.Equal(t, []byte("alex"), Query[[]byte](c, "name", []byte("john"))) - require.Equal(t, []byte("32.23"), Query[[]byte](c, "amount")) - require.Equal(t, []byte("32.23"), Query[[]byte](c, "amount", []byte("3.123"))) - require.Equal(t, []byte(nil), Query[[]byte](c, "id")) - require.Equal(t, []byte("12.87"), Query[[]byte](c, "id", []byte("12.87"))) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryBytes -benchmem -count=4 -func Benchmark_Ctx_QueryBytes(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res []byte - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query[[]byte](c, "age") - } - require.Equal(b, []byte("8"), res) -} - -// go test -run Test_Ctx_QueryWithoutGenericDataType -func Test_Ctx_QueryWithoutGenericDataType(t *testing.T) { - t.Parallel() - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - - c.Request().URI().SetQueryString("name=alex&amount=32.23&isAgent=true&id=32") - - require.Equal(t, "alex", Query(c, "name", "john")) - require.Equal(t, "john", Query(c, "unknown", "john")) - require.Equal(t, 32, Query(c, "id", 3)) - require.Equal(t, 3, Query(c, "unknown", 3)) - require.Equal(t, int8(32), Query(c, "id", int8(3))) - require.Equal(t, int8(3), Query(c, "unknown", int8(3))) - require.Equal(t, int16(32), Query(c, "id", int16(3))) - require.Equal(t, int16(3), Query(c, "unknown", int16(3))) - require.Equal(t, int32(32), Query(c, "id", int32(3))) - require.Equal(t, int32(3), Query(c, "unknown", int32(3))) - require.Equal(t, int64(32), Query(c, "id", int64(3))) - require.Equal(t, int64(3), Query(c, "unknown", int64(3))) - require.Equal(t, uint(32), Query(c, "id", uint(3))) - require.Equal(t, uint(3), Query(c, "unknown", uint(3))) - require.Equal(t, uint8(32), Query(c, "id", uint8(3))) - require.Equal(t, uint8(3), Query(c, "unknown", uint8(3))) - require.Equal(t, uint16(32), Query(c, "id", uint16(3))) - require.Equal(t, uint16(3), Query(c, "unknown", uint16(3))) - require.Equal(t, uint32(32), Query(c, "id", uint32(3))) - require.Equal(t, uint32(3), Query(c, "unknown", uint32(3))) - require.Equal(t, uint64(32), Query(c, "id", uint64(3))) - require.Equal(t, uint64(3), Query(c, "unknown", uint64(3))) - require.InEpsilon(t, 32.23, Query(c, "amount", 3.123), epsilon) - require.InEpsilon(t, 3.123, Query(c, "unknown", 3.123), epsilon) - require.InEpsilon(t, float32(32.23), Query(c, "amount", float32(3.123)), epsilon) - require.InEpsilon(t, float32(3.123), Query(c, "unknown", float32(3.123)), epsilon) - require.True(t, Query(c, "isAgent", false)) - require.False(t, Query(c, "unknown", false)) - require.Equal(t, []byte("alex"), Query(c, "name", []byte("john"))) - require.Equal(t, []byte("john"), Query(c, "unknown", []byte("john"))) -} - -// go test -v -run=^$ -bench=Benchmark_Ctx_QueryWithoutGenericDataType -benchmem -count=4 -func Benchmark_Ctx_QueryWithoutGenericDataType(b *testing.B) { - app := New() - c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") - var res int - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = Query(c, "age", 3) - } - require.Equal(b, 8, res) -} - // go test -run Test_Ctx_Range func Test_Ctx_Range(t *testing.T) { t.Parallel() @@ -4748,83 +4337,6 @@ func Benchmark_Ctx_String(b *testing.B) { require.Equal(b, "#0000000000000000 - 0.0.0.0:0 <-> 0.0.0.0:0 - GET http:///", str) } -func TestCtx_ParamsInt(t *testing.T) { - // Create a test context and set some strings (or params) - // create a fake app to be used within this test - t.Parallel() - app := New() - - // Create some test endpoints - - // For the user id I will use the number 1111, so I should be able to get the number - // 1111 from the Ctx - app.Get("/test/:user", func(c Ctx) error { - num, err := c.ParamsInt("user") - - // Check the number matches - require.Equal(t, 1111, num) - - // Check no errors are returned, because we want NO errors in this one - require.NoError(t, err) - - return nil - }) - - // In this test case, there will be a bad request where the expected number is NOT - // a number in the path - app.Get("/testnoint/:user", func(c Ctx) error { - num, err := c.ParamsInt("user") - - // Check the number matches - require.Equal(t, 0, num) - - // Check an error is returned, because we want NO errors in this one - require.Error(t, err) - - return nil - }) - - // For the user id I will use the number 2222, so I should be able to get the number - // 2222 from the Ctx even when the default value is specified - app.Get("/testignoredefault/:user", func(c Ctx) error { - num, err := c.ParamsInt("user", 1111) - - // Check the number matches - require.Equal(t, 2222, num) - - // Check no errors are returned, because we want NO errors in this one - require.NoError(t, err) - - return nil - }) - - // In this test case, there will be a bad request where the expected number is NOT - // a number in the path, default value of 1111 should be used instead - app.Get("/testdefault/:user", func(c Ctx) error { - num, err := c.ParamsInt("user", 1111) - - // Check the number matches - require.Equal(t, 1111, num) - - // Check an error is returned, because we want NO errors in this one - require.NoError(t, err) - - return nil - }) - - _, err := app.Test(httptest.NewRequest(MethodGet, "/test/1111", nil)) - require.NoError(t, err) - - _, err = app.Test(httptest.NewRequest(MethodGet, "/testnoint/xd", nil)) - require.NoError(t, err) - - _, err = app.Test(httptest.NewRequest(MethodGet, "/testignoredefault/2222", nil)) - require.NoError(t, err) - - _, err = app.Test(httptest.NewRequest(MethodGet, "/testdefault/xd", nil)) - require.NoError(t, err) -} - // go test -run Test_Ctx_IsFromLocal_X_Forwarded func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) { t.Parallel() @@ -5048,3 +4560,1198 @@ func Benchmark_Ctx_GetReqHeaders(b *testing.B) { "Test": {"Hello, World 👋!"}, }, 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) { + 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), + }, + } + + 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)) + }) + } +} + +// go test -run Test_genericParseTypeInt16s +func Test_genericParseTypeInt16s(t *testing.T) { + t.Parallel() + type genericTypes[v GenericType] struct { + value v + str string + } + + 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), + }, + } + + 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)) + }) + } +} + +// go test -run Test_genericParseTypeInt32s +func Test_genericParseTypeInt32s(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)) + }) + } +} + +// go test -run Test_genericParseTypeInt64s +func Test_genericParseTypeInt64s(t *testing.T) { + t.Parallel() + type genericTypes[v GenericType] struct { + value v + str string + } + + 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), + }, + } + + 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)) + }) + } +} + +// go test -run Test_genericParseTypeUints +func Test_genericParseTypeUints(t *testing.T) { + t.Parallel() + 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", + }, + } + + 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)) + }) + } +} + +// go test -run Test_genericParseTypeUints +func Test_genericParseTypeUint8s(t *testing.T) { + t.Parallel() + type genericTypes[v GenericType] struct { + value v + str string + } + + 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), + }, + } + + 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)) + }) + } +} + +// go test -run Test_genericParseTypeUint16s +func Test_genericParseTypeUint16s(t *testing.T) { + t.Parallel() + + type genericTypes[v GenericType] struct { + value v + str string + } + + 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), + }, + } + + 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)) + }) + } +} + +// 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_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) + }) + } +} diff --git a/docs/api/ctx.md b/docs/api/ctx.md index ad70924241..3b86ae0f9c 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -1197,32 +1197,33 @@ app.Get("/v1/*/shop/*", func(c fiber.Ctx) error { > _Returned value is only valid within the handler. Do not store any references. > Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) -## ParamsInt -Method can be used to get an integer from the route parameters. -Please note if that parameter is not in the request, zero -will be returned. If the parameter is NOT a number, zero and an error -will be returned - -:::info -Defaults to the integer zero \(`0`\), if the param **doesn't** exist. -::: +In certain scenarios, it can be useful to have an alternative approach to handle different types of parameters, not +just strings. This can be achieved using a generic Query function known as `Params[V GenericType](c Ctx, key string, defaultValue ...V) V`. +This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`. ```go title="Signature" -func (c Ctx) ParamsInt(key string) (int, error) +func Params[v GenericType](c Ctx, key string, default value ...V) V ``` ```go title="Example" -// GET http://example.com/user/123 -app.Get("/user/:id", func(c fiber.Ctx) error { - id, err := c.ParamsInt("id") // int 123 and no error - // ... +// Get http://example.com/user/114 +app.Get("/user/:id", func(c fiber.Ctx) error{ + fiber.Params[string](c, "id") // returns "114" as string. + fiber.Params[int](c, "id") // returns 114 as integer + fiber.Params[string](c, "number") // retunrs "" (default string type) + fiber.Params[int](c, "number") // returns 0 (default integer value type) }) - ``` -This method is equivalent of using `atoi` with ctx.Params +The generic Params function supports returning the following data types based on V GenericType: +- Integer: int, int8, int16, int32, int64 +- Unsigned integer: uint, uint8, uint16, uint32, uint64 +- Floating-point numbers: float32, float64 +- Boolean: bool +- String: string +- Byte array: []byte ## ParamsParser @@ -1375,13 +1376,13 @@ app.Get("/", func(c fiber.Ctx) error { > Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not -just strings. This can be achieved using a generic Query function known as `Query[V QueryType](c Ctx, key string, defaultValue ...V) V`. -This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V QueryType`. +just strings. This can be achieved using a generic Query function known as `Query[V GenericType](c Ctx, key string, defaultValue ...V) V`. +This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`. Here is the signature for the generic Query function: ```go title="Signature" -func Query[V QueryType](c Ctx, key string, defaultValue ...V) V +func Query[V GenericType](c Ctx, key string, defaultValue ...V) V ``` Consider this example: @@ -1398,12 +1399,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -In this case, `Query[V QueryType](c Ctx, key string, defaultValue ...V) V` can retrieve 'page' as an integer, 'brand' +In this case, `Query[V GenericType](c Ctx, key string, defaultValue ...V) V` can retrieve 'page' as an integer, 'brand' as a string, and 'new' as a boolean. The function uses the appropriate parsing function for each specified type to ensure the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your controller actions cleaner. -The generic Query function supports returning the following data types based on V QueryType: +The generic Query function supports returning the following data types based on V GenericType: - Integer: int, int8, int16, int32, int64 - Unsigned integer: uint, uint8, uint16, uint32, uint64 - Floating-point numbers: float32, float64 @@ -2202,3 +2203,26 @@ app.Get("/", func(c fiber.Ctx) error { // }) ``` + +## Convert +Converts a string value to a specified type, handling errors and optional default values. +This function simplifies the conversion process by encapsulating error handling and the management of default values, making your code cleaner and more consistent. +```go title="Signature" +func Convert[T any](value string, convertor func(string) (T, error), defaultValue ...T) (*T, error) +``` + +```go title="Example" +// GET http://example.com/id/bb70ab33-d455-4a03-8d78-d3c1dacae9ff +app.Get("/id/:id", func(c fiber.Ctx) error { + fiber.Convert(c.Params("id"), uuid.Parse) // UUID(bb70ab33-d455-4a03-8d78-d3c1dacae9ff), nil + + +// GET http://example.com/search?id=65f6f54221fb90e6a6b76db7 +app.Get("/search", func(c fiber.Ctx) error) { + fiber.Convert(c.Query("id"), mongo.ParseObjectID) // objectid(65f6f54221fb90e6a6b76db7), nil + fiber.Convert(c.Query("id"), uuid.Parse) // uuid.Nil, error(cannot parse given uuid) + fiber.Convert(c.Query("id"), uuid.Parse, mongo.NewObjectID) // new object id generated and return nil as error. +} + + // ... +}) diff --git a/utils.go b/utils.go index 996afb9a46..0e707cfac8 100644 --- a/utils.go +++ b/utils.go @@ -6,7 +6,7 @@ import ( ) // assertValueType asserts the type of the result to the type of the value -func assertValueType[V QueryType, T any](result T) V { +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)) @@ -14,7 +14,7 @@ func assertValueType[V QueryType, T any](result T) V { return v } -func queryParseDefault[V QueryType](err error, parser func() V, defaultValue ...V) V { +func genericParseDefault[V GenericType](err error, parser func() V, defaultValue ...V) V { var v V if err != nil { if len(defaultValue) > 0 { @@ -25,22 +25,88 @@ func queryParseDefault[V QueryType](err error, parser func() V, defaultValue ... return parser() } -func queryParseInt[V QueryType](q string, bitSize int, parser func(int64) V, defaultValue ...V) V { - result, err := strconv.ParseInt(q, 10, bitSize) - return queryParseDefault[V](err, func() V { return parser(result) }, defaultValue...) +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 queryParseUint[V QueryType](q string, bitSize int, parser func(uint64) V, defaultValue ...V) V { - result, err := strconv.ParseUint(q, 10, bitSize) - return queryParseDefault[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 queryParseFloat[V QueryType](q string, bitSize int, parser func(float64) V, defaultValue ...V) V { - result, err := strconv.ParseFloat(q, bitSize) - return queryParseDefault[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 queryParseBool[V QueryType](q string, parser func(bool) V, defaultValue ...V) V { - result, err := strconv.ParseBool(q) - return queryParseDefault[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...) +} + +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...) + case int8: + return genericParseInt[V](str, 8, func(i int64) V { return assertValueType[V, int8](int8(i)) }, defaultValue...) + case int16: + return genericParseInt[V](str, 16, func(i int64) V { return assertValueType[V, int16](int16(i)) }, defaultValue...) + case int32: + return genericParseInt[V](str, 32, func(i int64) V { return assertValueType[V, int32](int32(i)) }, defaultValue...) + case int64: + return genericParseInt[V](str, 64, func(i int64) V { return assertValueType[V, int64](i) }, defaultValue...) + case uint: + return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint](uint(i)) }, defaultValue...) + case uint8: + return genericParseUint[V](str, 8, func(i uint64) V { return assertValueType[V, uint8](uint8(i)) }, defaultValue...) + case uint16: + return genericParseUint[V](str, 16, func(i uint64) V { return assertValueType[V, uint16](uint16(i)) }, defaultValue...) + case uint32: + return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint32](uint32(i)) }, defaultValue...) + case uint64: + return genericParseUint[V](str, 64, func(i uint64) V { return assertValueType[V, uint64](i) }, defaultValue...) + case float32: + return genericParseFloat[V](str, 32, func(i float64) V { return assertValueType[V, float32](float32(i)) }, defaultValue...) + case float64: + return genericParseFloat[V](str, 64, func(i float64) V { return assertValueType[V, float64](i) }, defaultValue...) + case bool: + return genericParseBool[V](str, func(b bool) V { return assertValueType[V, bool](b) }, defaultValue...) + case string: + if str == "" && len(defaultValue) > 0 { + return defaultValue[0] + } + return assertValueType[V, string](str) + case []byte: + if str == "" && len(defaultValue) > 0 { + return defaultValue[0] + } + return assertValueType[V, []byte]([]byte(str)) + default: + if len(defaultValue) > 0 { + return defaultValue[0] + } + return v + } +} + +type GenericType interface { + GenericTypeInteger | GenericTypeFloat | bool | string | []byte +} + +type GenericTypeInteger interface { + GenericTypeIntegerSigned | GenericTypeIntegerUnsigned +} + +type GenericTypeIntegerSigned interface { + int | int8 | int16 | int32 | int64 +} + +type GenericTypeIntegerUnsigned interface { + uint | uint8 | uint16 | uint32 | uint64 +} + +type GenericTypeFloat interface { + float32 | float64 }