pull/3373/merge
Edvard 2025-04-03 19:15:08 -03:00 committed by GitHub
commit 0592b39634
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 106 additions and 0 deletions

57
bind.go
View File

@ -1,6 +1,8 @@
package fiber
import (
"reflect"
"github.com/gofiber/fiber/v3/binder"
"github.com/gofiber/utils/v2"
)
@ -273,3 +275,58 @@ func (b *Bind) Body(out any) error {
// No suitable content type found
return ErrUnprocessableEntity
}
func (b *Bind) All(out any) error {
outVal := reflect.ValueOf(out)
if outVal.Kind() != reflect.Ptr || outVal.Elem().Kind() != reflect.Struct {
return ErrUnprocessableEntity
}
outElem := outVal.Elem()
// Precedence: URL Params -> Body -> Query -> Headers -> Cookies
sources := []func(any) error{
b.URI,
b.Body,
b.Query,
b.Header,
b.Cookie,
}
tempStruct := reflect.New(outElem.Type()).Interface()
// TODO: Support custom precedence with an optional binding_source tag
// TODO: Create WithOverrideEmptyValues
// Bind from each source, but only update unset fields
for _, bindFunc := range sources {
if err := bindFunc(tempStruct); err != nil {
return err
}
tempStructVal := reflect.ValueOf(tempStruct).Elem()
mergeStruct(outElem, tempStructVal)
}
return nil
}
func mergeStruct(dst, src reflect.Value) {
dstFields := dst.NumField()
for i := 0; i < dstFields; i++ {
dstField := dst.Field(i)
srcField := src.Field(i)
// Skip if the destination field is already set
if isZero(dstField.Interface()) {
if dstField.CanSet() && srcField.IsValid() {
dstField.Set(srcField)
}
}
}
}
func isZero(value any) bool {
v := reflect.ValueOf(value)
return v.IsZero()
}

View File

@ -1885,3 +1885,52 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) {
testDecodeParser(MIMEApplicationForm, "body_param=body_param")
testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"body_param\"\r\n\r\nbody_param\r\n--b--")
}
func TestBind_All(t *testing.T) {
type User struct {
ID int `param:"id" query:"id" json:"id" form:"id"`
Name string `query:"name" json:"name" form:"name"`
Email string `json:"email" form:"email"`
Role string `header:"x-user-role"`
SessionID string `json:"SessionID" cookie:"session_id"`
Avatar *multipart.FileHeader `form:"avatar"`
}
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
tests := []struct {
name string
bind *Bind
out any
wantErr bool
}{
{
name: "Invalid output type",
bind: &Bind{},
out: 123,
wantErr: true,
},
{
// Validate this test case
name: "Successful binding",
bind: &Bind{
ctx: c,
},
out: &User{
ID: 10,
Name: "Alice",
Email: "alice@example.com",
Role: "admin",
SessionID: "abc123",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.bind.All(tt.out); (err != nil) != tt.wantErr {
t.Errorf("Bind.All() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}