This is an automated email from the ASF dual-hosted git repository.

Alanxtl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/develop by this push:
     new e94fa7ea3 fix(generic): reject unsupported generic modes in filters 
(#3501)
e94fa7ea3 is described below

commit e94fa7ea3a408baabd1829c48bec7ec9be0b886b
Author: XiaoFei <[email protected]>
AuthorDate: Wed Aug 5 07:43:35 2026 +0800

    fix(generic): reject unsupported generic modes in filters (#3501)
---
 filter/generic/filter.go              |  33 +++--
 filter/generic/filter_test.go         | 245 ++++++++++++++++++++++++++++++++++
 filter/generic/service_filter.go      |  29 ++--
 filter/generic/service_filter_test.go | 136 ++++++++++++++++++-
 filter/generic/util.go                |  71 ++++++++--
 filter/generic/util_test.go           |  45 ++++++-
 6 files changed, 515 insertions(+), 44 deletions(-)

diff --git a/filter/generic/filter.go b/filter/generic/filter.go
index 8a865ab33..bfada008b 100644
--- a/filter/generic/filter.go
+++ b/filter/generic/filter.go
@@ -62,6 +62,16 @@ func newGenericFilter() filter.Filter {
 
 // Invoke turns the parameters to map for generic method
 func (f *genericFilter) Invoke(ctx context.Context, invoker base.Invoker, inv 
base.Invocation) result.Result {
+       configuredGeneric := invoker.GetURL().GetParam(constant.GenericKey, "")
+       if isGenericDisabled(configuredGeneric) {
+               return invoker.Invoke(ctx, inv)
+       }
+
+       generic, g, err := resolveGeneralizer(configuredGeneric, inv)
+       if err != nil {
+               return &result.RPCResult{Err: err}
+       }
+
        if isCallingToGenericService(invoker, inv) {
 
                mtdName := inv.MethodName()
@@ -70,13 +80,7 @@ func (f *genericFilter) Invoke(ctx context.Context, invoker 
base.Invoker, inv ba
                types := make([]string, 0, len(oldArgs))
                args := make([]hessian.Object, 0, len(oldArgs))
 
-               // get generic info from attachments of invocation, the default 
value is "true"
-               generic := 
inv.GetAttachmentWithDefaultValue(constant.GenericKey, 
constant.GenericSerializationDefault)
-               // get generalizer according to value in the `generic`
-               g := getGeneralizer(generic)
-
                for _, arg := range oldArgs {
-                       // use the default generalizer(MapGeneralizer)
                        typ, err := g.GetType(arg)
                        if err != nil {
                                logger.Errorf("[Filter][Generic] failed to get 
type, err=%v", err)
@@ -111,7 +115,7 @@ func (f *genericFilter) Invoke(ctx context.Context, invoker 
base.Invoker, inv ba
                        invocation.WithAttachments(inv.Attachments()),
                        invocation.WithReply(reply),
                )
-               newIvc.Attachments()[constant.GenericKey] = 
invoker.GetURL().GetParam(constant.GenericKey, "")
+               newIvc.SetAttachment(constant.GenericKey, generic)
 
                // Copy CallType attribute from original invocation for Triple 
protocol support
                // If not present, set default to CallUnary for generic calls
@@ -139,7 +143,7 @@ func (f *genericFilter) Invoke(ctx context.Context, invoker 
base.Invoker, inv ba
                        invocation.WithAttachments(inv.Attachments()),
                        invocation.WithReply(reply),
                )
-               newIvc.Attachments()[constant.GenericKey] = 
invoker.GetURL().GetParam(constant.GenericKey, "")
+               newIvc.SetAttachment(constant.GenericKey, generic)
 
                // Set CallType for Triple protocol support
                if callType, ok := inv.GetAttribute(constant.CallTypeKey); ok {
@@ -164,7 +168,14 @@ func (f *genericFilter) OnResponse(_ context.Context, res 
result.Result, invoker
        }
 
        // Check if this is a generic invocation
-       if !isGeneric(invoker.GetURL().GetParam(constant.GenericKey, "")) {
+       configuredGeneric := invoker.GetURL().GetParam(constant.GenericKey, "")
+       if isGenericDisabled(configuredGeneric) {
+               return res
+       }
+       _, g, err := resolveGeneralizer(configuredGeneric, inv)
+       if err != nil {
+               res.SetError(err)
+               res.SetResult(nil)
                return res
        }
 
@@ -196,10 +207,6 @@ func (f *genericFilter) OnResponse(_ context.Context, res 
result.Result, invoker
        // Get the element type that the pointer points to
        replyElemType := replyValue.Elem().Type()
 
-       // Get the generalizer based on the generic serialization type
-       generic := invoker.GetURL().GetParam(constant.GenericKey, 
constant.GenericSerializationDefault)
-       g := getGeneralizer(generic)
-
        // Realize the map/slice to the target struct using shared helper
        realized, err := realizeResult(data, replyElemType, g)
        if err != nil {
diff --git a/filter/generic/filter_test.go b/filter/generic/filter_test.go
index 57dbdc440..5328b6ab5 100644
--- a/filter/generic/filter_test.go
+++ b/filter/generic/filter_test.go
@@ -35,12 +35,21 @@ import (
 import (
        "dubbo.apache.org/dubbo-go/v3/common"
        "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/filter/generic/generalizer"
        "dubbo.apache.org/dubbo-go/v3/protocol/base"
        "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
        "dubbo.apache.org/dubbo-go/v3/protocol/mock"
        "dubbo.apache.org/dubbo-go/v3/protocol/result"
 )
 
+type mockGenericPOJO struct {
+       Name string
+}
+
+func (*mockGenericPOJO) JavaClassName() string {
+       return "org.apache.dubbo.test.MockGenericPOJO"
+}
+
 // test isCallingToGenericService branch
 func TestFilter_Invoke(t *testing.T) {
        invokeUrl := common.NewURLWithOptions(
@@ -103,6 +112,224 @@ func TestFilter_InvokeWithGenericCall(t *testing.T) {
        assert.NotNil(t, r)
 }
 
+func TestFilter_InvokeUsesConfiguredGenericMode(t *testing.T) {
+       tests := []struct {
+               name       string
+               mode       string
+               arg        any
+               resultType any
+       }{
+               {
+                       name:       "map",
+                       mode:       constant.GenericSerializationDefault,
+                       arg:        &mockGenericPOJO{Name: "map"},
+                       resultType: map[string]any{},
+               },
+               {
+                       name:       "bean",
+                       mode:       constant.GenericSerializationBean,
+                       arg:        &mockGenericPOJO{Name: "bean"},
+                       resultType: &generalizer.JavaBeanDescriptor{},
+               },
+               {
+                       name:       "gson",
+                       mode:       constant.GenericSerializationGson,
+                       arg:        &mockGenericPOJO{Name: "gson"},
+                       resultType: "",
+               },
+               {
+                       name:       "protobuf-json",
+                       mode:       constant.GenericSerializationProtobufJson,
+                       arg:        &generalizer.RequestType{Id: 1},
+                       resultType: "",
+               },
+               {
+                       name:       "protobuf-legacy",
+                       mode:       constant.GenericSerializationProtobuf,
+                       arg:        &mockGenericPOJO{Name: "protobuf"},
+                       resultType: map[string]any{},
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       invokeURL := common.NewURLWithOptions(
+                               common.WithParams(url.Values{}),
+                               common.WithParamsValue(constant.GenericKey, 
tt.mode),
+                       )
+                       inv := invocation.NewRPCInvocation("Hello", 
[]any{tt.arg}, nil)
+
+                       ctrl := gomock.NewController(t)
+                       mockInvoker := mock.NewMockInvoker(ctrl)
+                       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(2)
+                       mockInvoker.EXPECT().Invoke(gomock.Any(), 
gomock.Any()).DoAndReturn(
+                               func(_ context.Context, outgoing 
base.Invocation) result.Result {
+                                       assert.Equal(t, tt.mode, 
outgoing.GetAttachmentWithDefaultValue(constant.GenericKey, ""))
+                                       assert.IsType(t, tt.resultType, 
outgoing.Arguments()[2].([]hessian.Object)[0])
+                                       return &result.RPCResult{}
+                               },
+                       )
+
+                       res := (&genericFilter{}).Invoke(context.Background(), 
mockInvoker, inv)
+
+                       require.NoError(t, res.Error())
+               })
+       }
+}
+
+func TestFilter_InvokeInvocationGenericModeOverridesURL(t *testing.T) {
+       invokeURL := common.NewURLWithOptions(
+               common.WithParams(url.Values{}),
+               common.WithParamsValue(constant.GenericKey, 
constant.GenericSerializationDefault),
+       )
+       inv := invocation.NewRPCInvocation("Hello", 
[]any{&mockGenericPOJO{Name: "bean"}}, map[string]any{
+               constant.GenericKey: constant.GenericSerializationBean,
+       })
+
+       ctrl := gomock.NewController(t)
+       mockInvoker := mock.NewMockInvoker(ctrl)
+       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(2)
+       mockInvoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).DoAndReturn(
+               func(_ context.Context, outgoing base.Invocation) result.Result 
{
+                       assert.Equal(t, constant.GenericSerializationBean,
+                               
outgoing.GetAttachmentWithDefaultValue(constant.GenericKey, ""))
+                       assert.IsType(t, &generalizer.JavaBeanDescriptor{}, 
outgoing.Arguments()[2].([]hessian.Object)[0])
+                       return &result.RPCResult{}
+               },
+       )
+
+       res := (&genericFilter{}).Invoke(context.Background(), mockInvoker, inv)
+
+       require.NoError(t, res.Error())
+}
+
+func TestFilter_InvokeWithUnsupportedGenericMode(t *testing.T) {
+       filter := &genericFilter{}
+
+       tests := []struct {
+               name    string
+               generic string
+       }{
+               {
+                       name:    "unknown",
+                       generic: "unsupported_type",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       invokeURL := common.NewURLWithOptions(
+                               common.WithParams(url.Values{}),
+                               common.WithParamsValue(constant.GenericKey, 
tt.generic),
+                       )
+                       inv := invocation.NewRPCInvocation("Hello", 
[]any{"arg1"}, nil)
+
+                       ctrl := gomock.NewController(t)
+                       mockInvoker := mock.NewMockInvoker(ctrl)
+                       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(1)
+                       mockInvoker.EXPECT().Invoke(gomock.Any(), 
gomock.Any()).Times(0)
+
+                       res := filter.Invoke(context.Background(), mockInvoker, 
inv)
+
+                       require.EqualError(t, res.Error(), `unsupported generic 
mode "`+tt.generic+`"`)
+                       assert.Nil(t, res.Result())
+               })
+       }
+}
+
+func TestFilter_InvokeRejectsUnsupportedInvocationGenericMode(t *testing.T) {
+       filter := &genericFilter{}
+       invokeURL := common.NewURLWithOptions(
+               common.WithParams(url.Values{}),
+               common.WithParamsValue(constant.GenericKey, 
constant.GenericSerializationDefault),
+       )
+
+       for _, generic := range []string{"unsupported_type"} {
+               t.Run(generic, func(t *testing.T) {
+                       inv := invocation.NewRPCInvocation("Hello", 
[]any{"arg1"}, map[string]any{
+                               constant.GenericKey: generic,
+                       })
+                       ctrl := gomock.NewController(t)
+                       mockInvoker := mock.NewMockInvoker(ctrl)
+                       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(1)
+                       mockInvoker.EXPECT().Invoke(gomock.Any(), 
gomock.Any()).Times(0)
+
+                       res := filter.Invoke(context.Background(), mockInvoker, 
inv)
+
+                       require.EqualError(t, res.Error(), `unsupported generic 
mode "`+generic+`"`)
+                       assert.Nil(t, res.Result())
+               })
+       }
+}
+
+func TestFilter_InvokeEmptyInvocationGenericModeUsesConfiguredMode(t 
*testing.T) {
+       filter := &genericFilter{}
+       invokeURL := common.NewURLWithOptions(
+               common.WithParams(url.Values{}),
+               common.WithParamsValue(constant.GenericKey, 
constant.GenericSerializationDefault),
+       )
+       inv := invocation.NewRPCInvocation("Hello", 
[]any{&mockGenericPOJO{Name: "map"}}, map[string]any{
+               constant.GenericKey: "",
+       })
+
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       mockInvoker := mock.NewMockInvoker(ctrl)
+       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(2)
+       mockInvoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).DoAndReturn(
+               func(_ context.Context, outgoing base.Invocation) result.Result 
{
+                       assert.Equal(t, constant.GenericSerializationDefault,
+                               
outgoing.GetAttachmentWithDefaultValue(constant.GenericKey, ""))
+                       assert.IsType(t, map[string]any{}, 
outgoing.Arguments()[2].([]hessian.Object)[0])
+                       return &result.RPCResult{}
+               },
+       )
+
+       res := filter.Invoke(context.Background(), mockInvoker, inv)
+
+       require.NoError(t, res.Error())
+}
+
+func TestFilter_InvokeWithoutGenericModePassesThrough(t *testing.T) {
+       filter := &genericFilter{}
+       invokeURL := common.NewURLWithOptions(common.WithParams(url.Values{}))
+       inv := invocation.NewRPCInvocation("Hello", []any{"arg1"}, nil)
+
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       mockInvoker := mock.NewMockInvoker(ctrl)
+       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(1)
+       mockInvoker.EXPECT().Invoke(gomock.Any(), 
gomock.Eq(inv)).Return(&result.RPCResult{Rest: "ok"})
+
+       res := filter.Invoke(context.Background(), mockInvoker, inv)
+
+       require.NoError(t, res.Error())
+       assert.Equal(t, "ok", res.Result())
+}
+
+func TestFilter_InvokeWithGenericFalsePassesThrough(t *testing.T) {
+       filter := &genericFilter{}
+       invokeURL := common.NewURLWithOptions(
+               common.WithParams(url.Values{}),
+               common.WithParamsValue(constant.GenericKey, "false"),
+       )
+       inv := invocation.NewRPCInvocation("Hello", []any{"arg1"}, nil)
+
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       mockInvoker := mock.NewMockInvoker(ctrl)
+       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(1)
+       mockInvoker.EXPECT().Invoke(gomock.Any(), 
gomock.Eq(inv)).Return(&result.RPCResult{Rest: "ok"})
+
+       res := filter.Invoke(context.Background(), mockInvoker, inv)
+
+       require.NoError(t, res.Error())
+       assert.Equal(t, "ok", res.Result())
+}
+
 // mockUser is a test struct for OnResponse deserialization
 type mockUser struct {
        Name    string
@@ -293,6 +520,24 @@ func TestFilter_OnResponse_WithSliceDeserialization(t 
*testing.T) {
        assert.Equal(t, 25, users[1].Age)
 }
 
+func TestFilter_OnResponse_WithUnsupportedGenericMode(t *testing.T) {
+       invokeURL := common.NewURLWithOptions(
+               common.WithParams(url.Values{}),
+               common.WithParamsValue(constant.GenericKey, "unsupported_type"),
+       )
+       filter := &genericFilter{}
+
+       ctrl := gomock.NewController(t)
+       mockInvoker := mock.NewMockInvoker(ctrl)
+       mockInvoker.EXPECT().GetURL().Return(invokeURL).Times(1)
+
+       res := &result.RPCResult{Rest: map[string]any{"name": "test"}}
+       newRes := filter.OnResponse(context.Background(), res, mockInvoker, 
invocation.NewRPCInvocation("Hello", nil, nil))
+
+       require.EqualError(t, newRes.Error(), `unsupported generic mode 
"unsupported_type"`)
+       assert.Nil(t, newRes.Result())
+}
+
 // TestFilter_OnResponse_DeserializationError tests that OnResponse gracefully 
handles
 // deserialization failures by logging a warning and returning the original 
result.
 func TestFilter_OnResponse_DeserializationError(t *testing.T) {
diff --git a/filter/generic/service_filter.go b/filter/generic/service_filter.go
index 2b39df173..64619b56c 100644
--- a/filter/generic/service_filter.go
+++ b/filter/generic/service_filter.go
@@ -70,6 +70,11 @@ func (f *genericServiceFilter) Invoke(ctx context.Context, 
invoker base.Invoker,
                return invoker.Invoke(ctx, inv)
        }
 
+       _, g, err := resolveGenericInvocationGeneralizer(inv)
+       if err != nil {
+               return &result.RPCResult{Err: err}
+       }
+
        // get real invocation info from the generic invocation
        mtdName := inv.Arguments()[0].(string)
        // types are not required in dubbo-go, for dubbo-go client to dubbo-go 
server, types could be nil
@@ -89,15 +94,13 @@ func (f *genericServiceFilter) Invoke(ctx context.Context, 
invoker base.Invoker,
        }
 
        argsType := method.ArgsType()
-       if err := validateGenericArgs(method.IsVariadic(), len(argsType), 
len(args), mtdName); err != nil {
+       err = validateGenericArgs(method.IsVariadic(), len(argsType), 
len(args), mtdName)
+       if err != nil {
                return &result.RPCResult{Err: err}
        }
 
-       // get generic info from attachments of invocation, the default value 
is "true"
-       generic := inv.GetAttachmentWithDefaultValue(constant.GenericKey, 
constant.GenericSerializationDefault)
-       // get generalizer according to value in the `generic`
        // realize
-       newArgs, err := realizeInvocationArgs(getGeneralizer(generic), 
argsType, args, method.IsVariadic(), types)
+       newArgs, err := realizeInvocationArgs(g, argsType, args, 
method.IsVariadic(), types)
        if err != nil {
                return &result.RPCResult{Err: err}
        }
@@ -411,11 +414,17 @@ func unwrapToSlice(obj hessian.Object) []hessian.Object {
 }
 
 func (f *genericServiceFilter) OnResponse(_ context.Context, result 
result.Result, _ base.Invoker, inv base.Invocation) result.Result {
-       if inv.IsGenericInvocation() && result.Result() != nil {
-               // get generic info from attachments of invocation, the default 
value is "true"
-               generic := 
inv.GetAttachmentWithDefaultValue(constant.GenericKey, 
constant.GenericSerializationDefault)
-               // get generalizer according to value in the `generic`
-               g := getGeneralizer(generic)
+       if !inv.IsGenericInvocation() {
+               return result
+       }
+
+       _, g, err := resolveGenericInvocationGeneralizer(inv)
+       if err != nil {
+               result.SetError(err)
+               result.SetResult(nil)
+               return result
+       }
+       if result.Result() != nil {
 
                obj, err := g.Generalize(result.Result())
                if err != nil {
diff --git a/filter/generic/service_filter_test.go 
b/filter/generic/service_filter_test.go
index 84756f42c..4bee9cfd3 100644
--- a/filter/generic/service_filter_test.go
+++ b/filter/generic/service_filter_test.go
@@ -125,7 +125,7 @@ func TestServiceFilter_Invoke(t *testing.T) {
        require.NoError(t, err)
 
        // mock
-       mockInvoker.EXPECT().GetURL().Return(ivkUrl).Times(3)
+       mockInvoker.EXPECT().GetURL().Return(ivkUrl).Times(4)
 
        // invoke a method without errors using default generalization
        invocation4 := invocation.NewRPCInvocation(constant.Generic,
@@ -154,6 +154,15 @@ func TestServiceFilter_Invoke(t *testing.T) {
                }, map[string]any{
                        constant.GenericKey: "true",
                })
+       // invoke a legacy Triple generic call with protobuf transport marker 
and Hessian args
+       invocation7 := invocation.NewRPCInvocation(constant.Generic,
+               []any{
+                       "Hello",
+                       []string{"java.lang.String"},
+                       []hessian.Object{"world"},
+               }, map[string]any{
+                       constant.GenericKey: 
constant.GenericSerializationProtobuf,
+               })
        // invoke a method without errors using protobuf-json generalization
        //invocation7 := invocation.NewRPCInvocation(constant.Generic,
        //      []any{
@@ -202,6 +211,10 @@ func TestServiceFilter_Invoke(t *testing.T) {
                "the number of args(=2) is not matched with \"Hello\" method",
                fmt.Sprintf("%v", invokeResult.Error()))
 
+       invokeResult = filter.Invoke(context.Background(), mockInvoker, 
invocation7)
+       require.NoError(t, invokeResult.Error())
+       assert.Equal(t, "hello, world", invokeResult.Result())
+
        //result = filter.Invoke(context.Background(), mockInvoker, invocation7)
        //assert.Equal(t, int64(200), 
result.Result().(*generalizer.ResponseType).GetCode())
        //assert.Equal(t, int64(1), 
result.Result().(*generalizer.ResponseType).GetId())
@@ -231,6 +244,127 @@ func TestServiceFilter_OnResponse(t *testing.T) {
        assert.Equal(t, "result", response.Result())
 }
 
+func TestServiceFilter_InvokeWithUnsupportedGenericMode(t *testing.T) {
+       filter := &genericServiceFilter{}
+       for _, generic := range []string{"unsupported_type"} {
+               t.Run(generic, func(t *testing.T) {
+                       inv := invocation.NewRPCInvocation(constant.Generic,
+                               []any{
+                                       "Hello",
+                                       []string{"java.lang.String"},
+                                       []hessian.Object{"world"},
+                               }, map[string]any{
+                                       constant.GenericKey: generic,
+                               })
+
+                       ctrl := gomock.NewController(t)
+                       mockInvoker := mock.NewMockInvoker(ctrl)
+                       mockInvoker.EXPECT().Invoke(gomock.Any(), 
gomock.Any()).Times(0)
+
+                       res := filter.Invoke(context.Background(), mockInvoker, 
inv)
+
+                       require.EqualError(t, res.Error(), `unsupported generic 
mode "`+generic+`"`)
+                       assert.Nil(t, res.Result())
+               })
+       }
+}
+
+func TestServiceFilter_InvokeWithEmptyGenericModeUsesDefault(t *testing.T) {
+       filter := &genericServiceFilter{}
+       service := &MockHelloService{}
+       ivkURL := common.NewURLWithOptions(
+               common.WithProtocol("test-empty-generic"),
+               common.WithParams(url.Values{}),
+               common.WithParamsValue(constant.InterfaceKey, 
service.Reference()),
+               common.WithParamsValue(constant.GenericKey, 
constant.GenericSerializationDefault),
+       )
+       _, err := 
common.ServiceMap.Register(ivkURL.GetParam(constant.InterfaceKey, ""),
+               ivkURL.Protocol,
+               "",
+               "",
+               service)
+       require.NoError(t, err)
+       t.Cleanup(func() {
+               _ = 
common.ServiceMap.UnRegister(ivkURL.GetParam(constant.InterfaceKey, ""), 
ivkURL.Protocol, ivkURL.ServiceKey())
+       })
+
+       inv := invocation.NewRPCInvocation(constant.Generic,
+               []any{
+                       "Hello",
+                       []string{"java.lang.String"},
+                       []hessian.Object{"world"},
+               }, map[string]any{
+                       constant.GenericKey: "",
+               })
+
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       mockInvoker := mock.NewMockInvoker(ctrl)
+       mockInvoker.EXPECT().GetURL().Return(ivkURL).AnyTimes()
+       mockInvoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).DoAndReturn(
+               func(_ context.Context, outgoing base.Invocation) result.Result 
{
+                       assert.Equal(t, "Hello", outgoing.MethodName())
+                       assert.Equal(t, "world", outgoing.Arguments()[0])
+                       return &result.RPCResult{Rest: "hello, world"}
+               },
+       )
+
+       res := filter.Invoke(context.Background(), mockInvoker, inv)
+
+       require.NoError(t, res.Error())
+       assert.Equal(t, "hello, world", res.Result())
+}
+
+func TestServiceFilter_OnResponseWithUnsupportedGenericMode(t *testing.T) {
+       filter := &genericServiceFilter{}
+       inv := invocation.NewRPCInvocation(constant.Generic,
+               []any{
+                       "Hello",
+                       []string{"java.lang.String"},
+                       []hessian.Object{"world"},
+               }, map[string]any{
+                       constant.GenericKey: "unsupported_type",
+               })
+       rpcResult := &result.RPCResult{Rest: "result"}
+
+       res := filter.OnResponse(context.Background(), rpcResult, nil, inv)
+
+       require.EqualError(t, res.Error(), `unsupported generic mode 
"unsupported_type"`)
+       assert.Nil(t, res.Result())
+}
+
+func TestServiceFilter_OnResponseWithEmptyGenericModeUsesDefault(t *testing.T) 
{
+       filter := &genericServiceFilter{}
+       inv := invocation.NewRPCInvocation(constant.Generic,
+               []any{
+                       "Hello",
+                       []string{"java.lang.String"},
+                       []hessian.Object{"world"},
+               }, map[string]any{
+                       constant.GenericKey: "",
+               })
+       rpcResult := &result.RPCResult{Rest: "result"}
+
+       res := filter.OnResponse(context.Background(), rpcResult, nil, inv)
+
+       require.NoError(t, res.Error())
+       assert.Equal(t, "result", res.Result())
+}
+
+func TestServiceFilter_OnResponseRejectsUnsupportedModeWithNilResult(t 
*testing.T) {
+       filter := &genericServiceFilter{}
+       inv := invocation.NewRPCInvocation(constant.Generic,
+               []any{"Hello", []string{"java.lang.String"}, 
[]hessian.Object{"world"}},
+               map[string]any{constant.GenericKey: "unsupported_type"},
+       )
+
+       res := filter.OnResponse(context.Background(), &result.RPCResult{}, 
nil, inv)
+
+       require.EqualError(t, res.Error(), `unsupported generic mode 
"unsupported_type"`)
+       assert.Nil(t, res.Result())
+}
+
 func TestServiceFilter_InvokeVariadic(t *testing.T) {
        filter := &genericServiceFilter{}
 
diff --git a/filter/generic/util.go b/filter/generic/util.go
index 3469111ae..cab2c2ede 100644
--- a/filter/generic/util.go
+++ b/filter/generic/util.go
@@ -23,8 +23,6 @@ import (
 )
 
 import (
-       "github.com/dubbogo/gost/log/logger"
-
        perrors "github.com/pkg/errors"
 )
 
@@ -52,27 +50,72 @@ func isMakingAGenericCall(invoker base.Invoker, invocation 
base.Invocation) bool
 
 // isGeneric receives a generic field from url of invoker to determine whether 
the service is generic or not
 func isGeneric(generic string) bool {
-       return strings.EqualFold(generic, constant.GenericSerializationDefault) 
||
-               strings.EqualFold(generic, constant.GenericSerializationGson) ||
-               strings.EqualFold(generic, 
constant.GenericSerializationProtobufJson) ||
-               strings.EqualFold(generic, constant.GenericSerializationBean)
+       _, err := getGeneralizer(generic)
+       return err == nil
+}
+
+func isGenericDisabled(generic string) bool {
+       return generic == "" || strings.EqualFold(generic, "false")
 }
 
-func getGeneralizer(generic string) (g generalizer.Generalizer) {
+// getGeneralizer resolves a generic mode to its generalizer.
+// Recommended modes are true, gson, bean, and protobuf-json. protobuf keeps
+// the legacy Map/Hessian generic semantics used by Triple generic invocations.
+func getGeneralizer(generic string) (generalizer.Generalizer, error) {
        switch {
        case strings.EqualFold(generic, constant.GenericSerializationDefault):
-               g = generalizer.GetMapGeneralizer()
+               return generalizer.GetMapGeneralizer(), nil
        case strings.EqualFold(generic, constant.GenericSerializationGson):
-               g = generalizer.GetGsonGeneralizer()
+               return generalizer.GetGsonGeneralizer(), nil
        case strings.EqualFold(generic, 
constant.GenericSerializationProtobufJson):
-               g = generalizer.GetProtobufJsonGeneralizer()
+               return generalizer.GetProtobufJsonGeneralizer(), nil
+       case strings.EqualFold(generic, constant.GenericSerializationProtobuf):
+               return generalizer.GetMapGeneralizer(), nil
        case strings.EqualFold(generic, constant.GenericSerializationBean):
-               g = generalizer.GetBeanGeneralizer()
+               return generalizer.GetBeanGeneralizer(), nil
        default:
-               logger.Debugf("[Filter][Generic] generic type not supported, 
use the default generalizer, generic=%s", generic)
-               g = generalizer.GetMapGeneralizer()
+               return nil, perrors.Errorf("unsupported generic mode %q", 
generic)
+       }
+}
+
+// resolveGeneralizer validates configured generic modes and selects a 
non-empty invocation mode when present.
+// Invocation generic mode takes precedence over the URL mode; empty or false 
values do not override URL mode.
+func resolveGeneralizer(configuredGeneric string, invocation base.Invocation) 
(string, generalizer.Generalizer, error) {
+       if isGenericDisabled(configuredGeneric) {
+               return "", nil, nil
+       }
+
+       var g generalizer.Generalizer
+       var err error
+       g, err = getGeneralizer(configuredGeneric)
+       if err != nil {
+               return "", nil, err
+       }
+
+       if invocationGeneric, ok := 
invocation.GetAttachment(constant.GenericKey); ok {
+               if isGenericDisabled(invocationGeneric) {
+                       return configuredGeneric, g, nil
+               }
+               invocationGeneralizer, err := getGeneralizer(invocationGeneric)
+               if err != nil {
+                       return "", nil, err
+               }
+               return invocationGeneric, invocationGeneralizer, nil
+       }
+
+       return configuredGeneric, g, nil
+}
+
+func resolveGenericInvocationGeneralizer(invocation base.Invocation) (string, 
generalizer.Generalizer, error) {
+       generic := 
invocation.GetAttachmentWithDefaultValue(constant.GenericKey, 
constant.GenericSerializationDefault)
+       if generic == "" {
+               generic = constant.GenericSerializationDefault
+       }
+       g, err := getGeneralizer(generic)
+       if err != nil {
+               return "", nil, err
        }
-       return
+       return generic, g, nil
 }
 
 // realizeResult deserializes the data into the target type using the provided 
generalizer.
diff --git a/filter/generic/util_test.go b/filter/generic/util_test.go
index 646c2d5a6..8618cfe72 100644
--- a/filter/generic/util_test.go
+++ b/filter/generic/util_test.go
@@ -25,6 +25,7 @@ import (
        "github.com/golang/mock/gomock"
 
        "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
 )
 
 import (
@@ -82,25 +83,57 @@ func TestIsGeneric(t *testing.T) {
        assert.False(t, isGeneric("false"))
        assert.False(t, isGeneric(""))
        assert.True(t, isGeneric("bean"))
+       assert.True(t, isGeneric("protobuf"))
 }
 
 func TestGetGeneralizer(t *testing.T) {
-       g1 := getGeneralizer(constant.GenericSerializationDefault)
+       g1, err := getGeneralizer(constant.GenericSerializationDefault)
+       require.NoError(t, err)
        assert.IsType(t, generalizer.GetMapGeneralizer(), g1)
 
-       g2 := getGeneralizer(constant.GenericSerializationGson)
+       g2, err := getGeneralizer(constant.GenericSerializationGson)
+       require.NoError(t, err)
        assert.IsType(t, generalizer.GetGsonGeneralizer(), g2)
 
-       g3 := getGeneralizer(constant.GenericSerializationProtobufJson)
+       g3, err := getGeneralizer(constant.GenericSerializationProtobufJson)
+       require.NoError(t, err)
        assert.IsType(t, generalizer.GetProtobufJsonGeneralizer(), g3)
 
        // test case insensitive
-       g4 := getGeneralizer("Protobuf-Json")
+       g4, err := getGeneralizer("Protobuf-Json")
+       require.NoError(t, err)
        assert.IsType(t, generalizer.GetProtobufJsonGeneralizer(), g4)
 
-       g5 := getGeneralizer(constant.GenericSerializationBean)
+       g5, err := getGeneralizer(constant.GenericSerializationBean)
+       require.NoError(t, err)
        assert.IsType(t, generalizer.GetBeanGeneralizer(), g5)
 
-       g6 := getGeneralizer("unsupported_type")
+       g6, err := getGeneralizer(constant.GenericSerializationProtobuf)
+       require.NoError(t, err)
        assert.IsType(t, generalizer.GetMapGeneralizer(), g6)
+
+       tests := []struct {
+               name    string
+               generic string
+               err     string
+       }{
+               {
+                       name:    "empty",
+                       generic: "",
+                       err:     `unsupported generic mode ""`,
+               },
+               {
+                       name:    "unknown",
+                       generic: "unsupported_type",
+                       err:     `unsupported generic mode "unsupported_type"`,
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       g, err := getGeneralizer(tt.generic)
+                       assert.Nil(t, g)
+                       require.EqualError(t, err, tt.err)
+               })
+       }
 }

Reply via email to