Skip to content

Commit f77fb56

Browse files
authored
Merge branch 'main' into 2025-08-21-14-48-18
2 parents be34c4a + 23c4f59 commit f77fb56

16 files changed

Lines changed: 502 additions & 66 deletions

.github/release-drafter.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
_extends: gofiber/.github:release-drafter.yml
1+
_extends:
2+
from: gofiber/.github:release-drafter.yml
3+
strategy:
4+
categories: append
25
commitish: main
36
filter-by-commitish: true
4-
include-labels:
5-
- 'v3'
6-
exclude-labels:
7-
- 'v2'
7+
categories:
8+
- type: pre-include
9+
when:
10+
label: 'v3'
11+
- type: pre-exclude
12+
when:
13+
label: 'v2'
814
template: |
915
$CHANGES
1016

.github/workflows/deadcode.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
runs-on: ubuntu-latest
2020
steps:
2121
- name: Checkout
22-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
22+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2323

2424
- name: Set up Go
2525
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0

.github/workflows/manual-dependabot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
timeout-minutes: 30
2727
steps:
2828
- name: Checkout repo
29-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3030

3131
- name: Checkout dependabot
3232
run: |

.github/workflows/markdown.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ jobs:
2424
timeout-minutes: 10
2525
steps:
2626
- name: Fetch Repository
27-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2828

