1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-06 23:12:02 +00:00

(v3) feature: Implement new generic functions: Params, Get and Convert (#2850)

* feature: implement generic params function

* update: add ctx generic params benchmark function

* fix: fix linter errors on boolean types

* fix: fix linter errors on float variable types

* tests: add Test_Params_TypeAssertFail

Co-authored-by: Jason McNeil <sixcolors@mac.com>

* Update ctx_test.go

* typo: change genericType typo to GenericType in documents

* remove ParamsInt method and rewrite Params method

* add genericParseType tests and benchmarks and simplify genericQuery and genericParams tests and benchmarks

* added GetReqHeader generic function

* added tests for params generic function

* add tests for GetReqHeader generic function

* added GetReqHeader generic function

* Revert "added GetReqHeader generic function"

This reverts commit a63cebb7121fdd315c53b6f0aa3042612a0d23ac.

* fix tests and benchamarks of generic tests

* added default value to array test genericParse

* fix Params generic function on default value and fixes some tests and typos

* remove Test_Params_TypeAssertFail function(it didn't panic anyway)

* fix bad usage on parallel tests

* add convert function

* fix generic tests

* fix fail tests on use parallel multiple time

* fix typo on params comment section

* remove pointer refer on Convert

* update generic benchmarks

* reslove conflicts1

* add specific tests to integer and unsigned integer
generic parser

* fix typo on Convert document

* change uint tests of Test_genericParseTypeInts

* move generic types to utils.go file and change
bitsize of int value type to 0

* update genericParseInt unit tests

* update generic uint tests and pass value type in
check functions

* reverse dependency of Params and genericParams

* update convert docs

---------

Co-authored-by: Jason McNeil <sixcolors@mac.com>
Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Co-authored-by: RW <rene@gofiber.io>
This commit is contained in:
Iliya 2024-03-18 17:32:15 +03:30 committed by GitHub
parent 82070cb4c8
commit 43dc60fb27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1376 additions and 627 deletions

121
ctx.go
View File

@ -550,7 +550,14 @@ func (c *DefaultCtx) Fresh() bool {
// Returned value is only valid within the handler. Do not store any references. // Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead. // Make copies or use the Immutable setting instead.
func (c *DefaultCtx) Get(key string, defaultValue ...string) string { 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. // 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) return defaultString("", defaultValue)
} }
// ParamsInt is used to get an integer from the route parameters // Params is used to get the route parameters.
// it defaults to zero if the parameter is not found or if the // This function is generic and can handle differnet route parameters type values.
// parameter cannot be converted to an integer //
// If a default value is given, it will return that value in case the param // Example:
// doesn't exist or cannot be converted to an integer //
func (c *DefaultCtx) ParamsInt(key string, defaultValue ...int) (int, error) { // http://example.com/user/:user -> http://example.com/user/john
// Use Atoi to convert the param to an int or return zero and an error // Params[string](c, "user") -> returns john
value, err := strconv.Atoi(c.Params(key)) //
if err != nil { // http://example.com/id/:id -> http://example.com/user/114
if len(defaultValue) > 0 { // Params[int](c, "id") -> returns 114 as integer.
return defaultValue[0], nil //
} // http://example.com/id/:number -> http://example.com/id/john
return 0, fmt.Errorf("failed to convert: %w", err) // 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 value, nil return genericParseType(c.Params(key), v, defaultValue...)
} }
// Path returns the path part of the request URL. // 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" // name := Query[string](c, "search") // Returns "john"
// age := Query[int](c, "age") // Returns 8 // age := Query[int](c, "age") // Returns 8
// unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found // 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 var v V
q := c.App().getString(c.Context().QueryArgs().Peek(key)) q := c.App().getString(c.Context().QueryArgs().Peek(key))
switch any(v).(type) { return genericParseType[V](q, v, defaultValue...)
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
} }
// Range returns a struct containing the type and a slice of ranges. // Range returns a struct containing the type and a slice of ranges.
@ -1761,3 +1706,17 @@ func (c *DefaultCtx) Bind() *Bind {
} }
return c.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
}

View File

@ -224,13 +224,6 @@ type Ctx interface {
// Make copies or use the Immutable setting to use the value outside the Handler. // Make copies or use the Immutable setting to use the value outside the Handler.
Params(key string, defaultValue ...string) string 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. // Path returns the path part of the request URL.
// Optionally, you could override the path. // Optionally, you could override the path.
Path(override ...string) string Path(override ...string) string

File diff suppressed because it is too large Load Diff

View File

@ -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. > _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) > 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. In certain scenarios, it can be useful to have an alternative approach to handle different types of parameters, not
Please note if that parameter is not in the request, zero just strings. This can be achieved using a generic Query function known as `Params[V GenericType](c Ctx, key string, defaultValue ...V) V`.
will be returned. If the parameter is NOT a number, zero and an error This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`.
will be returned
:::info
Defaults to the integer zero \(`0`\), if the param **doesn't** exist.
:::
```go title="Signature" ```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" ```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 ## 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) > 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 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`. 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 QueryType`. 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: Here is the signature for the generic Query function:
```go title="Signature" ```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: 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 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 the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your
controller actions cleaner. 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 - Integer: int, int8, int16, int32, int64
- Unsigned integer: uint, uint8, uint16, uint32, uint64 - Unsigned integer: uint, uint8, uint16, uint32, uint64
- Floating-point numbers: float32, float64 - Floating-point numbers: float32, float64
@ -2202,3 +2203,26 @@ app.Get("/", func(c fiber.Ctx) error {
// </Fiber> // </Fiber>
}) })
``` ```
## 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.
}
// ...
})

View File

@ -6,7 +6,7 @@ import (
) )
// assertValueType asserts the type of the result to the type of the value // 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) v, ok := any(result).(V)
if !ok { if !ok {
panic(fmt.Errorf("failed to type-assert to %T", v)) 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 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 var v V
if err != nil { if err != nil {
if len(defaultValue) > 0 { if len(defaultValue) > 0 {
@ -25,22 +25,88 @@ func queryParseDefault[V QueryType](err error, parser func() V, defaultValue ...
return parser() return parser()
} }
func queryParseInt[V QueryType](q string, bitSize int, parser func(int64) V, defaultValue ...V) V { func genericParseInt[V GenericType](str string, bitSize int, parser func(int64) V, defaultValue ...V) V {
result, err := strconv.ParseInt(q, 10, bitSize) result, err := strconv.ParseInt(str, 10, bitSize)
return queryParseDefault[V](err, func() V { return parser(result) }, defaultValue...) 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 { func genericParseUint[V GenericType](str string, bitSize int, parser func(uint64) V, defaultValue ...V) V {
result, err := strconv.ParseUint(q, 10, bitSize) result, err := strconv.ParseUint(str, 10, bitSize)
return queryParseDefault[V](err, func() V { return parser(result) }, defaultValue...) 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 { func genericParseFloat[V GenericType](str string, bitSize int, parser func(float64) V, defaultValue ...V) V {
result, err := strconv.ParseFloat(q, bitSize) result, err := strconv.ParseFloat(str, bitSize)
return queryParseDefault[V](err, func() V { return parser(result) }, defaultValue...) return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...)
} }
func queryParseBool[V QueryType](q string, parser func(bool) V, defaultValue ...V) V { func genericParseBool[V GenericType](str string, parser func(bool) V, defaultValue ...V) V {
result, err := strconv.ParseBool(q) result, err := strconv.ParseBool(str)
return queryParseDefault[V](err, func() V { return parser(result) }, defaultValue...) 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
} }