Skip to content

Commit 81029d2

Browse files
authored
feat: add method name formatter option for client handlers (#138)
* feat: add reverseHandlersFormatter to Config and update websocketClient to use it * test: add TestDifferentMethodNamersWithClientHandler to validate method name formatting with various configurations * docs: update README.md to enhance reverse calling feature description and add method name formatter options * docs: expand README.md with detailed examples of method name alias usage for JSON-RPC
1 parent 8e5f77c commit 81029d2

4 files changed

Lines changed: 199 additions & 7 deletions

File tree

README.md

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ fmt.Printf("Current value: %d\n", client.AddGet(5))
175175
```
176176

177177
### Reverse Calling Feature
178-
The go-jsonrpc library also supports reverse calling, where the server can make calls to the client. This is useful in scenarios where the server needs to notify or request data from the client.
178+
The go-jsonrpc library also supports reverse calling, where the server can make calls to the client. This is useful in scenarios where the server needs to notify, request data from the client, or for subscriptions (e.g. `eth_subscribe`).
179179

180180
NOTE: Reverse calling only works in websocket mode
181181

@@ -246,11 +246,13 @@ if err := client.Call(); err != nil {
246246

247247
## Options
248248

249-
### Using `WithServerMethodNameFormatter`
249+
### Using method name formatters
250+
251+
#### Using `WithServerMethodNameFormatter`
250252

251253
`WithServerMethodNameFormatter` allows you to customize a function that formats the JSON-RPC method name, given namespace and method name.
252254

253-
There are four possible options:
255+
There are four possible out-of-the-box options:
254256
- `jsonrpc.DefaultMethodNameFormatter` - default method name formatter, e.g. `SimpleServerHandler.AddGet`
255257
- `jsonrpc.NewMethodNameFormatter(true, jsonrpc.LowerFirstCharCase)` - method name formatter with namespace, e.g. `SimpleServerHandler.addGet`
256258
- `jsonrpc.NewMethodNameFormatter(false, jsonrpc.OriginalCase)` - method name formatter without namespace, e.g. `AddGet`
@@ -261,6 +263,8 @@ There are four possible options:
261263
> Go exported methods are capitalized, so, the method name will be capitalized as well.
262264
> e.g. `SimpleServerHandler.AddGet` (capital "A" in "AddGet")
263265
266+
You can also create your own method name formatter by creating a function that implements the `jsonrpc.MethodNameFormatter` interface.
267+
264268
```go
265269
func main() {
266270
// create a new server instance with a custom separator
@@ -286,7 +290,7 @@ func main() {
286290
}
287291
```
288292

289-
### Using `WithMethodNameFormatter`
293+
#### Using `WithMethodNameFormatter`
290294

291295
`WithMethodNameFormatter` is the client-side counterpart to `WithServerMethodNameFormatter`.
292296

@@ -304,6 +308,132 @@ func main() {
304308
}
305309
```
306310

311+
#### Using `WithClientHandlerFormatter`
312+
313+
Same as `WithMethodNameFormatter`, but for client handlers. Using it you can fully customize the JSON-RPC method name for client handlers,
314+
given namespace and method name.
315+
316+
```go
317+
func main() {
318+
closer, err := jsonrpc.NewMergeClient(
319+
context.Background(),
320+
"http://example.com",
321+
"SimpleServerHandler",
322+
[]any{&client},
323+
nil,
324+
jsonrpc.WithMethodNameFormatter(jsonrpc.NewMethodNameFormatter(false, OriginalCase)),
325+
jsonrpc.WithClientHandler("Client", &RevCallTestClientHandler{}),
326+
jsonrpc.WithClientHandlerFormatter(jsonrpc.NewMethodNameFormatter(false, OriginalCase)),
327+
)
328+
defer closer()
329+
}
330+
```
331+
### Using method name alias
332+
333+
You can also create an alias for a method name. This is useful if you want to use a different method name in the JSON-RPC
334+
request than the actual method name for a specific method.
335+
336+
#### Usage of method name alias in the server
337+
338+
```go
339+
type SimpleServerHandler struct {}
340+
341+
func (h *SimpleServerHandler) Double(in int) int {
342+
return in * 2
343+
}
344+
345+
// create a new server instance
346+
rpcServer := jsonrpc.NewServer()
347+
348+
// create a handler instance and register it
349+
serverHandler := &SimpleServerHandler{}
350+
rpcServer.Register("SimpleServerHandler", serverHandler)
351+
352+
// create an alias for the Double method. This will allow you to call the server's Double method
353+
// with the name "rand_myRandomAlias" in the JSON-RPC request.
354+
rpcServer.AliasMethod("rand_myRandomAlias", "SimpleServerHandler.Double")
355+
356+
```
357+
358+
#### Usage of method name alias with client handlers
359+
360+
```go
361+
// setup the client handler
362+
type ReverseHandler struct {}
363+
364+
func (h *ReverseHandler) DoubleOnClient(in int) int {
365+
return in * 2
366+
}
367+
368+
// create a new client instance with the client handler + method name alias
369+
closer, err := jsonrpc.NewMergeClient(
370+
context.Background(),
371+
"http://example.com",
372+
"SimpleServerHandler",
373+
[]any{&client},
374+
nil,
375+
jsonrpc.WithClientHandler("Client", &ReverseHandler{}),
376+
// this allows the server to call the client's DoubleOnClient method using the name "rand_theClientRandomAlias" in the JSON-RPC request.
377+
jsonrpc.WithClientHandlerAlias("rand_theClientRandomAlias", "Client.DoubleOnClient"),
378+
)
379+
```
380+
381+
#### Usage of a struct tag to define method name alias
382+
383+
There are two cases where you can also use the `rpc_method` struct tag to define method name alias:
384+
in the client struct and in the reverse handler struct in the server.
385+
386+
In the client struct:
387+
```go
388+
// setup the client struct
389+
var client struct {
390+
AddInt func(int) int `rpc_method:"rand_aRandomAlias"`
391+
}
392+
393+
// create a new client instance with the client struct that has the `rpc_method` struct tag
394+
closer, err := jsonrpc.NewMergeClient(
395+
context.Background(),
396+
"http://example.com",
397+
"SimpleServerHandler",
398+
[]any{&client},
399+
nil,
400+
)
401+
402+
// since we defined the method name alias in the client struct, this will send a JSON-RPC request with "rand_aRandomAlias" as the method name to the
403+
// server instead of "SimpleServerHandler.AddInt".
404+
result, err := client.AddInt(10)
405+
406+
```
407+
408+
In the server's reverse handler struct:
409+
410+
```go
411+
// Define the client handler interface
412+
type ClientHandler struct {
413+
CallOnClient func(int) (int, error) `rpc_method:"rand_theClientRandomAlias"`
414+
}
415+
416+
// Define the server handler
417+
type ServerHandler struct {}
418+
419+
func (h *ServerHandler) Call(ctx context.Context) (int, error) {
420+
revClient, _ := jsonrpc.ExtractReverseClient[ClientHandler](ctx)
421+
422+
// Reverse call to the client.
423+
// Since we defined the method name alias in the client handler struct tag, this
424+
// will send a JSON-RPC request with "rand_theClientRandomAlias" as the method name to the
425+
// client instead of "Client.CallOnClient".
426+
result, err := revClient.CallOnClient(7)
427+
428+
// ...
429+
}
430+
431+
// Setup server with reverse client capability
432+
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[ClientHandler]("Client"))
433+
rpcServer.Register("ServerHandler", &ServerHandler{})
434+
```
435+
436+
307437
## Contribute
308438

309439
PRs are welcome!

client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,11 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
305305

306306
var hnd reqestHandler
307307
if len(config.reverseHandlers) > 0 {
308-
h := makeHandler(defaultServerConfig())
308+
sc := defaultServerConfig()
309+
if config.reverseHandlersFormatter != nil {
310+
sc.methodNameFormatter = config.reverseHandlersFormatter
311+
}
312+
h := makeHandler(sc)
309313
h.aliasedMethods = config.aliasedHandlerMethods
310314
for _, reverseHandler := range config.reverseHandlers {
311315
h.register(reverseHandler.ns, reverseHandler.hnd)

method_formatter_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,53 @@ func TestDifferentMethodNamersWithClient(t *testing.T) {
124124
})
125125
}
126126
}
127+
128+
func TestDifferentMethodNamersWithClientHandler(t *testing.T) {
129+
tests := map[string]struct {
130+
namer MethodNameFormatter
131+
}{
132+
"default namer & ws": {
133+
namer: DefaultMethodNameFormatter,
134+
},
135+
"lower first char namer & ws": {
136+
namer: NewMethodNameFormatter(true, LowerFirstCharCase),
137+
},
138+
"no namespace namer & ws": {
139+
namer: NewMethodNameFormatter(false, OriginalCase),
140+
},
141+
"no namespace & lower first char & ws": {
142+
namer: NewMethodNameFormatter(false, LowerFirstCharCase),
143+
},
144+
}
145+
for name, test := range tests {
146+
t.Run(name, func(t *testing.T) {
147+
rpcServer := NewServer(WithReverseClient[RevCallTestClientProxy]("Client"), WithServerMethodNameFormatter(test.namer))
148+
rpcServer.Register("Server", &RevCallTestServerHandler{})
149+
150+
// httptest stuff
151+
testServ := httptest.NewServer(rpcServer)
152+
defer testServ.Close()
153+
// setup client
154+
155+
var client struct {
156+
Call func() error
157+
}
158+
159+
closer, err := NewMergeClient(
160+
context.Background(),
161+
"ws://"+testServ.Listener.Addr().String(),
162+
"Server",
163+
[]any{&client},
164+
nil,
165+
WithMethodNameFormatter(test.namer),
166+
WithClientHandler("Client", &RevCallTestClientHandler{}),
167+
WithClientHandlerFormatter(test.namer),
168+
)
169+
require.NoError(t, err)
170+
defer closer()
171+
172+
e := client.Call()
173+
require.NoError(t, e)
174+
})
175+
}
176+
}

options.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ type Config struct {
2323
paramEncoders map[reflect.Type]ParamEncoder
2424
errors *Errors
2525

26-
reverseHandlers []clientHandler
27-
aliasedHandlerMethods map[string]string
26+
reverseHandlers []clientHandler
27+
reverseHandlersFormatter MethodNameFormatter
28+
aliasedHandlerMethods map[string]string
2829

2930
httpClient *http.Client
3031

@@ -101,6 +102,13 @@ func WithClientHandler(ns string, hnd interface{}) func(c *Config) {
101102
}
102103
}
103104

105+
// Just like WithMethodNameFormatter, but for client handlers.
106+
func WithClientHandlerFormatter(namer MethodNameFormatter) func(c *Config) {
107+
return func(c *Config) {
108+
c.reverseHandlersFormatter = namer
109+
}
110+
}
111+
104112
// WithClientHandlerAlias creates an alias for a client HANDLER method - for handlers created
105113
// with WithClientHandler
106114
func WithClientHandlerAlias(alias, original string) func(c *Config) {

0 commit comments

Comments
 (0)