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
74 changes: 74 additions & 0 deletions pkg/app/server/binding/binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1747,3 +1747,77 @@ func Benchmark_Binding(b *testing.B) {
}
}
}

// TestBind_AnonymousFieldWithDefaultTag tests that default tag values don't override
// JSON-provided values when using anonymous struct embedding with multiple tags
func TestBind_AnonymousFieldWithDefaultTag(t *testing.T) {
type PageInfo struct {
Page int `json:"page" form:"page" query:"page" default:"1"`
Limit int `json:"limit" form:"limit" query:"limit" default:"15"`
}
type Req struct {
Keyword string `json:"keyword"`
PageInfo
}

// Test 1: JSON values should override defaults
req := protocol.NewRequest("POST", "/search", nil)
req.SetBody([]byte(`{"keyword":"test","page":2,"limit":5}`))
req.Header.SetContentTypeBytes([]byte("application/json"))
req.Header.SetContentLength(37)

var r Req
err := DefaultBinder().Bind(req, &r, nil)
assert.Nil(t, err)
assert.DeepEqual(t, "test", r.Keyword)
assert.DeepEqual(t, 2, r.Page) // Should use JSON value, not default
assert.DeepEqual(t, 5, r.Limit) // Should use JSON value, not default

// Test 2: Empty JSON should use defaults
req2 := protocol.NewRequest("POST", "/search", nil)
req2.SetBody([]byte(`{"keyword":"test"}`))
req2.Header.SetContentTypeBytes([]byte("application/json"))
req2.Header.SetContentLength(20)

var r2 Req
err2 := DefaultBinder().Bind(req2, &r2, nil)
assert.Nil(t, err2)
assert.DeepEqual(t, "test", r2.Keyword)
assert.DeepEqual(t, 1, r2.Page) // Should use default value
assert.DeepEqual(t, 15, r2.Limit) // Should use default value

// Test 3: Query values should work
req3 := protocol.NewRequest("POST", "/search?page=3&limit=4", nil)
req3.Header.SetContentTypeBytes([]byte("application/x-www-form-urlencoded"))

var r3 Req
err3 := DefaultBinder().Bind(req3, &r3, nil)
assert.Nil(t, err3)
assert.DeepEqual(t, 3, r3.Page) // Should use query value
assert.DeepEqual(t, 4, r3.Limit) // Should use query value

// Test 4: Nested anonymous structs (multi-level)
type BaseInfo struct {
Page int `json:"page" form:"page" default:"1"`
}
type ExtInfo struct {
BaseInfo
Limit int `json:"limit" form:"limit" default:"20"`
}
type Req2 struct {
Keyword string `json:"keyword"`
ExtInfo
}

req4 := protocol.NewRequest("POST", "/search", nil)
req4.SetBody([]byte(`{"keyword":"nested","page":10,"limit":30}`))
req4.Header.SetContentTypeBytes([]byte("application/json"))
req4.Header.SetContentLength(44)

var r4 Req2
err4 := DefaultBinder().Bind(req4, &r4, nil)
assert.Nil(t, err4)
assert.DeepEqual(t, "nested", r4.Keyword)
assert.DeepEqual(t, 10, r4.Page) // Should use JSON value from nested struct
assert.DeepEqual(t, 30, r4.Limit) // Should use JSON value, not default
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa
var text string
var exist bool
var defaultValue string
var defaultDisabled bool // Flag to prevent default value from overriding JSON-provided values

for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
if tagInfo.Key == jsonTag {
Expand All @@ -77,14 +79,19 @@ func (d *baseTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Pa
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request body does not have this parameter '%s'", tagInfo.Value, tagInfo.JSONName)
}
// If field exists in JSON body, disable default value to prevent override
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
defaultDisabled = true
}
}
continue
}
text, exist = tagInfo.Getter(req, params, tagInfo.Value)
defaultValue = tagInfo.Default
// Only set defaultValue if not disabled by JSON tag and not already set
if !defaultDisabled && len(defaultValue) == 0 {
defaultValue = tagInfo.Default
}
if exist {
err = nil
break
Expand Down
6 changes: 5 additions & 1 deletion pkg/app/server/binding/internal/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ func getFieldDecoder(pInfo parentInfos, field reflect.StructField, index int, by
idxes = append(idxes, index)
pInfo.Indexes = idxes
pInfo.Types = append(pInfo.Types, el)
pInfo.JSONName = newParentJSONName
// For anonymous embedded structs, keep the parent JSONName at the same level
// because Go's JSON marshaling flattens anonymous struct fields to the parent level
if !field.Anonymous {
pInfo.JSONName = newParentJSONName
}
dec, err := getFieldDecoder(pInfo, el.Field(i), i, byTag, config)
if err != nil {
return nil, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Par
var text string
var exist bool
var defaultValue string
var defaultDisabled bool // Flag to prevent default value from overriding JSON-provided values

for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
if tagInfo.Key == jsonTag {
Expand All @@ -69,14 +71,19 @@ func (d *mapTypeFieldTextDecoder) Decode(req *protocol.Request, params param.Par
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", tagInfo.Value)
}
// If field exists in JSON body, disable default value to prevent override
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
defaultDisabled = true
}
}
continue
}
text, exist = tagInfo.Getter(req, params, tagInfo.Value)
defaultValue = tagInfo.Default
// Only set defaultValue if not disabled by JSON tag and not already set
if !defaultDisabled && len(defaultValue) == 0 {
defaultValue = tagInfo.Default
}
if exist {
err = nil
break
Expand Down
11 changes: 9 additions & 2 deletions pkg/app/server/binding/internal/decoder/slice_type_decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
var defaultValue string
var bindRawBody bool
var isDefault bool
var defaultDisabled bool // Flag to prevent default value from overriding JSON-provided values

for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
if tagInfo.Key == jsonTag {
Expand All @@ -72,8 +74,10 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", tagInfo.Value)
}
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) { //
// If field exists in JSON body, disable default value to prevent override
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
defaultDisabled = true
}
}
continue
Expand All @@ -82,7 +86,10 @@ func (d *sliceTypeFieldTextDecoder) Decode(req *protocol.Request, params param.P
bindRawBody = true
}
texts = tagInfo.SliceGetter(req, params, tagInfo.Value)
defaultValue = tagInfo.Default
// Only set defaultValue if not disabled by JSON tag and not already set
if !defaultDisabled && len(defaultValue) == 0 {
defaultValue = tagInfo.Default
}
if len(texts) != 0 {
err = nil
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param.
var text string
var exist bool
var defaultValue string
var defaultDisabled bool // Flag to prevent default value from overriding JSON-provided values

for _, tagInfo := range d.tagInfos {
if tagInfo.Skip || tagInfo.Key == jsonTag || tagInfo.Key == fileNameTag {
if tagInfo.Key == jsonTag {
Expand All @@ -46,14 +48,19 @@ func (d *structTypeFieldTextDecoder) Decode(req *protocol.Request, params param.
} else {
err = fmt.Errorf("'%s' field is a 'required' parameter, but the request does not have this parameter", tagInfo.Value)
}
// If field exists in JSON body, disable default value to prevent override
if len(tagInfo.Default) != 0 && keyExist(req, tagInfo) {
defaultValue = ""
defaultDisabled = true
}
}
continue
}
text, exist = tagInfo.Getter(req, params, tagInfo.Value)
defaultValue = tagInfo.Default
// Only set defaultValue if not disabled by JSON tag and not already set
if !defaultDisabled && len(defaultValue) == 0 {
defaultValue = tagInfo.Default
}
if exist {
err = nil
break
Expand Down