diff --git a/internal/jsonrpc2/messages.go b/internal/jsonrpc2/messages.go index 77c95313..a33961cd 100644 --- a/internal/jsonrpc2/messages.go +++ b/internal/jsonrpc2/messages.go @@ -172,6 +172,10 @@ func EncodeIndent(msg Message, prefix, indent string) ([]byte, error) { } func DecodeMessage(data []byte) (Message, error) { + fields := map[string]json.RawMessage{} + if err := internaljson.Unmarshal(data, &fields); err != nil { + return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) + } msg := wireCombined{} if err := internaljson.Unmarshal(data, &msg); err != nil { return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) @@ -183,8 +187,8 @@ func DecodeMessage(data []byte) (Message, error) { if err != nil { return nil, err } - if msg.Method != "" { - // has a method, must be a call + if _, ok := fields["method"]; ok { + // has a method field, must be a call, even if the method is empty return &Request{ Method: msg.Method, ID: id, diff --git a/internal/jsonrpc2/wire_test.go b/internal/jsonrpc2/wire_test.go index 6f576f08..316f2e8f 100644 --- a/internal/jsonrpc2/wire_test.go +++ b/internal/jsonrpc2/wire_test.go @@ -63,6 +63,23 @@ func TestWireMessage(t *testing.T) { } } +func TestDecodeRequestWithEmptyMethod(t *testing.T) { + msg, err := jsonrpc2.DecodeMessage([]byte(`{"jsonrpc":"2.0","id":5,"method":"","params":{}}`)) + if err != nil { + t.Fatal(err) + } + req, ok := msg.(*jsonrpc2.Request) + if !ok { + t.Fatalf("DecodeMessage returned %T, want *jsonrpc2.Request", msg) + } + if req.Method != "" { + t.Fatalf("Request method = %q, want empty string", req.Method) + } + if !req.ID.IsValid() || req.ID.Raw() != int64(5) { + t.Fatalf("Request ID = %#v, want 5", req.ID.Raw()) + } +} + func newNotification(method string, params any) jsonrpc2.Message { msg, err := jsonrpc2.NewNotification(method, params) if err != nil {