Skip to content

Commit cb17873

Browse files
committed
feat: method namer
1 parent 9d4c6fb commit cb17873

File tree

6 files changed

+169
-2
lines changed

6 files changed

+169
-2
lines changed

client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func NewClient(ctx context.Context, addr string, namespace string, handler inter
9898

9999
type client struct {
100100
namespace string
101+
methodNamer MethodNamer
101102
paramEncoders map[reflect.Type]ParamEncoder
102103
errors *Errors
103104

@@ -139,6 +140,7 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co
139140

140141
c := client{
141142
namespace: namespace,
143+
methodNamer: config.methodNamer,
142144
paramEncoders: config.paramEncoders,
143145
errors: config.errors,
144146
}
@@ -193,6 +195,7 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co
193195
func httpClient(ctx context.Context, addr string, namespace string, outs []interface{}, requestHeader http.Header, config Config) (ClientCloser, error) {
194196
c := client{
195197
namespace: namespace,
198+
methodNamer: config.methodNamer,
196199
paramEncoders: config.paramEncoders,
197200
errors: config.errors,
198201
}
@@ -288,6 +291,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
288291

289292
c := client{
290293
namespace: namespace,
294+
methodNamer: config.methodNamer,
291295
paramEncoders: config.paramEncoders,
292296
errors: config.errors,
293297
}
@@ -710,7 +714,7 @@ func (c *client) makeRpcFunc(f reflect.StructField) (reflect.Value, error) {
710714
return reflect.Value{}, xerrors.New("handler field not a func")
711715
}
712716

713-
name := c.namespace + "." + f.Name
717+
name := c.methodNamer(c.namespace, f.Name)
714718
if tag, ok := f.Tag.Lookup(ProxyTagRPCMethod); ok {
715719
name = tag
716720
}

handler.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ type handler struct {
7878
paramDecoders map[reflect.Type]ParamDecoder
7979

8080
tracer Tracer
81+
82+
methodNamer MethodNamer
8183
}
8284

8385
type Tracer func(method string, params []reflect.Value, results []reflect.Value, err error)
@@ -93,6 +95,8 @@ func makeHandler(sc ServerConfig) *handler {
9395
maxRequestSize: sc.maxRequestSize,
9496

9597
tracer: sc.tracer,
98+
99+
methodNamer: sc.methodNamer,
96100
}
97101
}
98102

@@ -126,7 +130,7 @@ func (s *handler) register(namespace string, r interface{}) {
126130

127131
valOut, errOut, _ := processFuncOut(funcType)
128132

129-
s.methods[namespace+"."+method.Name] = methodHandler{
133+
s.methods[s.methodNamer(namespace, method.Name)] = methodHandler{
130134
paramReceivers: recvs,
131135
nParams: ins,
132136

namer.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package jsonrpc
2+
3+
import "strings"
4+
5+
// MethodNamer is a function that takes a namespace and a method name and returns the full method name, sent via JSON-RPC.
6+
// This is useful if you want to customize the default behaviour, e.g. send without the namespace or make it lowercase.
7+
type MethodNamer func(string, string) string
8+
9+
// DefaultMethodNamer joins the namespace and method name with a dot.
10+
func DefaultMethodNamer(namespace, method string) string {
11+
return namespace + "." + method
12+
}
13+
14+
// NoNamespaceMethodNamer returns the method name as is, without the namespace.
15+
func NoNamespaceMethodNamer(_, method string) string {
16+
return method
17+
}
18+
19+
// NoNamespaceDecapitalizedMethodNamer returns the method name as is, without the namespace, and decapitalizes the first letter.
20+
func NoNamespaceDecapitalizedMethodNamer(_, method string) string {
21+
if len(method) == 0 {
22+
return ""
23+
}
24+
return strings.ToLower(method[:1]) + method[1:]
25+
}

namer_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package jsonrpc
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/stretchr/testify/require"
7+
"net/http"
8+
"net/http/httptest"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestDifferentMethodNamers(t *testing.T) {
14+
tests := map[string]struct {
15+
namer MethodNamer
16+
17+
requestedMethod string
18+
}{
19+
"default namer": {
20+
namer: DefaultMethodNamer,
21+
requestedMethod: "SimpleServerHandler.Inc",
22+
},
23+
"no namespace namer": {
24+
namer: NoNamespaceMethodNamer,
25+
requestedMethod: "Inc",
26+
},
27+
"no namespace & decapitalized namer": {
28+
namer: NoNamespaceDecapitalizedMethodNamer,
29+
requestedMethod: "inc",
30+
},
31+
}
32+
for name, test := range tests {
33+
t.Run(name, func(t *testing.T) {
34+
rpcServer := NewServer(WithServerMethodNamer(test.namer))
35+
36+
serverHandler := &SimpleServerHandler{}
37+
rpcServer.Register("SimpleServerHandler", serverHandler)
38+
39+
testServ := httptest.NewServer(rpcServer)
40+
defer testServ.Close()
41+
42+
req := fmt.Sprintf(`{"jsonrpc": "2.0", "method": "%s", "params": [], "id": 1}`, test.requestedMethod)
43+
44+
res, err := http.Post(testServ.URL, "application/json", strings.NewReader(req))
45+
require.NoError(t, err)
46+
47+
require.Equal(t, http.StatusOK, res.StatusCode)
48+
require.Equal(t, int32(1), serverHandler.n)
49+
})
50+
}
51+
}
52+
53+
func TestDifferentMethodNamersWithClient(t *testing.T) {
54+
tests := map[string]struct {
55+
namer MethodNamer
56+
urlPrefix string
57+
}{
58+
"default namer & http": {
59+
namer: DefaultMethodNamer,
60+
urlPrefix: "http://",
61+
},
62+
"default namer & ws": {
63+
namer: DefaultMethodNamer,
64+
urlPrefix: "ws://",
65+
},
66+
"no namespace namer & http": {
67+
namer: NoNamespaceMethodNamer,
68+
urlPrefix: "http://",
69+
},
70+
"no namespace namer & ws": {
71+
namer: NoNamespaceMethodNamer,
72+
urlPrefix: "ws://",
73+
},
74+
"no namespace & decapitalized namer & http": {
75+
namer: NoNamespaceDecapitalizedMethodNamer,
76+
urlPrefix: "http://",
77+
},
78+
"no namespace & decapitalized namer & ws": {
79+
namer: NoNamespaceDecapitalizedMethodNamer,
80+
urlPrefix: "ws://",
81+
},
82+
}
83+
for name, test := range tests {
84+
t.Run(name, func(t *testing.T) {
85+
rpcServer := NewServer(WithServerMethodNamer(test.namer))
86+
87+
serverHandler := &SimpleServerHandler{}
88+
rpcServer.Register("SimpleServerHandler", serverHandler)
89+
90+
testServ := httptest.NewServer(rpcServer)
91+
defer testServ.Close()
92+
93+
var client struct {
94+
AddGet func(int) int
95+
}
96+
97+
closer, err := NewMergeClient(
98+
context.Background(),
99+
test.urlPrefix+testServ.Listener.Addr().String(),
100+
"SimpleServerHandler",
101+
[]any{&client},
102+
nil,
103+
WithHTTPClient(testServ.Client()),
104+
WithMethodNamer(test.namer),
105+
)
106+
require.NoError(t, err)
107+
defer closer()
108+
109+
n := client.AddGet(123)
110+
require.Equal(t, 123, n)
111+
})
112+
}
113+
}

options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type Config struct {
3030

3131
noReconnect bool
3232
proxyConnFactory func(func() (*websocket.Conn, error)) func() (*websocket.Conn, error) // for testing
33+
34+
methodNamer MethodNamer
3335
}
3436

3537
func defaultConfig() Config {
@@ -46,6 +48,8 @@ func defaultConfig() Config {
4648
paramEncoders: map[reflect.Type]ParamEncoder{},
4749

4850
httpClient: _defaultHTTPClient,
51+
52+
methodNamer: DefaultMethodNamer,
4953
}
5054
}
5155

@@ -110,3 +114,9 @@ func WithHTTPClient(h *http.Client) func(c *Config) {
110114
c.httpClient = h
111115
}
112116
}
117+
118+
func WithMethodNamer(namer MethodNamer) func(c *Config) {
119+
return func(c *Config) {
120+
c.methodNamer = namer
121+
}
122+
}

options_server.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type ServerConfig struct {
2222

2323
reverseClientBuilder func(context.Context, *wsConn) (context.Context, error)
2424
tracer Tracer
25+
26+
methodNamer MethodNamer
2527
}
2628

2729
type ServerOption func(c *ServerConfig)
@@ -32,6 +34,8 @@ func defaultServerConfig() ServerConfig {
3234
maxRequestSize: DEFAULT_MAX_REQUEST_SIZE,
3335

3436
pingInterval: 5 * time.Second,
37+
38+
methodNamer: DefaultMethodNamer,
3539
}
3640
}
3741

@@ -74,6 +78,7 @@ func WithReverseClient[RP any](namespace string) ServerOption {
7478
c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) {
7579
cl := client{
7680
namespace: namespace,
81+
methodNamer: c.methodNamer,
7782
paramEncoders: map[reflect.Type]ParamEncoder{},
7883
}
7984

@@ -114,3 +119,9 @@ func ExtractReverseClient[C any](ctx context.Context) (C, bool) {
114119

115120
return *c, ok
116121
}
122+
123+
func WithServerMethodNamer(namer MethodNamer) ServerOption {
124+
return func(c *ServerConfig) {
125+
c.methodNamer = namer
126+
}
127+
}

0 commit comments

Comments
 (0)