mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-06 20:31:35 +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:
parent
82070cb4c8
commit
43dc60fb27
121
ctx.go
121
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
|
||||
}
|
||||
|
@ -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
|
||||
|
1715
ctx_test.go
1715
ctx_test.go
File diff suppressed because it is too large
Load Diff
@ -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 {
|
||||
// </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.
|
||||
}
|
||||
|
||||
// ...
|
||||
})
|
||||
|
94
utils.go
94
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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user