From 906c8640129f21511a7614717c5998852964045a Mon Sep 17 00:00:00 2001 From: Sidi Chang Date: Sat, 26 Jul 2025 23:38:01 +0900 Subject: [PATCH] feat(binding): add support for slices with BindUnmarshaler elements (#4312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue where slice/array elements implementing BindUnmarshaler interface were not properly handled. The setArray function now checks if elements implement custom unmarshaling before falling back to default type conversion. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- binding/form_mapping.go | 13 ++++++-- binding/form_mapping_test.go | 62 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 45a39e15..8841096b 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -449,9 +449,16 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { - err := setWithProperType(s, value.Index(i), field) - if err != nil { - return err + elementValue := value.Index(i) + if isSet, err := trySetCustom(s, elementValue); isSet { + if err != nil { + return err + } + } else { + err := setWithProperType(s, elementValue, field) + if err != nil { + return err + } } } return nil diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 55b967a3..0a89d7f0 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -635,3 +635,65 @@ func TestMappingCustomArrayForm(t *testing.T) { expected, _ := convertTo(val) assert.Equal(t, expected, s.FileData) } + +func TestMappingSliceOfCustomBindUnmarshalerElementsUri(t *testing.T) { + var s struct { + Items []customUnmarshalParamType `uri:"items"` + } + err := mappingByPtr(&s, formSource{"items": {"http:path1:name1", "https:path2:name2"}}, "uri") + require.NoError(t, err) + + assert.Len(t, s.Items, 2) + assert.Equal(t, "http", s.Items[0].Protocol) + assert.Equal(t, "path1", s.Items[0].Path) + assert.Equal(t, "name1", s.Items[0].Name) + assert.Equal(t, "https", s.Items[1].Protocol) + assert.Equal(t, "path2", s.Items[1].Path) + assert.Equal(t, "name2", s.Items[1].Name) +} + +func TestMappingSliceOfCustomBindUnmarshalerElementsForm(t *testing.T) { + var s struct { + Items []customUnmarshalParamType `form:"items"` + } + err := mappingByPtr(&s, formSource{"items": {"tcp:socket1:server", "udp:socket2:client"}}, "form") + require.NoError(t, err) + + assert.Len(t, s.Items, 2) + assert.Equal(t, "tcp", s.Items[0].Protocol) + assert.Equal(t, "socket1", s.Items[0].Path) + assert.Equal(t, "server", s.Items[0].Name) + assert.Equal(t, "udp", s.Items[1].Protocol) + assert.Equal(t, "socket2", s.Items[1].Path) + assert.Equal(t, "client", s.Items[1].Name) +} + +func TestMappingArrayOfCustomBindUnmarshalerElementsUri(t *testing.T) { + var s struct { + Items [2]customUnmarshalParamType `uri:"items"` + } + err := mappingByPtr(&s, formSource{"items": {"grpc:service1:auth", "rest:service2:data"}}, "uri") + require.NoError(t, err) + + assert.Equal(t, "grpc", s.Items[0].Protocol) + assert.Equal(t, "service1", s.Items[0].Path) + assert.Equal(t, "auth", s.Items[0].Name) + assert.Equal(t, "rest", s.Items[1].Protocol) + assert.Equal(t, "service2", s.Items[1].Path) + assert.Equal(t, "data", s.Items[1].Name) +} + +func TestMappingArrayOfCustomBindUnmarshalerElementsForm(t *testing.T) { + var s struct { + Items [2]customUnmarshalParamType `form:"items"` + } + err := mappingByPtr(&s, formSource{"items": {"ws:chat:room1", "wss:chat:room2"}}, "form") + require.NoError(t, err) + + assert.Equal(t, "ws", s.Items[0].Protocol) + assert.Equal(t, "chat", s.Items[0].Path) + assert.Equal(t, "room1", s.Items[0].Name) + assert.Equal(t, "wss", s.Items[1].Protocol) + assert.Equal(t, "chat", s.Items[1].Path) + assert.Equal(t, "room2", s.Items[1].Name) +}