mirror of https://github.com/gofiber/fiber.git
Merge 97da803d61
into 551570326c
commit
0592b39634
57
bind.go
57
bind.go
|
@ -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()
|
||||
}
|
||||
|
|
49
bind_test.go
49
bind_test.go
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue