From 3bd8614ec501ae34ec658136f8b818bb0584caa5 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 31 May 2024 02:28:01 +0530 Subject: [PATCH 1/9] ft:add namespace separator and method transformer --- .gitignore | 1 + go.mod | 2 +- handler.go | 24 ++++++++++++--- options_server.go | 23 +++++++++++++-- rpc_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/go.mod b/go.mod index 752a3f4..6d30b39 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/filecoin-project/go-jsonrpc -go 1.23 +go 1.23.0 require ( github.com/google/uuid v1.1.1 diff --git a/handler.go b/handler.go index a154666..7a74fe4 100644 --- a/handler.go +++ b/handler.go @@ -77,7 +77,10 @@ type handler struct { paramDecoders map[reflect.Type]ParamDecoder - tracer Tracer + methodCaseTransformer MethodCaseTransformer + + tracer Tracer + separator string } type Tracer func(method string, params []reflect.Value, results []reflect.Value, err error) @@ -90,9 +93,12 @@ func makeHandler(sc ServerConfig) *handler { aliasedMethods: map[string]string{}, paramDecoders: sc.paramDecoders, + methodCaseTransformer: sc.methodCaseTransformer, + maxRequestSize: sc.maxRequestSize, - tracer: sc.tracer, + tracer: sc.tracer, + separator: sc.separator, } } @@ -125,8 +131,11 @@ func (s *handler) register(namespace string, r interface{}) { } valOut, errOut, _ := processFuncOut(funcType) + if s.methodCaseTransformer != nil { + method.Name = s.methodCaseTransformer(method.Name) + } - s.methods[namespace+"."+method.Name] = methodHandler{ + s.methods[namespace+s.separator+method.Name] = methodHandler{ paramReceivers: recvs, nParams: ins, @@ -303,7 +312,14 @@ func (s *handler) createError(err error) *JSONRPCError { return out } -func (s *handler) handle(ctx context.Context, req request, w func(func(io.Writer)), rpcError rpcErrFunc, done func(keepCtx bool), chOut chanOut) { +func (s *handler) handle( + ctx context.Context, + req request, + w func(func(io.Writer)), + rpcError rpcErrFunc, + done func(keepCtx bool), + chOut chanOut, +) { // Not sure if we need to sanitize the incoming req.Method or not. ctx, span := s.getSpan(ctx, req) ctx, _ = tag.New(ctx, tag.Insert(metrics.RPCMethod, req.Method)) diff --git a/options_server.go b/options_server.go index 1443ec7..fc23349 100644 --- a/options_server.go +++ b/options_server.go @@ -13,6 +13,10 @@ type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) +type MethodCaseTransformer func(string) string + +const DEFAULT_SEPARATOR = "." + type ServerConfig struct { maxRequestSize int64 pingInterval time.Duration @@ -20,8 +24,10 @@ type ServerConfig struct { paramDecoders map[reflect.Type]ParamDecoder errors *Errors - reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) - tracer Tracer + reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) + tracer Tracer + methodCaseTransformer MethodCaseTransformer + separator string } type ServerOption func(c *ServerConfig) @@ -32,6 +38,7 @@ func defaultServerConfig() ServerConfig { maxRequestSize: DEFAULT_MAX_REQUEST_SIZE, pingInterval: 5 * time.Second, + separator: DEFAULT_SEPARATOR, } } @@ -59,6 +66,18 @@ func WithServerPingInterval(d time.Duration) ServerOption { } } +func WithNamespaceSeparator(separator string) ServerOption { + return func(c *ServerConfig) { + c.separator = separator + } +} + +func WithMethodTransformer(methodCaseTransformer MethodCaseTransformer) ServerOption { + return func(c *ServerConfig) { + c.methodCaseTransformer = methodCaseTransformer + } +} + // WithTracer allows the instantiator to trace the method calls and results. // This is useful for debugging a client-server interaction. func WithTracer(l Tracer) ServerOption { diff --git a/rpc_test.go b/rpc_test.go index 709d90e..59c01bf 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -12,6 +12,7 @@ import ( "net/http/httptest" "os" "reflect" + "regexp" "strconv" "strings" "sync" @@ -1248,7 +1249,11 @@ func TestIDHandling(t *testing.T) { expect interface{} expectErr bool }{ - {`{"id":"8116d306-56cc-4637-9dd7-39ce1548a5a0","jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, "8116d306-56cc-4637-9dd7-39ce1548a5a0", false}, + { + `{"id":"8116d306-56cc-4637-9dd7-39ce1548a5a0","jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, + "8116d306-56cc-4637-9dd7-39ce1548a5a0", + false, + }, {`{"id":1234,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, float64(1234), false}, {`{"id":null,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, nil, false}, {`{"id":1234.0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, 1234.0, false}, @@ -1711,3 +1716,71 @@ func TestNewCustomClient(t *testing.T) { require.Equal(t, 13, n) require.Equal(t, int32(13), serverHandler.n) } + +func TestReverseCallWithCustomSeparator(t *testing.T) { + // setup server + + rpcServer := NewServer(WithNamespaceSeparator("_")) + rpcServer.Register("Server", &RawParamHandler{}) + + // httptest stuff + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + // setup client + + var client struct { + Call func(ctx context.Context, ps RawParams) error `rpc_method:"Server_Call"` + } + closer, err := NewMergeClient(context.Background(), "ws://"+testServ.Listener.Addr().String(), "Server", []interface{}{ + &client, + }, nil) + require.NoError(t, err) + + // do the call! + + e := client.Call(context.Background(), []byte(`{"I": 1}`)) + require.NoError(t, e) + + closer() +} + +type MethodTransformedHandler struct{} + +func (h *RawParamHandler) CallSomethingInSnakeCase(ctx context.Context, v int) (int, error) { + return v + 1, nil +} + +func TestCallWithMethodTransformer(t *testing.T) { + // setup server + + var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") + var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") + + rpcServer := NewServer(WithMethodTransformer(func(method string) string { + snake := matchFirstCap.ReplaceAllString(method, "${1}_${2}") + snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") + return strings.ToLower(snake) + })) + rpcServer.Register("Raw", &RawParamHandler{}) + + // httptest stuff + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + // setup client + var client struct { + Call func(ctx context.Context, v int) (int, error) `rpc_method:"Raw.call_something_in_snake_case"` + } + closer, err := NewMergeClient(context.Background(), "ws://"+testServ.Listener.Addr().String(), "Raw", []interface{}{ + &client, + }, nil) + require.NoError(t, err) + + // this will block if it's not sent as a notification + n, err := client.Call(context.Background(), 6) + require.NoError(t, err) + require.Equal(t, 7, n) + + closer() +} From b51ea2b16f6927f7e35b319e0ee0363019c29072 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Tue, 18 Mar 2025 16:05:22 +0530 Subject: [PATCH 2/9] ft: fix const --- options_server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/options_server.go b/options_server.go index fc23349..8a6a221 100644 --- a/options_server.go +++ b/options_server.go @@ -15,7 +15,7 @@ type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) type MethodCaseTransformer func(string) string -const DEFAULT_SEPARATOR = "." +const defaultSeparator = "." type ServerConfig struct { maxRequestSize int64 @@ -38,7 +38,7 @@ func defaultServerConfig() ServerConfig { maxRequestSize: DEFAULT_MAX_REQUEST_SIZE, pingInterval: 5 * time.Second, - separator: DEFAULT_SEPARATOR, + separator: defaultSeparator, } } From d12cfe7ed8363d877fc18ecb078fc6d096480ce6 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 31 May 2024 02:35:06 +0530 Subject: [PATCH 3/9] ft: add docs --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index 0c06deb..f917534 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,53 @@ if err := client.Call(); err != nil { } ``` +## Options + + +### Using `WithNamespaceSeparator` +```go +func main() { + // create a new server instance with a custom namespace separator + rpcServer := jsonrpc.NewServer(jsonrpc.WithNamespaceSeparator("_")) + + // create a handler instance and register it + serverHandler := &SimpleServerHandler{} + rpcServer.Register("SimpleServerHandler", serverHandler) + + // serve the api + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) + + // rpc method becomes SimpleServerHandler_AddGet + + [..do other app stuff / wait..] +} +``` + +### Using `WithMethodTransformer` +```go +func main() { + // create a new server instance with a custom method transformer + rpcServer := jsonrpc.NewServer(jsonrpc.WithMethodTransformer(strcase.ToSnake)) + + // create a handler instance and register it + serverHandler := &SimpleServerHandler{} + rpcServer.Register("SimpleServerHandler", serverHandler) + + // serve the api + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) + + // rpc method becomes SimpleServerHandler.add_get + + [..do other app stuff / wait..] +} +``` + ## Contribute PRs are welcome! From 5fdbbd64584a19e17ddcf5a19bca9f0f35bbe458 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Tue, 18 Mar 2025 21:00:04 +0530 Subject: [PATCH 4/9] ft: rename to methodTransformer --- handler.go | 8 ++++---- options_server.go | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/handler.go b/handler.go index 7a74fe4..faff7aa 100644 --- a/handler.go +++ b/handler.go @@ -77,7 +77,7 @@ type handler struct { paramDecoders map[reflect.Type]ParamDecoder - methodCaseTransformer MethodCaseTransformer + methodTransformer MethodTransformer tracer Tracer separator string @@ -93,7 +93,7 @@ func makeHandler(sc ServerConfig) *handler { aliasedMethods: map[string]string{}, paramDecoders: sc.paramDecoders, - methodCaseTransformer: sc.methodCaseTransformer, + methodTransformer: sc.methodTransformer, maxRequestSize: sc.maxRequestSize, @@ -131,8 +131,8 @@ func (s *handler) register(namespace string, r interface{}) { } valOut, errOut, _ := processFuncOut(funcType) - if s.methodCaseTransformer != nil { - method.Name = s.methodCaseTransformer(method.Name) + if s.methodTransformer != nil { + method.Name = s.methodTransformer(method.Name) } s.methods[namespace+s.separator+method.Name] = methodHandler{ diff --git a/options_server.go b/options_server.go index 8a6a221..b5c544b 100644 --- a/options_server.go +++ b/options_server.go @@ -13,7 +13,7 @@ type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) -type MethodCaseTransformer func(string) string +type MethodTransformer func(string) string const defaultSeparator = "." @@ -24,10 +24,10 @@ type ServerConfig struct { paramDecoders map[reflect.Type]ParamDecoder errors *Errors - reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) - tracer Tracer - methodCaseTransformer MethodCaseTransformer - separator string + reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) + tracer Tracer + methodTransformer MethodTransformer + separator string } type ServerOption func(c *ServerConfig) @@ -72,9 +72,9 @@ func WithNamespaceSeparator(separator string) ServerOption { } } -func WithMethodTransformer(methodCaseTransformer MethodCaseTransformer) ServerOption { +func WithMethodTransformer(methodTransformer MethodTransformer) ServerOption { return func(c *ServerConfig) { - c.methodCaseTransformer = methodCaseTransformer + c.methodTransformer = methodTransformer } } From 89bdd8a096863d4c7a07337c8e4ca63574935cd5 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Tue, 18 Mar 2025 21:03:29 +0530 Subject: [PATCH 5/9] ft: better naming --- README.md | 4 ++-- handler.go | 8 ++++---- options_server.go | 14 +++++++------- rpc_test.go | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f917534..c599266 100644 --- a/README.md +++ b/README.md @@ -269,11 +269,11 @@ func main() { } ``` -### Using `WithMethodTransformer` +### Using `WithMethodNameTransformer` ```go func main() { // create a new server instance with a custom method transformer - rpcServer := jsonrpc.NewServer(jsonrpc.WithMethodTransformer(strcase.ToSnake)) + rpcServer := jsonrpc.NewServer(jsonrpc.WithMethodNameTransformer(toSnakeCase)) // create a handler instance and register it serverHandler := &SimpleServerHandler{} diff --git a/handler.go b/handler.go index faff7aa..4ca2aab 100644 --- a/handler.go +++ b/handler.go @@ -77,7 +77,7 @@ type handler struct { paramDecoders map[reflect.Type]ParamDecoder - methodTransformer MethodTransformer + methodNameTransformer MethodNameTransformer tracer Tracer separator string @@ -93,7 +93,7 @@ func makeHandler(sc ServerConfig) *handler { aliasedMethods: map[string]string{}, paramDecoders: sc.paramDecoders, - methodTransformer: sc.methodTransformer, + methodNameTransformer: sc.methodNameTransformer, maxRequestSize: sc.maxRequestSize, @@ -131,8 +131,8 @@ func (s *handler) register(namespace string, r interface{}) { } valOut, errOut, _ := processFuncOut(funcType) - if s.methodTransformer != nil { - method.Name = s.methodTransformer(method.Name) + if s.methodNameTransformer != nil { + method.Name = s.methodNameTransformer(method.Name) } s.methods[namespace+s.separator+method.Name] = methodHandler{ diff --git a/options_server.go b/options_server.go index b5c544b..df3db1e 100644 --- a/options_server.go +++ b/options_server.go @@ -13,7 +13,7 @@ type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) -type MethodTransformer func(string) string +type MethodNameTransformer func(string) string const defaultSeparator = "." @@ -24,10 +24,10 @@ type ServerConfig struct { paramDecoders map[reflect.Type]ParamDecoder errors *Errors - reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) - tracer Tracer - methodTransformer MethodTransformer - separator string + reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) + tracer Tracer + methodNameTransformer MethodNameTransformer + separator string } type ServerOption func(c *ServerConfig) @@ -72,9 +72,9 @@ func WithNamespaceSeparator(separator string) ServerOption { } } -func WithMethodTransformer(methodTransformer MethodTransformer) ServerOption { +func WithMethodNameTransformer(methodNameTransformer MethodNameTransformer) ServerOption { return func(c *ServerConfig) { - c.methodTransformer = methodTransformer + c.methodNameTransformer = methodNameTransformer } } diff --git a/rpc_test.go b/rpc_test.go index 59c01bf..1d045f8 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -1757,7 +1757,7 @@ func TestCallWithMethodTransformer(t *testing.T) { var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") - rpcServer := NewServer(WithMethodTransformer(func(method string) string { + rpcServer := NewServer(WithMethodNameTransformer(func(method string) string { snake := matchFirstCap.ReplaceAllString(method, "${1}_${2}") snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") return strings.ToLower(snake) From 7cff1ccd770fa75d48ff9ecabc47790ce2b197fb Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 21 Mar 2025 22:12:08 +0530 Subject: [PATCH 6/9] fmt --- .gitignore | 1 - handler.go | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 62c8935..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.idea/ \ No newline at end of file diff --git a/handler.go b/handler.go index 4ca2aab..a1942fe 100644 --- a/handler.go +++ b/handler.go @@ -312,14 +312,7 @@ func (s *handler) createError(err error) *JSONRPCError { return out } -func (s *handler) handle( - ctx context.Context, - req request, - w func(func(io.Writer)), - rpcError rpcErrFunc, - done func(keepCtx bool), - chOut chanOut, -) { +func (s *handler) handle(ctx context.Context, req request, w func(func(io.Writer)), rpcError rpcErrFunc, done func(keepCtx bool), chOut chanOut) { // Not sure if we need to sanitize the incoming req.Method or not. ctx, span := s.getSpan(ctx, req) ctx, _ = tag.New(ctx, tag.Insert(metrics.RPCMethod, req.Method)) From e1e380b381b29bc48c801abd26d66cad21c4a98e Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 21 Mar 2025 22:24:07 +0530 Subject: [PATCH 7/9] ft: add a common name formatter --- README.md | 96 ++++++++++++++++++++--------------------------- handler.go | 15 +++----- options_server.go | 25 +++++------- rpc_test.go | 39 +------------------ 4 files changed, 56 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index c599266..8ef9fc3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -go-jsonrpc -================== +# go-jsonrpc [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/filecoin-project/go-jsonrpc) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) @@ -24,41 +23,42 @@ func (h *SimpleServerHandler) AddGet(in int) int { func main() { // create a new server instance rpcServer := jsonrpc.NewServer() - + // create a handler instance and register it serverHandler := &SimpleServerHandler{} rpcServer.Register("SimpleServerHandler", serverHandler) - + // rpcServer is now http.Handler which will serve jsonrpc calls to SimpleServerHandler.AddGet // a method with a single int param, and an int response. The server supports both http and websockets. - + // serve the api testServ := httptest.NewServer(rpcServer) defer testServ.Close() - + fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) - + [..do other app stuff / wait..] } ``` ### Client + ```go func start() error { // Create a struct where each field is an exported function with signatures matching rpc calls var client struct { AddGet func(int) int } - + // Make jsonrp populate func fields in the struct with JSONRPC calls closer, err := jsonrpc.NewClient(context.Background(), rpcURL, "SimpleServerHandler", &client, nil) if err != nil { return err } defer closer() - + ... - + n := client.AddGet(10) // if the server is the one from the example above, n = 10 @@ -73,11 +73,11 @@ func start() error { type _ interface { // No Params / Return val Func1() - + // With Params // Note: If param types implement json.[Un]Marshaler, go-jsonrpc will use it Func2(param1 int, param2 string, param3 struct{A int}) - + // Returning errors // * For some connection errors, go-jsonrpc will return jsonrpc.RPCConnectionError{}. // * RPC-returned errors will be constructed with basic errors.New(__"string message"__) @@ -85,16 +85,16 @@ type _ interface { // * For typed errors to work, server needs to be constructed with the `WithServerErrors` // option, and the client needs to be constructed with the `WithErrors` option Func3() error - + // Returning a value // Note: The value must be serializable with encoding/json. Func4() int - + // Returning a value and an error // Note: if the handler returns an error and a non-zero value, the value will not // be returned to the client - the client will see a zero value. Func4() (int, error) - + // With context // * Context isn't passed as JSONRPC param, instead it has a number of different uses // * When the context is cancelled on the client side, context cancellation should propagate to the server handler @@ -103,9 +103,9 @@ type _ interface { // * If the context contains an opencensus trace span, it will be propagated to the server through a // `"Meta": {"SpanContext": base64.StdEncoding.EncodeToString(propagation.Binary(span.SpanContext()))}` field in // the jsonrpc request - // + // Func5(ctx context.Context, param1 string) error - + // With non-json-serializable (e.g. interface) params // * There are client and server options which make it possible to register transformers for types // to make them json-(de)serializable @@ -115,7 +115,7 @@ type _ interface { // which will pass reader data through separate http streams on a different hanhler. // * Note: a similar mechanism for return value transformation isn't supported yet Func6(r io.Reader) - + // Returning a channel // * Only supported in websocket mode // * If no error is returned, the return value will be an int channelId @@ -132,12 +132,13 @@ type _ interface { ``` ### Custom Transport Feature + The go-jsonrpc library supports creating clients with custom transport mechanisms (e.g. use for IPC). This allows for greater flexibility in how requests are sent and received, enabling the use of custom protocols, special handling of requests, or integration with other systems. #### Example Usage of Custom Transport Here is an example demonstrating how to create a custom client with a custom transport mechanism: - + ```go // Setup server serverHandler := &SimpleServerHandler{} // some type with methods @@ -175,6 +176,7 @@ fmt.Printf("Current value: %d\n", client.AddGet(5)) ``` ### Reverse Calling Feature + 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. NOTE: Reverse calling only works in websocket mode @@ -244,49 +246,31 @@ if err := client.Call(); err != nil { } ``` -## Options +## Options +### Using `WithMethodNameFormatter` -### Using `WithNamespaceSeparator` -```go +```go func main() { - // create a new server instance with a custom namespace separator - rpcServer := jsonrpc.NewServer(jsonrpc.WithNamespaceSeparator("_")) - - // create a handler instance and register it - serverHandler := &SimpleServerHandler{} - rpcServer.Register("SimpleServerHandler", serverHandler) - - // serve the api - testServ := httptest.NewServer(rpcServer) - defer testServ.Close() - - fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) + // create a new server instance with a custom separator + rpcServer := jsonrpc.NewServer(jsonrpc.WithMethodNameFormatter( + func(namespace, method string) string { + return namespace + "_" + method + }), + ) - // rpc method becomes SimpleServerHandler_AddGet - - [..do other app stuff / wait..] -} -``` + // create a handler instance and register it + serverHandler := &SimpleServerHandler{} + rpcServer.Register("SimpleServerHandler", serverHandler) -### Using `WithMethodNameTransformer` -```go -func main() { - // create a new server instance with a custom method transformer - rpcServer := jsonrpc.NewServer(jsonrpc.WithMethodNameTransformer(toSnakeCase)) - - // create a handler instance and register it - serverHandler := &SimpleServerHandler{} - rpcServer.Register("SimpleServerHandler", serverHandler) - - // serve the api - testServ := httptest.NewServer(rpcServer) - defer testServ.Close() - - fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) + // serve the api + testServ := httptest.NewServer(rpcServer) + defer testServ.Close() + + fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) + + // rpc method becomes SimpleServerHandler_AddGet - // rpc method becomes SimpleServerHandler.add_get - [..do other app stuff / wait..] } ``` diff --git a/handler.go b/handler.go index a1942fe..b54b997 100644 --- a/handler.go +++ b/handler.go @@ -77,10 +77,9 @@ type handler struct { paramDecoders map[reflect.Type]ParamDecoder - methodNameTransformer MethodNameTransformer + methodNameFormatter MethodNameFormatter - tracer Tracer - separator string + tracer Tracer } type Tracer func(method string, params []reflect.Value, results []reflect.Value, err error) @@ -93,12 +92,11 @@ func makeHandler(sc ServerConfig) *handler { aliasedMethods: map[string]string{}, paramDecoders: sc.paramDecoders, - methodNameTransformer: sc.methodNameTransformer, + methodNameFormatter: sc.methodNameFormatter, maxRequestSize: sc.maxRequestSize, - tracer: sc.tracer, - separator: sc.separator, + tracer: sc.tracer, } } @@ -131,11 +129,8 @@ func (s *handler) register(namespace string, r interface{}) { } valOut, errOut, _ := processFuncOut(funcType) - if s.methodNameTransformer != nil { - method.Name = s.methodNameTransformer(method.Name) - } - s.methods[namespace+s.separator+method.Name] = methodHandler{ + s.methods[s.methodNameFormatter(namespace, method.Name)] = methodHandler{ paramReceivers: recvs, nParams: ins, diff --git a/options_server.go b/options_server.go index df3db1e..1b6a14a 100644 --- a/options_server.go +++ b/options_server.go @@ -13,9 +13,7 @@ type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) -type MethodNameTransformer func(string) string - -const defaultSeparator = "." +type MethodNameFormatter func(namespace string, method string) string type ServerConfig struct { maxRequestSize int64 @@ -24,10 +22,9 @@ type ServerConfig struct { paramDecoders map[reflect.Type]ParamDecoder errors *Errors - reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) - tracer Tracer - methodNameTransformer MethodNameTransformer - separator string + reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) + tracer Tracer + methodNameFormatter MethodNameFormatter } type ServerOption func(c *ServerConfig) @@ -38,7 +35,9 @@ func defaultServerConfig() ServerConfig { maxRequestSize: DEFAULT_MAX_REQUEST_SIZE, pingInterval: 5 * time.Second, - separator: defaultSeparator, + methodNameFormatter: func(namespace, method string) string { + return namespace + "." + method + }, } } @@ -66,15 +65,9 @@ func WithServerPingInterval(d time.Duration) ServerOption { } } -func WithNamespaceSeparator(separator string) ServerOption { - return func(c *ServerConfig) { - c.separator = separator - } -} - -func WithMethodNameTransformer(methodNameTransformer MethodNameTransformer) ServerOption { +func WithMethodNameFormatter(formatter MethodNameFormatter) ServerOption { return func(c *ServerConfig) { - c.methodNameTransformer = methodNameTransformer + c.methodNameFormatter = formatter } } diff --git a/rpc_test.go b/rpc_test.go index 1d045f8..832d0c1 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -12,7 +12,6 @@ import ( "net/http/httptest" "os" "reflect" - "regexp" "strconv" "strings" "sync" @@ -1717,10 +1716,10 @@ func TestNewCustomClient(t *testing.T) { require.Equal(t, int32(13), serverHandler.n) } -func TestReverseCallWithCustomSeparator(t *testing.T) { +func TestReverseCallWithCustomMethodName(t *testing.T) { // setup server - rpcServer := NewServer(WithNamespaceSeparator("_")) + rpcServer := NewServer(WithMethodNameFormatter(func(namespace, method string) string { return namespace + "_" + method })) rpcServer.Register("Server", &RawParamHandler{}) // httptest stuff @@ -1750,37 +1749,3 @@ type MethodTransformedHandler struct{} func (h *RawParamHandler) CallSomethingInSnakeCase(ctx context.Context, v int) (int, error) { return v + 1, nil } - -func TestCallWithMethodTransformer(t *testing.T) { - // setup server - - var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") - var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") - - rpcServer := NewServer(WithMethodNameTransformer(func(method string) string { - snake := matchFirstCap.ReplaceAllString(method, "${1}_${2}") - snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") - return strings.ToLower(snake) - })) - rpcServer.Register("Raw", &RawParamHandler{}) - - // httptest stuff - testServ := httptest.NewServer(rpcServer) - defer testServ.Close() - - // setup client - var client struct { - Call func(ctx context.Context, v int) (int, error) `rpc_method:"Raw.call_something_in_snake_case"` - } - closer, err := NewMergeClient(context.Background(), "ws://"+testServ.Listener.Addr().String(), "Raw", []interface{}{ - &client, - }, nil) - require.NoError(t, err) - - // this will block if it's not sent as a notification - n, err := client.Call(context.Background(), 6) - require.NoError(t, err) - require.Equal(t, 7, n) - - closer() -} From fc74ed817b3353c6191ec9c78d56543cbd050a46 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 21 Mar 2025 22:28:24 +0530 Subject: [PATCH 8/9] fmt --- README.md | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8ef9fc3..444a6d0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# go-jsonrpc +go-jsonrpc +================== [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/filecoin-project/go-jsonrpc) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) @@ -23,42 +24,41 @@ func (h *SimpleServerHandler) AddGet(in int) int { func main() { // create a new server instance rpcServer := jsonrpc.NewServer() - + // create a handler instance and register it serverHandler := &SimpleServerHandler{} rpcServer.Register("SimpleServerHandler", serverHandler) - + // rpcServer is now http.Handler which will serve jsonrpc calls to SimpleServerHandler.AddGet // a method with a single int param, and an int response. The server supports both http and websockets. - + // serve the api testServ := httptest.NewServer(rpcServer) defer testServ.Close() - + fmt.Println("URL: ", "ws://"+testServ.Listener.Addr().String()) - + [..do other app stuff / wait..] } ``` ### Client - ```go func start() error { // Create a struct where each field is an exported function with signatures matching rpc calls var client struct { AddGet func(int) int } - + // Make jsonrp populate func fields in the struct with JSONRPC calls closer, err := jsonrpc.NewClient(context.Background(), rpcURL, "SimpleServerHandler", &client, nil) if err != nil { return err } defer closer() - + ... - + n := client.AddGet(10) // if the server is the one from the example above, n = 10 @@ -73,11 +73,11 @@ func start() error { type _ interface { // No Params / Return val Func1() - + // With Params // Note: If param types implement json.[Un]Marshaler, go-jsonrpc will use it Func2(param1 int, param2 string, param3 struct{A int}) - + // Returning errors // * For some connection errors, go-jsonrpc will return jsonrpc.RPCConnectionError{}. // * RPC-returned errors will be constructed with basic errors.New(__"string message"__) @@ -85,16 +85,16 @@ type _ interface { // * For typed errors to work, server needs to be constructed with the `WithServerErrors` // option, and the client needs to be constructed with the `WithErrors` option Func3() error - + // Returning a value // Note: The value must be serializable with encoding/json. Func4() int - + // Returning a value and an error // Note: if the handler returns an error and a non-zero value, the value will not // be returned to the client - the client will see a zero value. Func4() (int, error) - + // With context // * Context isn't passed as JSONRPC param, instead it has a number of different uses // * When the context is cancelled on the client side, context cancellation should propagate to the server handler @@ -103,9 +103,9 @@ type _ interface { // * If the context contains an opencensus trace span, it will be propagated to the server through a // `"Meta": {"SpanContext": base64.StdEncoding.EncodeToString(propagation.Binary(span.SpanContext()))}` field in // the jsonrpc request - // + // Func5(ctx context.Context, param1 string) error - + // With non-json-serializable (e.g. interface) params // * There are client and server options which make it possible to register transformers for types // to make them json-(de)serializable @@ -115,7 +115,7 @@ type _ interface { // which will pass reader data through separate http streams on a different hanhler. // * Note: a similar mechanism for return value transformation isn't supported yet Func6(r io.Reader) - + // Returning a channel // * Only supported in websocket mode // * If no error is returned, the return value will be an int channelId @@ -132,13 +132,12 @@ type _ interface { ``` ### Custom Transport Feature - The go-jsonrpc library supports creating clients with custom transport mechanisms (e.g. use for IPC). This allows for greater flexibility in how requests are sent and received, enabling the use of custom protocols, special handling of requests, or integration with other systems. #### Example Usage of Custom Transport Here is an example demonstrating how to create a custom client with a custom transport mechanism: - + ```go // Setup server serverHandler := &SimpleServerHandler{} // some type with methods @@ -176,7 +175,6 @@ fmt.Printf("Current value: %d\n", client.AddGet(5)) ``` ### Reverse Calling Feature - 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. NOTE: Reverse calling only works in websocket mode @@ -275,10 +273,11 @@ func main() { } ``` + ## Contribute 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) +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 From 9d9dbaf832bf05c3ff544162c6636c694a44a1b4 Mon Sep 17 00:00:00 2001 From: Rahul Tripathi Date: Fri, 21 Mar 2025 22:32:05 +0530 Subject: [PATCH 9/9] fmt --- options_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options_server.go b/options_server.go index 1b6a14a..7a3e2d3 100644 --- a/options_server.go +++ b/options_server.go @@ -13,7 +13,7 @@ type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) -type MethodNameFormatter func(namespace string, method string) string +type MethodNameFormatter func(namespace, method string) string type ServerConfig struct { maxRequestSize int64