rpc: add method to test for subscription support (#25942)
This adds two ways to check for subscription support. First, one can now check whether the transport method (HTTP/WS/etc.) is capable of subscriptions using the new Client.SupportsSubscriptions method. Second, the error returned by Subscribe can now reliably be tested using this pattern: sub, err := client.Subscribe(...) if errors.Is(err, rpc.ErrNotificationsUnsupported) { // no subscription support } --------- Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
8bbaf882a6
commit
6f08c2f3f1
|
@ -538,6 +538,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf
|
||||||
return op.sub, nil
|
return op.sub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportsSubscriptions reports whether subscriptions are supported by the client
|
||||||
|
// transport. When this returns false, Subscribe and related methods will return
|
||||||
|
// ErrNotificationsUnsupported.
|
||||||
|
func (c *Client) SupportsSubscriptions() bool {
|
||||||
|
return !c.isHTTP
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) {
|
func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) {
|
||||||
msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method}
|
msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method}
|
||||||
if paramsIn != nil { // prevent sending "params":null
|
if paramsIn != nil { // prevent sending "params":null
|
||||||
|
|
|
@ -59,11 +59,12 @@ var (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errcodeDefault = -32000
|
errcodeDefault = -32000
|
||||||
errcodeNotificationsUnsupported = -32001
|
|
||||||
errcodeTimeout = -32002
|
errcodeTimeout = -32002
|
||||||
errcodeResponseTooLarge = -32003
|
errcodeResponseTooLarge = -32003
|
||||||
errcodePanic = -32603
|
errcodePanic = -32603
|
||||||
errcodeMarshalError = -32603
|
errcodeMarshalError = -32603
|
||||||
|
|
||||||
|
legacyErrcodeNotificationsUnsupported = -32001
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -80,6 +81,34 @@ func (e *methodNotFoundError) Error() string {
|
||||||
return fmt.Sprintf("the method %s does not exist/is not available", e.method)
|
return fmt.Sprintf("the method %s does not exist/is not available", e.method)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type notificationsUnsupportedError struct{}
|
||||||
|
|
||||||
|
func (e notificationsUnsupportedError) Error() string {
|
||||||
|
return "notifications not supported"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e notificationsUnsupportedError) ErrorCode() int { return -32601 }
|
||||||
|
|
||||||
|
// Is checks for equivalence to another error. Here we define that all errors with code
|
||||||
|
// -32601 (method not found) are equivalent to notificationsUnsupportedError. This is
|
||||||
|
// done to enable the following pattern:
|
||||||
|
//
|
||||||
|
// sub, err := client.Subscribe(...)
|
||||||
|
// if errors.Is(err, rpc.ErrNotificationsUnsupported) {
|
||||||
|
// // server doesn't support subscriptions
|
||||||
|
// }
|
||||||
|
func (e notificationsUnsupportedError) Is(other error) bool {
|
||||||
|
if other == (notificationsUnsupportedError{}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
rpcErr, ok := other.(Error)
|
||||||
|
if ok {
|
||||||
|
code := rpcErr.ErrorCode()
|
||||||
|
return code == -32601 || code == legacyErrcodeNotificationsUnsupported
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type subscriptionNotFoundError struct{ namespace, subscription string }
|
type subscriptionNotFoundError struct{ namespace, subscription string }
|
||||||
|
|
||||||
func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 }
|
func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 }
|
||||||
|
|
|
@ -530,10 +530,7 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
|
||||||
// handleSubscribe processes *_subscribe method calls.
|
// handleSubscribe processes *_subscribe method calls.
|
||||||
func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
|
func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage {
|
||||||
if !h.allowSubscribe {
|
if !h.allowSubscribe {
|
||||||
return msg.errorResponse(&internalServerError{
|
return msg.errorResponse(ErrNotificationsUnsupported)
|
||||||
code: errcodeNotificationsUnsupported,
|
|
||||||
message: ErrNotificationsUnsupported.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscription method name is first argument.
|
// Subscription method name is first argument.
|
||||||
|
|
|
@ -32,8 +32,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
|
// ErrNotificationsUnsupported is returned by the client when the connection doesn't
|
||||||
ErrNotificationsUnsupported = errors.New("notifications not supported")
|
// support notifications. You can use this error value to check for subscription
|
||||||
|
// support like this:
|
||||||
|
//
|
||||||
|
// sub, err := client.EthSubscribe(ctx, channel, "newHeads", true)
|
||||||
|
// if errors.Is(err, rpc.ErrNotificationsUnsupported) {
|
||||||
|
// // Server does not support subscriptions, fall back to polling.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
ErrNotificationsUnsupported = notificationsUnsupportedError{}
|
||||||
|
|
||||||
// ErrSubscriptionNotFound is returned when the notification for the given id is not found
|
// ErrSubscriptionNotFound is returned when the notification for the given id is not found
|
||||||
ErrSubscriptionNotFound = errors.New("subscription not found")
|
ErrSubscriptionNotFound = errors.New("subscription not found")
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue