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)
+ })
+ }
}