internal/jsonrpc2: test WireError.Is and fix nil target panic (#1079)

# Problem
WireError.Is backs errors.Is checks used across the SDK against
sentinels such as ErrMethodNotFound, ErrRejected, and ErrClientClosing,
but had no direct test. Add a table-driven test for it.

While doing so, errors.Is(err, (*WireError)(nil)) was found to panic on
a nil pointer dereference, because Is read w.Code without a nil check.
Guard against a nil *WireError target so the comparison returns false.

  ```go
  errors.Is(err, (*WireError)(nil)) // panic: nil pointer dereference
 ```

# Approach


Is read w.Code after a successful type assertion without checking
whether w itself was nil. The fix guards against a nil *WireError target
so the comparison returns false instead of panicking.
# Test plan

  - go test ./internal/jsonrpc2/ passes.
- The new typed nil WireError case panics without the one-line fix and
passes with it, confirming the test exercises the guarded path.

Co-authored-by: Yaroslav <yarolegovich@gmail.com>
This commit is contained in:
jansen 2026-08-21 16:56:31 +08:00 • committed by GitHub
commit b074c7f3af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 1 deletions

View file

@ -87,7 +87,7 @@ func (err *WireError) Error() string {
func (err *WireError) Is(other error) bool {
w, ok := other.(*WireError)
if !ok {
if !ok || w == nil {
return false
}
return err.Code == w.Code

View file

@ -7,6 +7,7 @@ package jsonrpc2_test
import (
"bytes"
"encoding/json"
"errors"
"reflect"
"testing"
@ -147,6 +148,29 @@ func TestDecodeIDOnlyMessageIsResponse(t *testing.T) {
}
}
// TestWireErrorIs checks the errors.Is behavior of WireError, including that
// comparing against a typed-nil *WireError does not panic.
func TestWireErrorIs(t *testing.T) {
err := jsonrpc2.NewError(-32600, "invalid request")
for _, test := range []struct {
name string
target error
want bool
}{
{name: "same code", target: jsonrpc2.NewError(-32600, "other message"), want: true},
{name: "different code", target: jsonrpc2.NewError(-32601, "invalid request"), want: false},
{name: "typed nil WireError", target: (*jsonrpc2.WireError)(nil), want: false},
{name: "untyped nil", target: nil, want: false},
{name: "other error type", target: errors.New("invalid request"), want: false},
} {
t.Run(test.name, func(t *testing.T) {
if got := errors.Is(err, test.target); got != test.want {
t.Errorf("errors.Is(%v, %v) = %v, want %v", err, test.target, got, test.want)
}
})
}
}
func checkJSON(t *testing.T, got, want []byte) {
// compare the compact form, to allow for formatting differences
g := &bytes.Buffer{}