From fe5d436e6640817e7797037a801908a4e6d242be Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Mon, 17 Mar 2025 07:28:12 +0100 Subject: [PATCH 1/5] feat: method namer --- client.go | 6 ++- namer.go | 25 ++++++++++ namer_test.go | 113 ++++++++++++++++++++++++++++++++++++++++++++++ options.go | 10 ++++ options_server.go | 1 + 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 namer.go create mode 100644 namer_test.go diff --git a/client.go b/client.go index bc3dac6..2c18876 100644 --- a/client.go +++ b/client.go @@ -98,6 +98,7 @@ func NewClient(ctx context.Context, addr string, namespace string, handler inter type client struct { namespace string + methodNamer MethodNamer paramEncoders map[reflect.Type]ParamEncoder errors *Errors @@ -139,6 +140,7 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co c := client{ namespace: namespace, + methodNamer: config.methodNamer, paramEncoders: config.paramEncoders, errors: config.errors, } @@ -193,6 +195,7 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co func httpClient(ctx context.Context, addr string, namespace string, outs []interface{}, requestHeader http.Header, config Config) (ClientCloser, error) { c := client{ namespace: namespace, + methodNamer: config.methodNamer, paramEncoders: config.paramEncoders, errors: config.errors, } @@ -288,6 +291,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs [] c := client{ namespace: namespace, + methodNamer: config.methodNamer, paramEncoders: config.paramEncoders, errors: config.errors, } @@ -710,7 +714,7 @@ func (c *client) makeRpcFunc(f reflect.StructField) (reflect.Value, error) { return reflect.Value{}, xerrors.New("handler field not a func") } - name := c.namespace + "." + f.Name + name := c.methodNamer(c.namespace, f.Name) if tag, ok := f.Tag.Lookup(ProxyTagRPCMethod); ok { name = tag } diff --git a/namer.go b/namer.go new file mode 100644 index 0000000..9df11fe --- /dev/null +++ b/namer.go @@ -0,0 +1,25 @@ +package jsonrpc + +import "strings" + +// MethodNamer is a function that takes a namespace and a method name and returns the full method name, sent via JSON-RPC. +// This is useful if you want to customize the default behaviour, e.g. send without the namespace or make it lowercase. +type MethodNamer func(string, string) string + +// DefaultMethodNamer joins the namespace and method name with a dot. +func DefaultMethodNamer(namespace, method string) string { + return namespace + "." + method +} + +// NoNamespaceMethodNamer returns the method name as is, without the namespace. +func NoNamespaceMethodNamer(_, method string) string { + return method +} + +// NoNamespaceDecapitalizedMethodNamer returns the method name as is, without the namespace, and decapitalizes the first letter. +func NoNamespaceDecapitalizedMethodNamer(_, method string) string { + if len(method) == 0 { + return "" + } + return strings.ToLower(method[:1]) + method[1:] +} diff --git a/namer_test.go b/namer_test.go new file mode 100644 index 0000000..e290092 --- /dev/null +++ b/namer_test.go @@ -0,0 +1,113 @@ +package jsonrpc + +import ( + "context" + "fmt" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestDifferentMethodNamers(t *testing.T) { + tests := map[string]struct { + namer MethodNamer + + requestedMethod string + }{ + "default namer": { + namer: DefaultMethodNamer, + requestedMethod: "SimpleServerHandler.Inc", + }, + "no namespace namer": { + namer: NoNamespaceMethodNamer, + requestedMethod: "Inc", + }, + "no namespace & decapitalized namer": { + namer: NoNamespaceDecapitalizedMethodNamer, + requestedMethod: "inc", + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + rpcServer := NewServer(WithServerMethodNamer(test.namer)) + + serverHandler := &SimpleServerHandler{} + rpcServer.Register("SimpleServerHandler", serverHandler) + + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + req := fmt.Sprintf(`{"jsonrpc": "2.0", "method": "%s", "params": [], "id": 1}`, test.requestedMethod) + + res, err := http.Post(testServ.URL, "application/json", strings.NewReader(req)) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, res.StatusCode) + require.Equal(t, int32(1), serverHandler.n) + }) + } +} + +func TestDifferentMethodNamersWithClient(t *testing.T) { + tests := map[string]struct { + namer MethodNamer + urlPrefix string + }{ + "default namer & http": { + namer: DefaultMethodNamer, + urlPrefix: "http://", + }, + "default namer & ws": { + namer: DefaultMethodNamer, + urlPrefix: "ws://", + }, + "no namespace namer & http": { + namer: NoNamespaceMethodNamer, + urlPrefix: "http://", + }, + "no namespace namer & ws": { + namer: NoNamespaceMethodNamer, + urlPrefix: "ws://", + }, + "no namespace & decapitalized namer & http": { + namer: NoNamespaceDecapitalizedMethodNamer, + urlPrefix: "http://", + }, + "no namespace & decapitalized namer & ws": { + namer: NoNamespaceDecapitalizedMethodNamer, + urlPrefix: "ws://", + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + rpcServer := NewServer(WithServerMethodNamer(test.namer)) + + serverHandler := &SimpleServerHandler{} + rpcServer.Register("SimpleServerHandler", serverHandler) + + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + var client struct { + AddGet func(int) int + } + + closer, err := NewMergeClient( + context.Background(), + test.urlPrefix+testServ.Listener.Addr().String(), + "SimpleServerHandler", + []any{&client}, + nil, + WithHTTPClient(testServ.Client()), + WithMethodNamer(test.namer), + ) + require.NoError(t, err) + defer closer() + + n := client.AddGet(123) + require.Equal(t, 123, n) + }) + } +} diff --git a/options.go b/options.go index 2172f50..da59752 100644 --- a/options.go +++ b/options.go @@ -30,6 +30,8 @@ type Config struct { noReconnect bool proxyConnFactory func(func() (*websocket.Conn, error)) func() (*websocket.Conn, error) // for testing + + methodNamer MethodNamer } func defaultConfig() Config { @@ -46,6 +48,8 @@ func defaultConfig() Config { paramEncoders: map[reflect.Type]ParamEncoder{}, httpClient: _defaultHTTPClient, + + methodNamer: DefaultMethodNamer, } } @@ -110,3 +114,9 @@ func WithHTTPClient(h *http.Client) func(c *Config) { c.httpClient = h } } + +func WithMethodNamer(namer MethodNamer) func(c *Config) { + return func(c *Config) { + c.methodNamer = namer + } +} diff --git a/options_server.go b/options_server.go index 7a3e2d3..30cb4d8 100644 --- a/options_server.go +++ b/options_server.go @@ -86,6 +86,7 @@ func WithReverseClient[RP any](namespace string) ServerOption { c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) { cl := client{ namespace: namespace, + methodNamer: c.methodNamer, paramEncoders: map[reflect.Type]ParamEncoder{}, } From 57d1697a6f77c2589df640c2d6592a065ec92c32 Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Mon, 24 Mar 2025 10:07:25 +0100 Subject: [PATCH 2/5] feat: adjust to MethodNameFormatter concept --- README.md | 35 +++++++++++++++++++++-- client.go | 29 ++++++++++--------- method_formatter.go | 26 +++++++++++++++++ namer_test.go => method_formatter_test.go | 28 +++++++++--------- namer.go | 25 ---------------- options.go | 6 ++-- options_server.go | 16 ++++------- rpc_test.go | 2 +- 8 files changed, 97 insertions(+), 70 deletions(-) create mode 100644 method_formatter.go rename namer_test.go => method_formatter_test.go (75%) delete mode 100644 namer.go diff --git a/README.md b/README.md index 444a6d0..9573ce6 100644 --- a/README.md +++ b/README.md @@ -246,12 +246,24 @@ if err := client.Call(); err != nil { ## Options -### Using `WithMethodNameFormatter` +### Using `WithServerMethodNameFormatter` + +`WithServerMethodNameFormatter` allows you to customize a function that formats the JSON-RPC method name, given namespace and method name. + +There are three predefined formatters: +- `jsonrpc.DefaultMethodNameFormatter` - default method name formatter, e.g. `SimpleServerHandler.AddGet` +- `jsonrpc.NoNamespaceMethodNameFormatter` - method name formatter without namespace, e.g. `AddGet` +- `jsonrpc.NoNamespaceDecapitalizedMethodNameFormatter` - method name formatter without namespace and decapitalized, e.g. `addGet` + +> [!NOTE] +> The default method name formatter concatenates the namespace and method name with a dot. +> Go exported methods are capitalized, so, the method name will be capitalized as well. +> e.g. `SimpleServerHandler.AddGet` (capital "A" in "AddGet") ```go func main() { // create a new server instance with a custom separator - rpcServer := jsonrpc.NewServer(jsonrpc.WithMethodNameFormatter( + rpcServer := jsonrpc.NewServer(jsonrpc.WithServerMethodNameFormatter( func(namespace, method string) string { return namespace + "_" + method }), @@ -273,6 +285,23 @@ func main() { } ``` +### Using `WithMethodNameFormatter` + +`WithMethodNameFormatter` is the client-side counterpart to `WithServerMethodNameFormatter`. + +```go +func main() { + closer, err := NewMergeClient( + context.Background(), + "http://example.com", + "SimpleServerHandler", + []any{&client}, + nil, + WithMethodNameFormatter(test.namer), + ) + defer closer() +} +``` ## Contribute @@ -280,4 +309,4 @@ PRs are welcome! ## License -Dual-licensed under [MIT](https://github.com/filecoin-project/go-jsonrpc/blob/master/LICENSE-MIT) + [Apache 2.0](https://github.com/filecoin-project/go-jsonrpc/blob/master/LICENSE-APACHE) \ No newline at end of file +Dual-licensed under [MIT](https://github.com/filecoin-project/go-jsonrpc/blob/master/LICENSE-MIT) + [Apache 2.0](https://github.com/filecoin-project/go-jsonrpc/blob/master/LICENSE-APACHE) diff --git a/client.go b/client.go index 2c18876..270c4cf 100644 --- a/client.go +++ b/client.go @@ -98,13 +98,14 @@ func NewClient(ctx context.Context, addr string, namespace string, handler inter type client struct { namespace string - methodNamer MethodNamer paramEncoders map[reflect.Type]ParamEncoder errors *Errors doRequest func(context.Context, clientRequest) (clientResponse, error) exiting <-chan struct{} idCtr int64 + + methodNameFormatter MethodNameFormatter } // NewMergeClient is like NewClient, but allows to specify multiple structs @@ -139,10 +140,10 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co } c := client{ - namespace: namespace, - methodNamer: config.methodNamer, - paramEncoders: config.paramEncoders, - errors: config.errors, + namespace: namespace, + methodNameFormatter: config.methodNamer, + paramEncoders: config.paramEncoders, + errors: config.errors, } stop := make(chan struct{}) @@ -194,10 +195,10 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co func httpClient(ctx context.Context, addr string, namespace string, outs []interface{}, requestHeader http.Header, config Config) (ClientCloser, error) { c := client{ - namespace: namespace, - methodNamer: config.methodNamer, - paramEncoders: config.paramEncoders, - errors: config.errors, + namespace: namespace, + methodNameFormatter: config.methodNamer, + paramEncoders: config.paramEncoders, + errors: config.errors, } stop := make(chan struct{}) @@ -290,10 +291,10 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs [] } c := client{ - namespace: namespace, - methodNamer: config.methodNamer, - paramEncoders: config.paramEncoders, - errors: config.errors, + namespace: namespace, + methodNameFormatter: config.methodNamer, + paramEncoders: config.paramEncoders, + errors: config.errors, } requests := c.setupRequestChan() @@ -714,7 +715,7 @@ func (c *client) makeRpcFunc(f reflect.StructField) (reflect.Value, error) { return reflect.Value{}, xerrors.New("handler field not a func") } - name := c.methodNamer(c.namespace, f.Name) + name := c.methodNameFormatter(c.namespace, f.Name) if tag, ok := f.Tag.Lookup(ProxyTagRPCMethod); ok { name = tag } diff --git a/method_formatter.go b/method_formatter.go new file mode 100644 index 0000000..a4eff9d --- /dev/null +++ b/method_formatter.go @@ -0,0 +1,26 @@ +package jsonrpc + +import "strings" + +// MethodNameFormatter is a function that takes a namespace and a method name and returns the full method name, sent via JSON-RPC. +// This is useful if you want to customize the default behaviour, e.g. send without the namespace or make it lowercase. +type MethodNameFormatter func(namespace, method string) string + +// DefaultMethodNameFormatter joins the namespace and method name with a dot. +func DefaultMethodNameFormatter(namespace, method string) string { + return namespace + "." + method +} + +// NoNamespaceMethodNameFormatter returns the method name as is, without the namespace. +func NoNamespaceMethodNameFormatter(_, method string) string { + return method +} + +// NoNamespaceDecapitalizedMethodNameFormatter returns the method name as is, without the namespace, and decapitalizes the first letter. +// e.g. "Inc" -> "inc" +func NoNamespaceDecapitalizedMethodNameFormatter(_, method string) string { + if len(method) == 0 { + return "" + } + return strings.ToLower(method[:1]) + method[1:] +} diff --git a/namer_test.go b/method_formatter_test.go similarity index 75% rename from namer_test.go rename to method_formatter_test.go index e290092..ad9ee22 100644 --- a/namer_test.go +++ b/method_formatter_test.go @@ -12,26 +12,26 @@ import ( func TestDifferentMethodNamers(t *testing.T) { tests := map[string]struct { - namer MethodNamer + namer MethodNameFormatter requestedMethod string }{ "default namer": { - namer: DefaultMethodNamer, + namer: DefaultMethodNameFormatter, requestedMethod: "SimpleServerHandler.Inc", }, "no namespace namer": { - namer: NoNamespaceMethodNamer, + namer: NoNamespaceMethodNameFormatter, requestedMethod: "Inc", }, "no namespace & decapitalized namer": { - namer: NoNamespaceDecapitalizedMethodNamer, + namer: NoNamespaceDecapitalizedMethodNameFormatter, requestedMethod: "inc", }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - rpcServer := NewServer(WithServerMethodNamer(test.namer)) + rpcServer := NewServer(WithServerMethodNameFormatter(test.namer)) serverHandler := &SimpleServerHandler{} rpcServer.Register("SimpleServerHandler", serverHandler) @@ -52,37 +52,37 @@ func TestDifferentMethodNamers(t *testing.T) { func TestDifferentMethodNamersWithClient(t *testing.T) { tests := map[string]struct { - namer MethodNamer + namer MethodNameFormatter urlPrefix string }{ "default namer & http": { - namer: DefaultMethodNamer, + namer: DefaultMethodNameFormatter, urlPrefix: "http://", }, "default namer & ws": { - namer: DefaultMethodNamer, + namer: DefaultMethodNameFormatter, urlPrefix: "ws://", }, "no namespace namer & http": { - namer: NoNamespaceMethodNamer, + namer: NoNamespaceMethodNameFormatter, urlPrefix: "http://", }, "no namespace namer & ws": { - namer: NoNamespaceMethodNamer, + namer: NoNamespaceMethodNameFormatter, urlPrefix: "ws://", }, "no namespace & decapitalized namer & http": { - namer: NoNamespaceDecapitalizedMethodNamer, + namer: NoNamespaceDecapitalizedMethodNameFormatter, urlPrefix: "http://", }, "no namespace & decapitalized namer & ws": { - namer: NoNamespaceDecapitalizedMethodNamer, + namer: NoNamespaceDecapitalizedMethodNameFormatter, urlPrefix: "ws://", }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - rpcServer := NewServer(WithServerMethodNamer(test.namer)) + rpcServer := NewServer(WithServerMethodNameFormatter(test.namer)) serverHandler := &SimpleServerHandler{} rpcServer.Register("SimpleServerHandler", serverHandler) @@ -101,7 +101,7 @@ func TestDifferentMethodNamersWithClient(t *testing.T) { []any{&client}, nil, WithHTTPClient(testServ.Client()), - WithMethodNamer(test.namer), + WithMethodNameFormatter(test.namer), ) require.NoError(t, err) defer closer() diff --git a/namer.go b/namer.go deleted file mode 100644 index 9df11fe..0000000 --- a/namer.go +++ /dev/null @@ -1,25 +0,0 @@ -package jsonrpc - -import "strings" - -// MethodNamer is a function that takes a namespace and a method name and returns the full method name, sent via JSON-RPC. -// This is useful if you want to customize the default behaviour, e.g. send without the namespace or make it lowercase. -type MethodNamer func(string, string) string - -// DefaultMethodNamer joins the namespace and method name with a dot. -func DefaultMethodNamer(namespace, method string) string { - return namespace + "." + method -} - -// NoNamespaceMethodNamer returns the method name as is, without the namespace. -func NoNamespaceMethodNamer(_, method string) string { - return method -} - -// NoNamespaceDecapitalizedMethodNamer returns the method name as is, without the namespace, and decapitalizes the first letter. -func NoNamespaceDecapitalizedMethodNamer(_, method string) string { - if len(method) == 0 { - return "" - } - return strings.ToLower(method[:1]) + method[1:] -} diff --git a/options.go b/options.go index da59752..8b4d0e9 100644 --- a/options.go +++ b/options.go @@ -31,7 +31,7 @@ type Config struct { noReconnect bool proxyConnFactory func(func() (*websocket.Conn, error)) func() (*websocket.Conn, error) // for testing - methodNamer MethodNamer + methodNamer MethodNameFormatter } func defaultConfig() Config { @@ -49,7 +49,7 @@ func defaultConfig() Config { httpClient: _defaultHTTPClient, - methodNamer: DefaultMethodNamer, + methodNamer: DefaultMethodNameFormatter, } } @@ -115,7 +115,7 @@ func WithHTTPClient(h *http.Client) func(c *Config) { } } -func WithMethodNamer(namer MethodNamer) func(c *Config) { +func WithMethodNameFormatter(namer MethodNameFormatter) func(c *Config) { return func(c *Config) { c.methodNamer = namer } diff --git a/options_server.go b/options_server.go index 30cb4d8..f90bfa4 100644 --- a/options_server.go +++ b/options_server.go @@ -13,8 +13,6 @@ type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) -type MethodNameFormatter func(namespace, method string) string - type ServerConfig struct { maxRequestSize int64 pingInterval time.Duration @@ -34,10 +32,8 @@ func defaultServerConfig() ServerConfig { paramDecoders: map[reflect.Type]ParamDecoder{}, maxRequestSize: DEFAULT_MAX_REQUEST_SIZE, - pingInterval: 5 * time.Second, - methodNameFormatter: func(namespace, method string) string { - return namespace + "." + method - }, + pingInterval: 5 * time.Second, + methodNameFormatter: DefaultMethodNameFormatter, } } @@ -65,7 +61,7 @@ func WithServerPingInterval(d time.Duration) ServerOption { } } -func WithMethodNameFormatter(formatter MethodNameFormatter) ServerOption { +func WithServerMethodNameFormatter(formatter MethodNameFormatter) ServerOption { return func(c *ServerConfig) { c.methodNameFormatter = formatter } @@ -85,9 +81,9 @@ func WithReverseClient[RP any](namespace string) ServerOption { return func(c *ServerConfig) { c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) { cl := client{ - namespace: namespace, - methodNamer: c.methodNamer, - paramEncoders: map[reflect.Type]ParamEncoder{}, + namespace: namespace, + methodNameFormatter: c.methodNameFormatter, + paramEncoders: map[reflect.Type]ParamEncoder{}, } // todo test that everything is closing correctly diff --git a/rpc_test.go b/rpc_test.go index 832d0c1..f394708 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -1719,7 +1719,7 @@ func TestNewCustomClient(t *testing.T) { func TestReverseCallWithCustomMethodName(t *testing.T) { // setup server - rpcServer := NewServer(WithMethodNameFormatter(func(namespace, method string) string { return namespace + "_" + method })) + rpcServer := NewServer(WithServerMethodNameFormatter(func(namespace, method string) string { return namespace + "_" + method })) rpcServer.Register("Server", &RawParamHandler{}) // httptest stuff From a86ba499549d36b7515795446f8c0b91eccd42ab Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:12:32 +0200 Subject: [PATCH 3/5] feat: NewMethodNameFormatter concept --- README.md | 9 +++++---- method_formatter.go | 34 ++++++++++++++++++++-------------- method_formatter_test.go | 30 +++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 9573ce6..85b523c 100644 --- a/README.md +++ b/README.md @@ -250,10 +250,11 @@ if err := client.Call(); err != nil { `WithServerMethodNameFormatter` allows you to customize a function that formats the JSON-RPC method name, given namespace and method name. -There are three predefined formatters: +There are four predefined options: - `jsonrpc.DefaultMethodNameFormatter` - default method name formatter, e.g. `SimpleServerHandler.AddGet` -- `jsonrpc.NoNamespaceMethodNameFormatter` - method name formatter without namespace, e.g. `AddGet` -- `jsonrpc.NoNamespaceDecapitalizedMethodNameFormatter` - method name formatter without namespace and decapitalized, e.g. `addGet` +- `jsonrpc.NewMethodNameFormatter(true, jsonrpc.LowerFirstCharCase)` - method name formatter with namespace, e.g. `SimpleServerHandler.addGet` +- `jsonrpc.NewMethodNameFormatter(false, jsonrpc.OriginalCase)` - method name formatter without namespace, e.g. `AddGet` +- `jsonrpc.NewMethodNameFormatter(false, jsonrpc.LowerFirstCharCase)` - method name formatter without namespace and with the first char lowercased, e.g. `addGet` > [!NOTE] > The default method name formatter concatenates the namespace and method name with a dot. @@ -297,7 +298,7 @@ func main() { "SimpleServerHandler", []any{&client}, nil, - WithMethodNameFormatter(test.namer), + WithMethodNameFormatter(jsonrpc.NewMethodNameFormatter(false, OriginalCase)), ) defer closer() } diff --git a/method_formatter.go b/method_formatter.go index a4eff9d..c2b3616 100644 --- a/method_formatter.go +++ b/method_formatter.go @@ -6,21 +6,27 @@ import "strings" // This is useful if you want to customize the default behaviour, e.g. send without the namespace or make it lowercase. type MethodNameFormatter func(namespace, method string) string -// DefaultMethodNameFormatter joins the namespace and method name with a dot. -func DefaultMethodNameFormatter(namespace, method string) string { - return namespace + "." + method -} +// CaseStyle represents the case style for method names. +type CaseStyle int -// NoNamespaceMethodNameFormatter returns the method name as is, without the namespace. -func NoNamespaceMethodNameFormatter(_, method string) string { - return method -} +const ( + OriginalCase CaseStyle = iota + LowerFirstCharCase +) -// NoNamespaceDecapitalizedMethodNameFormatter returns the method name as is, without the namespace, and decapitalizes the first letter. -// e.g. "Inc" -> "inc" -func NoNamespaceDecapitalizedMethodNameFormatter(_, method string) string { - if len(method) == 0 { - return "" +// NewMethodNameFormatter creates a new method name formatter based on the provided options. +func NewMethodNameFormatter(includeNamespace bool, nameCase CaseStyle) MethodNameFormatter { + return func(namespace, method string) string { + formattedMethod := method + if nameCase == LowerFirstCharCase && len(method) > 0 { + formattedMethod = strings.ToLower(method[:1]) + method[1:] + } + if includeNamespace { + return namespace + "." + formattedMethod + } + return formattedMethod } - return strings.ToLower(method[:1]) + method[1:] } + +// DefaultMethodNameFormatter is a pass-through formatter with default options. +var DefaultMethodNameFormatter = NewMethodNameFormatter(true, OriginalCase) diff --git a/method_formatter_test.go b/method_formatter_test.go index ad9ee22..d003ddd 100644 --- a/method_formatter_test.go +++ b/method_formatter_test.go @@ -20,12 +20,16 @@ func TestDifferentMethodNamers(t *testing.T) { namer: DefaultMethodNameFormatter, requestedMethod: "SimpleServerHandler.Inc", }, + "lower fist char": { + namer: NewMethodNameFormatter(true, LowerFirstCharCase), + requestedMethod: "SimpleServerHandler.inc", + }, "no namespace namer": { - namer: NoNamespaceMethodNameFormatter, + namer: NewMethodNameFormatter(false, OriginalCase), requestedMethod: "Inc", }, - "no namespace & decapitalized namer": { - namer: NoNamespaceDecapitalizedMethodNameFormatter, + "no namespace & lower fist char": { + namer: NewMethodNameFormatter(false, LowerFirstCharCase), requestedMethod: "inc", }, } @@ -63,20 +67,28 @@ func TestDifferentMethodNamersWithClient(t *testing.T) { namer: DefaultMethodNameFormatter, urlPrefix: "ws://", }, + "lower first char namer & http": { + namer: NewMethodNameFormatter(true, LowerFirstCharCase), + urlPrefix: "http://", + }, + "lower first char namer & ws": { + namer: NewMethodNameFormatter(true, LowerFirstCharCase), + urlPrefix: "ws://", + }, "no namespace namer & http": { - namer: NoNamespaceMethodNameFormatter, + namer: NewMethodNameFormatter(false, OriginalCase), urlPrefix: "http://", }, "no namespace namer & ws": { - namer: NoNamespaceMethodNameFormatter, + namer: NewMethodNameFormatter(false, OriginalCase), urlPrefix: "ws://", }, - "no namespace & decapitalized namer & http": { - namer: NoNamespaceDecapitalizedMethodNameFormatter, + "no namespace & lower first char & http": { + namer: NewMethodNameFormatter(false, LowerFirstCharCase), urlPrefix: "http://", }, - "no namespace & decapitalized namer & ws": { - namer: NoNamespaceDecapitalizedMethodNameFormatter, + "no namespace & lower first char & ws": { + namer: NewMethodNameFormatter(false, LowerFirstCharCase), urlPrefix: "ws://", }, } From 19da84b1a468749dedfea10f4546b24521199307 Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Tue, 1 Apr 2025 09:26:34 +0200 Subject: [PATCH 4/5] feat: client instantiation ordering --- client.go | 6 +++--- options_server.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index 270c4cf..69a9cfe 100644 --- a/client.go +++ b/client.go @@ -141,9 +141,9 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co c := client{ namespace: namespace, - methodNameFormatter: config.methodNamer, paramEncoders: config.paramEncoders, errors: config.errors, + methodNameFormatter: config.methodNamer, } stop := make(chan struct{}) @@ -196,9 +196,9 @@ func NewCustomClient(namespace string, outs []interface{}, doRequest func(ctx co func httpClient(ctx context.Context, addr string, namespace string, outs []interface{}, requestHeader http.Header, config Config) (ClientCloser, error) { c := client{ namespace: namespace, - methodNameFormatter: config.methodNamer, paramEncoders: config.paramEncoders, errors: config.errors, + methodNameFormatter: config.methodNamer, } stop := make(chan struct{}) @@ -292,9 +292,9 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs [] c := client{ namespace: namespace, - methodNameFormatter: config.methodNamer, paramEncoders: config.paramEncoders, errors: config.errors, + methodNameFormatter: config.methodNamer, } requests := c.setupRequestChan() diff --git a/options_server.go b/options_server.go index f90bfa4..c6897a4 100644 --- a/options_server.go +++ b/options_server.go @@ -82,8 +82,8 @@ func WithReverseClient[RP any](namespace string) ServerOption { c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) { cl := client{ namespace: namespace, - methodNameFormatter: c.methodNameFormatter, paramEncoders: map[reflect.Type]ParamEncoder{}, + methodNameFormatter: c.methodNameFormatter, } // todo test that everything is closing correctly From e72f58d6360d61762ed0fd7204fec6d9f75a8407 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 8 Apr 2025 14:53:51 +1000 Subject: [PATCH 5/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85b523c..03eff66 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ if err := client.Call(); err != nil { `WithServerMethodNameFormatter` allows you to customize a function that formats the JSON-RPC method name, given namespace and method name. -There are four predefined options: +There are four possible options: - `jsonrpc.DefaultMethodNameFormatter` - default method name formatter, e.g. `SimpleServerHandler.AddGet` - `jsonrpc.NewMethodNameFormatter(true, jsonrpc.LowerFirstCharCase)` - method name formatter with namespace, e.g. `SimpleServerHandler.addGet` - `jsonrpc.NewMethodNameFormatter(false, jsonrpc.OriginalCase)` - method name formatter without namespace, e.g. `AddGet`