Skip to content

Commit

Permalink
Refactor parsing functions in query handlers
Browse files Browse the repository at this point in the history
The parsing functions in query handlers have been refactored to simplify the process. Parsing code has been extracted into dedicated functions like 'parseIntWithDefault' and 'parseFloatWithDefault', and they now reside in a new utils file. This modularization improves readability and maintainability of the code. Additionally, documentation is updated to reflect the changes.
  • Loading branch information
ryanbekhen committed Jan 18, 2024
1 parent 2fa8b46 commit 0b63d06
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 105 deletions.
120 changes: 16 additions & 104 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ func (c *DefaultCtx) Protocol() string {
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting to use the value outside the Handler.
func (c *DefaultCtx) Query(key string, defaultValue ...string) string {
return defaultString(c.app.getString(c.fasthttp.QueryArgs().Peek(key)), defaultValue)
return Query[string](c, key, defaultValue...)
}

// Queries returns a map of query parameters and their values.
Expand Down Expand Up @@ -1068,127 +1068,47 @@ func Query[V QueryType](c Ctx, key string, defaultValue ...V) V {

switch any(v).(type) {
case int:
result, err := strconv.ParseInt(q, 10, 32)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseIntWithDefault[V](q, 32, defaultValue...)
return assertValueType[V, int](int(result))
case int8:
result, err := strconv.ParseInt(q, 10, 8)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseIntWithDefault[V](q, 8, defaultValue...)
return assertValueType[V, int8](int8(result))
case int16:
result, err := strconv.ParseInt(q, 10, 16)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseIntWithDefault[V](q, 16, defaultValue...)
return assertValueType[V, int16](int16(result))
case int32:
result, err := strconv.ParseInt(q, 10, 32)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseIntWithDefault[V](q, 32, defaultValue...)
return assertValueType[V, int32](int32(result))
case int64:
result, err := strconv.ParseInt(q, 10, 64)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseIntWithDefault[V](q, 64, defaultValue...)
return assertValueType[V, int64](result)
case uint:
result, err := strconv.ParseUint(q, 10, 32)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseUintWithDefault[V](q, 32, defaultValue...)
return assertValueType[V, uint](uint(result))
case uint8:
result, err := strconv.ParseUint(q, 10, 8)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseUintWithDefault[V](q, 8, defaultValue...)
return assertValueType[V, uint8](uint8(result))
case uint16:
result, err := strconv.ParseUint(q, 10, 16)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseUintWithDefault[V](q, 16, defaultValue...)
return assertValueType[V, uint16](uint16(result))
case uint32:
result, err := strconv.ParseUint(q, 10, 32)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseUintWithDefault[V](q, 32, defaultValue...)
return assertValueType[V, uint32](uint32(result))
case uint64:
result, err := strconv.ParseUint(q, 10, 64)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseUintWithDefault[V](q, 64, defaultValue...)
return assertValueType[V, uint64](result)
case float32:
result, err := strconv.ParseFloat(q, 32)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseFloatWithDefault[V](q, 32, defaultValue...)
return assertValueType[V, float32](float32(result))
case float64:
result, err := strconv.ParseFloat(q, 64)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseFloatWithDefault[V](q, 64, defaultValue...)
return assertValueType[V, float64](result)
case bool:
result, err := strconv.ParseBool(q)
if err != nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}
result := parseBoolWithDefault[V](q, defaultValue...)
return assertValueType[V, bool](result)
case string:
if q == "" && len(defaultValue) > 0 {
return defaultValue[0]
}
return assertValueType[V, string](q)
result := parseStringWithDefault[V](q, defaultValue...)
return assertValueType[V, string](result)
case []byte:
if q == "" && len(defaultValue) > 0 {
return defaultValue[0]
Expand All @@ -1202,14 +1122,6 @@ func Query[V QueryType](c Ctx, key string, defaultValue ...V) V {
}
}

func assertValueType[V QueryType, T any](result T) V {
v, ok := any(result).(V)
if !ok {
panic(fmt.Errorf("failed to type-assert to %T", v))
}
return v
}

type QueryType interface {
QueryTypeInteger | QueryTypeFloat | bool | string | []byte
}
Expand Down
39 changes: 38 additions & 1 deletion docs/api/ctx.md
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,7 @@ func (c *Ctx) Query(key string, defaultValue ...string) string
```go title="Example"
// GET http://example.com/?order=desc&brand=nike

app.Get("/", func(c *fiber.Ctx) error {
app.Get("/", func(c fiber.Ctx) error {
c.Query("order") // "desc"
c.Query("brand") // "nike"
c.Query("empty", "nike") // "nike"
Expand All @@ -1349,6 +1349,43 @@ app.Get("/", 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)
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`.
Here is the signature for the generic Query function:
```go title="Signature"
func Query[V QueryType](c Ctx, key string, defaultValue ...V) V
```
Consider this example:
```go title="Example"
// GET http://example.com/?page=1&brand=nike&new=true

app.Get("/", func(c fiber.Ctx) error {
fiber.Query[int](c, "page") // 1
fiber.Query[string](c, "brand") // "nike"
fiber.Query[bool](c, "new") // true

// ...
})
```
In this case, `Query[V QueryType](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:
- 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
## QueryParser
This method is similar to [BodyParser](ctx.md#bodyparser), but for query parameters.
Expand Down
66 changes: 66 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package fiber

import (
"fmt"
"strconv"
)

// assertValueType asserts the type of the result to the type of the value
func assertValueType[V QueryType, T any](result T) V {
v, ok := any(result).(V)
if !ok {
panic(fmt.Errorf("failed to type-assert to %T", v))
}
return v
}

func parseIntWithDefault[V QueryType](q string, bitSize int, defaultValue ...V) int64 {
result, err := strconv.ParseInt(q, 10, bitSize)
if err != nil {
if len(defaultValue) > 0 {
return assertValueType[int64, V](defaultValue[0])
}
return int64(0)
}
return result
}

func parseUintWithDefault[V QueryType](q string, bitSize int, defaultValue ...V) uint64 {
result, err := strconv.ParseUint(q, 10, bitSize)
if err != nil {
if len(defaultValue) > 0 {
return assertValueType[uint64, V](defaultValue[0])
}
return uint64(0)
}
return result
}

func parseFloatWithDefault[V QueryType](q string, bitSize int, defaultValue ...V) float64 {
result, err := strconv.ParseFloat(q, bitSize)
if err != nil {
if len(defaultValue) > 0 {
return assertValueType[float64, V](defaultValue[0])
}
return float64(0)
}
return result
}

func parseStringWithDefault[V QueryType](q string, defaultValue ...V) string {
if q == "" && len(defaultValue) > 0 {
return assertValueType[string, V](defaultValue[0])
}
return q
}

func parseBoolWithDefault[V QueryType](q string, defaultValue ...V) bool {
result, err := strconv.ParseBool(q)
if err != nil {
if len(defaultValue) > 0 {
return assertValueType[bool, V](defaultValue[0])
}
return false
}
return result
}

0 comments on commit 0b63d06

Please sign in to comment.