Skip to content

Commit

Permalink
✨ (v3) feature: Implement new generic functions: Params, Get and Conv…
Browse files Browse the repository at this point in the history
…ert (#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 <[email protected]>

* 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 a63cebb.

* 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 <[email protected]>
Co-authored-by: Juan Calderon-Perez <[email protected]>
Co-authored-by: RW <[email protected]>
  • Loading branch information
4 people authored Mar 18, 2024
1 parent 82070cb commit 43dc60f
Show file tree
Hide file tree
Showing 5 changed files with 1,376 additions and 627 deletions.
121 changes: 40 additions & 81 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
7 changes: 0 additions & 7 deletions ctx_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

1 comment on commit 43dc60f

@ReneWerner87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 43dc60f Previous: d2b19e2 Ratio
Benchmark_Etag 200 ns/op 0 B/op 0 allocs/op 98.08 ns/op 0 B/op 0 allocs/op 2.04
Benchmark_Middleware_Favicon 208.9 ns/op 12 B/op 4 allocs/op 90.01 ns/op 3 B/op 1 allocs/op 2.32

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.