Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions v2/json2/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,76 @@ func TestDecodeNullResult(t *testing.T) {
t.Error("Expected result to be nil, but got:", result)
}
}

type notificationArgs struct {
Message string
Priority uint8
Critical bool
}

func TestReadRequestPositionalStruct(t *testing.T) {
body := `{"jsonrpc":"2.0","method":"Service1.Multiply","params":[4,2],"id":1}`
r, err := http.NewRequest("POST", "/", strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
cr := NewCodec().NewRequest(r)
var args Service1Request
if err := cr.ReadRequest(&args); err != nil {
t.Fatal(err)
}
if args.A != 4 || args.B != 2 {
t.Fatalf("got %+v, want A=4 B=2", args)
}

s := rpc.NewServer()
s.RegisterCodec(NewCodec(), "application/json")
if err := s.RegisterService(new(Service1), ""); err != nil {
t.Fatal(err)
}
var res Service1Response
raw := map[string]interface{}{
"jsonrpc": "2.0",
"method": "Service1.Multiply",
"params": []int{4, 2},
"id": 1,
}
if err := executeRaw(t, s, raw, &res); err != nil {
t.Fatal(err)
}
if res.Result != 8 {
t.Fatalf("Multiply [4,2] got %d, want 8", res.Result)
}
}

func TestReadRequestPositionalNotification(t *testing.T) {
body := `{"jsonrpc":"2.0","method":"X.Y","params":["Hello world", 10, false],"id":1}`
r, err := http.NewRequest("POST", "/", strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
cr := NewCodec().NewRequest(r)
var args notificationArgs
if err := cr.ReadRequest(&args); err != nil {
t.Fatal(err)
}
if args.Message != "Hello world" || args.Priority != 10 || args.Critical {
t.Fatalf("got %+v", args)
}
}

func TestReadRequestByNameStillWorks(t *testing.T) {
body := `{"jsonrpc":"2.0","method":"Service1.Multiply","params":{"A":4,"B":2},"id":1}`
r, err := http.NewRequest("POST", "/", strings.NewReader(body))
if err != nil {
t.Fatal(err)
}
cr := NewCodec().NewRequest(r)
var args Service1Request
if err := cr.ReadRequest(&args); err != nil {
t.Fatal(err)
}
if args.A != 4 || args.B != 2 {
t.Fatalf("got %+v, want A=4 B=2", args)
}
}
38 changes: 32 additions & 6 deletions v2/json2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"encoding/json"
"io"
"net/http"
"reflect"

"github.com/gorilla/rpc/v2"
)
Expand Down Expand Up @@ -175,12 +176,12 @@ func (c *CodecRequest) ReadRequest(args interface{}) error {
// Note: if c.request.Params is nil it's not an error, it's an optional member.
// JSON params structured object. Unmarshal to the args object.
if err := json.Unmarshal(*c.request.Params, args); err != nil {
// Clearly JSON params is not a structured object,
// fallback and attempt an unmarshal with JSON params as
// array value and RPC params is struct. Unmarshal into
// array containing the request struct.
params := [1]interface{}{args}
if err = json.Unmarshal(*c.request.Params, &params); err != nil {
// Clearly JSON params is not a structured object.
// JSON-RPC 2.0 by-position params is an array of values in
// the method's expected order. First try the historical
// one-element wrap (array of a single object). If that
// fails, map array elements onto exported struct fields.
if err = unmarshalArrayParams(*c.request.Params, args); err != nil {
c.err = &Error{
Code: E_INVALID_REQ,
Message: err.Error(),
Expand All @@ -192,6 +193,31 @@ func (c *CodecRequest) ReadRequest(args interface{}) error {
return c.err
}

// unmarshalArrayParams decodes a JSON-RPC 2.0 by-position params array.
func unmarshalArrayParams(params json.RawMessage, args interface{}) error {
wrap := [1]interface{}{args}
if err := json.Unmarshal(params, &wrap); err == nil {
return nil
}
rv := reflect.ValueOf(args)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return json.Unmarshal(params, &wrap)
}
ev := rv.Elem()
if ev.Kind() != reflect.Struct {
return json.Unmarshal(params, &wrap)
}
fields := make([]interface{}, 0, ev.NumField())
for i := 0; i < ev.NumField(); i++ {
f := ev.Field(i)
if !f.CanSet() {
continue
}
fields = append(fields, f.Addr().Interface())
}
return json.Unmarshal(params, &fields)
}

// WriteResponse encodes the response and writes it to the ResponseWriter.
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
res := &serverResponse{
Expand Down
Loading