Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion addon/retry/exponential_backoff.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (e *ExponentialBackoff) Retry(f func() error) error {
e.currentInterval = e.InitialInterval
}
var err error
for i := 0; i < e.MaxRetryCount; i++ {
for range e.MaxRetryCount {
Copy link

Choose a reason for hiding this comment

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

🐛 Correctness Issue

Syntax Error: Invalid range expression.

The code uses 'for range e.MaxRetryCount' which is invalid Go syntax as range requires an iterable type (slice, array, map, string, or channel), not an integer.

Current Code (Diff):

- 	for range e.MaxRetryCount {
+ 	for i := 0; i < e.MaxRetryCount; i++ {
📝 Committable suggestion

‼️ IMPORTANT
Trust, but verify! 🕵️ Please review this suggestion with the care of a code archaeologist - check that it perfectly replaces the highlighted code, preserves all lines, maintains proper indentation, and won't break anything in production. Your future self will thank you! 🚀

Suggested change
for range e.MaxRetryCount {
for i := 0; i < e.MaxRetryCount; i++ {

err = f()
if err == nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion addon/retry/exponential_backoff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func Test_ExponentialBackoff_Next(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
for i := 0; i < tt.expBackoff.MaxRetryCount; i++ {
for i := range tt.expBackoff.MaxRetryCount {
Copy link

Choose a reason for hiding this comment

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

🐛 Correctness Issue

Compilation Error: Invalid range expression.

Using range with an integer (MaxRetryCount) will cause a compilation failure as range in Go requires a slice, array, string, map, or channel to iterate over.

Current Code (Diff):

- 			for i := range tt.expBackoff.MaxRetryCount {
+ 			for i := 0; i < tt.expBackoff.MaxRetryCount; i++ {
📝 Committable suggestion

‼️ IMPORTANT
Trust, but verify! 🕵️ Please review this suggestion with the care of a code archaeologist - check that it perfectly replaces the highlighted code, preserves all lines, maintains proper indentation, and won't break anything in production. Your future self will thank you! 🚀

Suggested change
for i := range tt.expBackoff.MaxRetryCount {
for i := 0; i < tt.expBackoff.MaxRetryCount; i++ {

next := tt.expBackoff.next()
if next < tt.expNextTimeIntervals[i] || next > tt.expNextTimeIntervals[i]+1*time.Second {
t.Errorf("wrong next time:\n"+
Expand Down
2 changes: 1 addition & 1 deletion bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func Test_Bind_Query_Map(t *testing.T) {

em := make(map[string][]int)
c.Request().URI().SetQueryString("")
require.ErrorIs(t, c.Bind().Query(&em), binder.ErrMapNotConvertable)
require.ErrorIs(t, c.Bind().Query(&em), binder.ErrMapNotConvertible)
}

// go test -run Test_Bind_Query_WithSetParserDecoder -v
Expand Down
2 changes: 1 addition & 1 deletion binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
// Binder errors
var (
ErrSuitableContentNotFound = errors.New("binder: suitable content not found to parse body")
ErrMapNotConvertable = errors.New("binder: map is not convertable to map[string]string or map[string][]string")
ErrMapNotConvertible = errors.New("binder: map is not convertible to map[string]string or map[string][]string")
)

var HeaderBinderPool = sync.Pool{
Expand Down
4 changes: 2 additions & 2 deletions binder/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@ func parseToMap(ptr any, data map[string][]string) error {
case reflect.Slice:
newMap, ok := ptr.(map[string][]string)
if !ok {
return ErrMapNotConvertable
return ErrMapNotConvertible
}

maps.Copy(newMap, data)
case reflect.String, reflect.Interface:
newMap, ok := ptr.(map[string]string)
if !ok {
return ErrMapNotConvertable
return ErrMapNotConvertible
}

for k, v := range data {
Expand Down
2 changes: 1 addition & 1 deletion binder/mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func Test_parseToMap(t *testing.T) {
// Test map[string]any
m3 := make(map[string]any)
err = parseToMap(m3, inputMap)
require.ErrorIs(t, err, ErrMapNotConvertable)
require.ErrorIs(t, err, ErrMapNotConvertible)
}

func Test_FilterFlags(t *testing.T) {
Expand Down
32 changes: 23 additions & 9 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -1751,21 +1751,35 @@ func (c *DefaultCtx) setCanonical(key, val string) {
c.fasthttp.Response.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val))
}

// Subdomains returns a string slice of subdomains in the domain name of the request.
// The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments.
// Subdomains returns a slice of subdomains from the host, excluding the last `offset` components.
// If the offset is negative or exceeds the number of subdomains, an empty slice is returned.
// If the offset is zero every label (no trimming) is returned.
func (c *DefaultCtx) Subdomains(offset ...int) []string {
o := 2
if len(offset) > 0 {
o = offset[0]
}
subdomains := strings.Split(c.Host(), ".")
l := len(subdomains) - o
// Check index to avoid slice bounds out of range panic
if l < 0 {
l = len(subdomains)

// Negative offset, return nothing.
if o < 0 {
return []string{}
}

// strip “:port” if present
host := c.Hostname()
parts := strings.Split(host, ".")

// offset == 0, caller wants everything.
if o == 0 {
return parts
}
subdomains = subdomains[:l]
return subdomains

// If we trim away the whole slice (or more), nothing remains.
if o >= len(parts) {
return []string{}
}

return parts[:len(parts)-o]
}

// Stale is not implemented yet, pull requests are welcome!
Expand Down
85 changes: 79 additions & 6 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3015,15 +3015,88 @@ func Test_Ctx_Stale(t *testing.T) {

// go test -run Test_Ctx_Subdomains
func Test_Ctx_Subdomains(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})

c.Request().URI().SetHost("john.doe.is.awesome.google.com")
require.Equal(t, []string{"john", "doe"}, c.Subdomains(4))
type tc struct {
name string
host string
offset []int // nil ⇒ call without argument
want []string
}

c.Request().URI().SetHost("localhost:3000")
require.Equal(t, []string{"localhost:3000"}, c.Subdomains())
cases := []tc{
{
name: "default offset (2) drops registrable domain + TLD",
host: "john.doe.is.awesome.google.com",
offset: nil, // Subdomains()
want: []string{"john", "doe", "is", "awesome"},
},
{
name: "custom offset trims N right-hand labels",
host: "john.doe.is.awesome.google.com",
offset: []int{4},
want: []string{"john", "doe"},
},
{
name: "offset too high returns empty",
host: "john.doe.is.awesome.google.com",
offset: []int{10},
want: []string{},
},
{
name: "zero offset returns all labels",
host: "john.doe.google.com",
offset: []int{0},
want: []string{"john", "doe", "google", "com"},
},
{
name: "offset 1 keeps registrable domain",
host: "john.doe.google.com",
offset: []int{1},
want: []string{"john", "doe", "google"},
},
{
name: "negative offset returns empty",
host: "john.doe.google.com",
offset: []int{-1},
want: []string{},
},
{
name: "offset equal len returns empty",
host: "john.doe.com",
offset: []int{3},
want: []string{},
},
{
name: "offset equal len returns empty",
host: "john.doe.com",
offset: []int{3},
want: []string{},
},
{
name: "zero offset returns all labels with port present",
host: "localhost:3000",
offset: []int{0},
want: []string{"localhost"},
},
{
name: "host with port — custom offset trims 2 labels",
host: "foo.bar.example.com:8080",
offset: []int{2},
want: []string{"foo", "bar"},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)

c.Request().URI().SetHost(tc.host)
got := c.Subdomains(tc.offset...)
require.Equal(t, tc.want, got)
})
}
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Subdomains -benchmem -count=4
Expand Down
2 changes: 1 addition & 1 deletion docs/api/bind.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ For more control over error handling, you can use the following methods.

If you want to handle binder errors automatically, you can use `WithAutoHandling`.
If there's an error, it will return the error and set HTTP status to `400 Bad Request`.
This function does NOT panic therefor you must still return on error explicitly
This function does NOT panic therefore you must still return on error explicitly

```go title="Signature"
func (b *Bind) WithAutoHandling() *Bind
Expand Down
30 changes: 21 additions & 9 deletions docs/api/ctx.md
Original file line number Diff line number Diff line change
Expand Up @@ -1344,21 +1344,33 @@ func (c fiber.Ctx) Stale() bool

### Subdomains

Returns a slice of subdomains in the domain name of the request.
Returns a slice with the host’s sub-domain labels. The dot-separated parts that precede the registrable domain (`example`) and the top-level domain (ex: `com`).

The application property `subdomain offset`, which defaults to `2`, is used for determining the beginning of the subdomain segments.
The `subdomain offset` (default `2`) tells Fiber how many labels, counting from the right-hand side, are always discarded.
Passing an `offset` argument lets you override that value for a single call.

```go title="Signature"
```go
func (c fiber.Ctx) Subdomains(offset ...int) []string
```

```go title="Example"
| `offset` | Result | Meaning |
|----------|----------------------------------------|-------------------------------------------------------|
| *omitted* → **2** | trim 2 right-most labels | drop the registrable domain **and** the TLD |
| `1` to `len(labels)-1` | trim exactly `offset` right-most labels | custom trimming of available labels |
| `>= len(labels)` | **return `[]`** | offset exceeds available labels → empty slice |
| `0` | **return every label** | keep the entire host unchanged |
| `< 0` | **return `[]`** | negative offsets are invalid → empty slice |

#### Example

```go
// Host: "tobi.ferrets.example.com"

app.Get("/", func(c fiber.Ctx) error {
c.Subdomains() // ["ferrets", "tobi"]
c.Subdomains(1) // ["tobi"]

c.Subdomains() // ["tobi", "ferrets"]
c.Subdomains(1) // ["tobi", "ferrets", "example"]
c.Subdomains(0) // ["tobi", "ferrets", "example", "com"]
c.Subdomains(-1) // []
// ...
})
```
Expand Down Expand Up @@ -1586,8 +1598,8 @@ app.Get("/", func(c fiber.Ctx) error {

Transfers the file from the given path as an `attachment`.

Typically, browsers will prompt the user to download. By default, the [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header `filename=` parameter is the file path (_this typically appears in the browser dialog_).
Override this default with the **filename** parameter.
Typically, browsers will prompt the user to download. By default, the [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header `filename=` parameter is the file path (this typically appears in the browser dialog).
Override this default with the `filename` parameter.

```go title="Signature"
func (c fiber.Ctx) Download(file string, filename ...string) error
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Converts a string value to a specified type, handling errors and optional defaul
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)
func Convert[T any](value string, converter func(string) (T, error), defaultValue ...T) (*T, error)
```

```go title="Example"
Expand Down
Loading