mirror of
https://github.com/sourcegraph/jsonrpc2.git
synced 2026-07-02 15:23:41 +02:00
support responses with null result ({"result":null})
Previously, we incorrectly interpreted these as neither a request nor a response, and we printed an error for them. This is incorrect behavior per JSON-RPC 2.0 spec; responses can have a null result.
This commit is contained in:
parent
6e06d561ec
commit
d58e8cc226
3 changed files with 34 additions and 9 deletions
27
jsonrpc2.go
27
jsonrpc2.go
|
|
@ -3,6 +3,7 @@
|
||||||
package jsonrpc2
|
package jsonrpc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -540,15 +541,16 @@ func (m *anyMessage) UnmarshalJSON(data []byte) error {
|
||||||
// The presence of these fields distinguishes between the 2
|
// The presence of these fields distinguishes between the 2
|
||||||
// message types.
|
// message types.
|
||||||
type msg struct {
|
type msg struct {
|
||||||
|
ID interface{} `json:"id"`
|
||||||
Method *string `json:"method"`
|
Method *string `json:"method"`
|
||||||
Result interface{} `json:"result"`
|
Result anyValueWithExplicitNull `json:"result"`
|
||||||
Error interface{} `json:"error"`
|
Error interface{} `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var isRequest, isResponse bool
|
var isRequest, isResponse bool
|
||||||
checkType := func(m *msg) error {
|
checkType := func(m *msg) error {
|
||||||
mIsRequest := m.Method != nil
|
mIsRequest := m.Method != nil
|
||||||
mIsResponse := m.Result != nil || m.Error != nil
|
mIsResponse := m.Result.null || m.Result.value != nil || m.Error != nil
|
||||||
if (!mIsRequest && !mIsResponse) || (mIsRequest && mIsResponse) {
|
if (!mIsRequest && !mIsResponse) || (mIsRequest && mIsResponse) {
|
||||||
return errors.New("jsonrpc2: unable to determine message type (request or response)")
|
return errors.New("jsonrpc2: unable to determine message type (request or response)")
|
||||||
}
|
}
|
||||||
|
|
@ -593,6 +595,27 @@ func (m *anyMessage) UnmarshalJSON(data []byte) error {
|
||||||
return json.Unmarshal(data, v)
|
return json.Unmarshal(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// anyValueWithExplicitNull is used to distinguish {} from
|
||||||
|
// {"result":null} by anyMessage's JSON unmarshaler.
|
||||||
|
type anyValueWithExplicitNull struct {
|
||||||
|
null bool // JSON "null"
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v anyValueWithExplicitNull) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(v.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *anyValueWithExplicitNull) UnmarshalJSON(data []byte) error {
|
||||||
|
data = bytes.TrimSpace(data)
|
||||||
|
if string(data) == "null" {
|
||||||
|
*v = anyValueWithExplicitNull{null: true}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*v = anyValueWithExplicitNull{}
|
||||||
|
return json.Unmarshal(data, &v.value)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidRequestJSON = errors.New("jsonrpc2: request must be either a JSON object or JSON array")
|
errInvalidRequestJSON = errors.New("jsonrpc2: request must be either a JSON object or JSON array")
|
||||||
errInvalidResponseJSON = errors.New("jsonrpc2: response must be either a JSON object or JSON array")
|
errInvalidResponseJSON = errors.New("jsonrpc2: response must be either a JSON object or JSON array")
|
||||||
|
|
|
||||||
|
|
@ -24,18 +24,19 @@ func TestRequest_MarshalJSON_jsonrpc(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if want := `"jsonrpc":"2.0"`; !strings.Contains(string(b), want) {
|
if want := `{"method":"","id":0,"jsonrpc":"2.0"}`; string(b) != want {
|
||||||
t.Errorf("got %s, want it to include the string %s", b, want)
|
t.Errorf("got %q, want %q", b, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResponse_MarshalJSON_jsonrpc(t *testing.T) {
|
func TestResponse_MarshalJSON_jsonrpc(t *testing.T) {
|
||||||
b, err := json.Marshal(&jsonrpc2.Response{})
|
null := json.RawMessage("null")
|
||||||
|
b, err := json.Marshal(&jsonrpc2.Response{Result: &null})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if want := `"jsonrpc":"2.0"`; !strings.Contains(string(b), want) {
|
if want := `{"id":0,"result":null,"jsonrpc":"2.0"}`; string(b) != want {
|
||||||
t.Errorf("got %s, want it to include the string %s", b, want)
|
t.Errorf("got %q, want %q", b, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ func TestAnyMessage(t *testing.T) {
|
||||||
`{"foo":"bar"}`: {},
|
`{"foo":"bar"}`: {},
|
||||||
`{"method":"m"}`: {request: true},
|
`{"method":"m"}`: {request: true},
|
||||||
`{"result":123}`: {response: true},
|
`{"result":123}`: {response: true},
|
||||||
|
`{"result":null}`: {response: true},
|
||||||
`{"error":{"code":456,"message":"m"}}`: {response: true},
|
`{"error":{"code":456,"message":"m"}}`: {response: true},
|
||||||
}
|
}
|
||||||
for s, want := range tests {
|
for s, want := range tests {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue