package jsonrpc import ( "context" "reflect" "time" "golang.org/x/xerrors" ) // note: we embed reflect.Type because proxy-structs are not comparable type jsonrpcReverseClient struct{ reflect.Type } type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error) type ServerConfig struct { maxRequestSize int64 pingInterval time.Duration paramDecoders map[reflect.Type]ParamDecoder errors *Errors reverseClientBuilder func(context.Context, *wsConn) (context.Context, error) tracer Tracer methodNameFormatter MethodNameFormatter } type ServerOption func(c *ServerConfig) func defaultServerConfig() ServerConfig { return ServerConfig{ paramDecoders: map[reflect.Type]ParamDecoder{}, maxRequestSize: DEFAULT_MAX_REQUEST_SIZE, pingInterval: 5 * time.Second, methodNameFormatter: DefaultMethodNameFormatter, } } func WithParamDecoder(t interface{}, decoder ParamDecoder) ServerOption { return func(c *ServerConfig) { c.paramDecoders[reflect.TypeOf(t).Elem()] = decoder } } func WithMaxRequestSize(max int64) ServerOption { return func(c *ServerConfig) { c.maxRequestSize = max } } func WithServerErrors(es Errors) ServerOption { return func(c *ServerConfig) { c.errors = &es } } func WithServerPingInterval(d time.Duration) ServerOption { return func(c *ServerConfig) { c.pingInterval = d } } func WithServerMethodNameFormatter(formatter MethodNameFormatter) ServerOption { return func(c *ServerConfig) { c.methodNameFormatter = formatter } } // 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 { return func(c *ServerConfig) { c.tracer = l } } // WithReverseClient will allow extracting reverse client on **WEBSOCKET** calls. // RP is a proxy-struct type, much like the one passed to NewClient. 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, paramEncoders: map[reflect.Type]ParamEncoder{}, methodNameFormatter: c.methodNameFormatter, } // todo test that everything is closing correctly cl.exiting = conn.exiting requests := cl.setupRequestChan() conn.requests = requests calls := new(RP) err := cl.provide([]interface{}{ calls, }) if err != nil { return nil, xerrors.Errorf("provide reverse client calls: %w", err) } return context.WithValue(ctx, jsonrpcReverseClient{reflect.TypeOf(calls).Elem()}, calls), nil } } } // ExtractReverseClient will extract reverse client from context. Reverse client for the type // will only be present if the server was constructed with a matching WithReverseClient option // and the connection was a websocket connection. // If there is no reverse client, the call will return a zero value and `false`. Otherwise a reverse // client and `true` will be returned. func ExtractReverseClient[C any](ctx context.Context) (C, bool) { c, ok := ctx.Value(jsonrpcReverseClient{reflect.TypeOf(new(C)).Elem()}).(*C) if !ok { return *new(C), false } if c == nil { // something is very wrong, but don't panic return *new(C), false } return *c, ok }