2929
- name: Run markdownlint-cli2
30-
uses: DavidAnson/markdownlint-cli2-action@8de2aa07cae85fd17c0b35642db70cf5495f1d25 # v24.0.0
30+
uses: DavidAnson/markdownlint-cli2-action@6bf21b07787794f89a243495939cd651942aeabe # v24.1.0
3131
with:
3232
globs: |
3333
**/*.md

.github/workflows/release-drafter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ jobs:
2121
runs-on: ubuntu-latest
2222
timeout-minutes: 10
2323
steps:
24-
- uses: release-drafter/release-drafter@4d75298e00d9e34c483e5ff8c68d0ea1c1940c1e # v7
24+
- uses: release-drafter/release-drafter@eada3c96a64734dd381cfbda23511034e328ddb0 # v7

.github/workflows/spell-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
runs-on: ubuntu-latest
2727
timeout-minutes: 10
2828
steps:
29-
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3030

3131
- name: Set up Node.js
3232
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
timeout-minutes: 30
2929
steps:
3030
- name: Fetch Repository
31-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
31+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3232

3333
- name: Install Go
3434
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
@@ -56,7 +56,7 @@ jobs:
5656
timeout-minutes: 30
5757
steps:
5858
- name: Fetch Repository
59-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
59+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
6060

6161
- name: Install Go
6262
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0

.github/workflows/update-version.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
runs-on: ubuntu-latest
2323
timeout-minutes: 30
2424
steps:
25-
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2626
with:
2727
# PAT with bypass rights for the protected `main` branch.
2828
# The default GITHUB_TOKEN cannot push directly to `main`.

.github/workflows/vulncheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
GO111MODULE: on
2727
steps:
2828
- name: Fetch Repository
29-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3030

3131
- name: Install Go
3232
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0

bind.go

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"reflect"
88
"slices"
9+
"strings"
910
"sync"
1011

1112
"github.com/gofiber/fiber/v3/binder"
@@ -428,6 +429,75 @@ func (b *Bind) Body(out any) error {
428429
return ErrUnprocessableEntity
429430
}
430431

432+
type bindSource int
433+
434+
const (
435+
sourceURI bindSource = iota
436+
sourceBody
437+
sourceQuery
438+
sourceHeader
439+
sourceCookie
440+
)
441+
442+
type cachedPrecedence struct {
443+
err error
444+
sources []bindSource
445+
}
446+
447+
var bindingPrecedenceCache sync.Map // map[reflect.Type]cachedPrecedence
448+
449+
func getBindingPrecedence(t reflect.Type) ([]bindSource, error) {
450+
if cached, ok := bindingPrecedenceCache.Load(t); ok {
451+
if cp, ok := cached.(cachedPrecedence); ok {
452+
return cp.sources, cp.err
453+
}
454+
}
455+
var precedence []bindSource
456+
var tagFound bool
457+
for i := range t.NumField() {
458+
if tag := t.Field(i).Tag.Get("binding_source"); tag != "" {
459+
if tagFound {
460+
err := fmt.Errorf("multiple binding_source tags found on struct %s", t.Name())
461+
bindingPrecedenceCache.Store(t, cachedPrecedence{err: err, sources: nil})
462+
return nil, err
463+
}
464+
tagFound = true
465+
466+
parts := strings.SplitSeq(tag, ",")
467+
for p := range parts {
468+
sourceName := strings.TrimSpace(p)
469+
if sourceName == "" {
470+
continue
471+
}
472+
var source bindSource
473+
switch sourceName {
474+
case "uri":
475+
source = sourceURI
476+
case "body":
477+
source = sourceBody
478+
case "query":
479+
source = sourceQuery
480+
case "header":
481+
source = sourceHeader
482+
case "cookie":
483+
source = sourceCookie
484+
default:
485+
err := fmt.Errorf("unknown binding_source %q", sourceName)
486+
bindingPrecedenceCache.Store(t, cachedPrecedence{err: err, sources: nil})
487+
return nil, err
488+
}
489+
490+
// check for duplicates
491+
if !slices.Contains(precedence, source) {
492+
precedence = append(precedence, source)
493+
}
494+
}
495+
}
496+
}
497+
bindingPrecedenceCache.Store(t, cachedPrecedence{err: nil, sources: precedence})
498+
return precedence, nil
499+
}
500+
431501
// All binds values from URI params, the request body, the query string,
432502
// headers, and cookies into the provided struct in precedence order.
433503
// Returns *BindError on parse failure (manual mode) or *Error with status 400 (auto-handling mode).
@@ -439,18 +509,48 @@ func (b *Bind) All(out any) error {
439509

440510
outElem := outVal.Elem()
441511

442-
// Precedence: URL Params -> Body -> Query -> Headers -> Cookies
443-
sources := []func(any) error{b.URI}
512+
sources := make([]func(any) error, 0, 5)
513+
customPrecedence, err := getBindingPrecedence(outElem.Type())
514+
if err != nil {
515+
// Note: A malformed binding_source tag is a programmer error, not a client error.
516+
// Returning the raw error here bypasses b.returnErr, intentionally resulting in a 500
517+
// rather than a 400 even in auto-handling mode.
518+
return err
519+
}
444520

445-
// Check if both Body and Content-Type are set
446-
if len(b.ctx.Request().Body()) > 0 && len(b.ctx.RequestCtx().Request.Header.ContentType()) > 0 {
447-
sources = append(sources, b.Body)
521+
hasBody := len(b.ctx.Request().Body()) > 0 && len(b.ctx.RequestCtx().Request.Header.ContentType()) > 0
522+
523+
if len(customPrecedence) > 0 {
524+
for _, source := range customPrecedence {
525+
switch source {
526+
case sourceURI:
527+
sources = append(sources, b.URI)
528+
case sourceBody:
529+
if hasBody {
530+
sources = append(sources, b.Body)
531+
}
532+
case sourceQuery:
533+
sources = append(sources, b.Query)
534+
case sourceHeader:
535+
sources = append(sources, b.Header)
536+
case sourceCookie:
537+
sources = append(sources, b.Cookie)
538+
}
539+
}
540+
} else {
541+
// Precedence: URL Params -> Body -> Query -> Headers -> Cookies
542+
sources = append(sources, b.URI)
543+
544+
// Check if both Body and Content-Type are set
545+
if hasBody {
546+
sources = append(sources, b.Body)
547+
}
548+
sources = append(sources, b.Query, b.Header, b.Cookie)
448549
}
449-
sources = append(sources, b.Query, b.Header, b.Cookie)
550+
450551
prevSkip := b.shouldSkipValidation
451552
b.shouldSkipValidation = true
452553

453-
// TODO: Support custom precedence with an optional binding_source tag
454554
// TODO: Create WithOverrideEmptyValues
455555
// Bind from each source, but only update unset fields
456556
for _, bindFunc := range sources {

0 commit comments

Comments
 (0)