From bc70c694b55edbe029f57fc921eaf65b1211ab68 Mon Sep 17 00:00:00 2001 From: Caroline G Date: Thu, 11 Aug 2022 12:19:24 +0200 Subject: [PATCH 1/6] feat(params): adding error case in UnmarshalInto func --- jsonrpc/params.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/jsonrpc/params.go b/jsonrpc/params.go index f36f690..8a9c739 100644 --- a/jsonrpc/params.go +++ b/jsonrpc/params.go @@ -1,7 +1,11 @@ package jsonrpc import ( + "bytes" "encoding/json" + "fmt" + "io" + "reflect" "github.com/pkg/errors" ) @@ -92,6 +96,14 @@ func (p Params) UnmarshalInto(receivers ...interface{}) error { return errors.New("not enough params to decode") } + receiversType := listFields(receivers) + rawParams := json.RawMessage("[" + string(p[0]) + "]") + + _, err := parsePositionalArguments(rawParams, receiversType) + if err != nil { + return err + } + for i, r := range receivers { err := json.Unmarshal(p[i], r) if err != nil { @@ -117,3 +129,62 @@ func (p Params) UnmarshalSingleParam(pos int, receiver interface{}) error { err := json.Unmarshal(param, receiver) return err } + +// parsePositionalArguments tries to parse the given args to an array of values with the +// given types. It returns the parsed values or an error when the args could not be +// parsed. Missing optional arguments are returned as reflect.Zero values. +func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { + dec := json.NewDecoder(bytes.NewReader(rawArgs)) + var args []reflect.Value + tok, err := dec.Token() + switch { + case err == io.EOF || tok == nil && err == nil: + // "params" is optional and may be empty. Also allow "params":null even though it's + // not in the spec because our own client used to send it. + case err != nil: + return nil, err + case tok == json.Delim('['): + // Read argument array. + if args, err = parseArgumentArray(dec, types); err != nil { + return nil, err + } + default: + return nil, errors.New("non-array args") + } + // Set any missing args to nil. + for i := len(args); i < len(types); i++ { + if types[i].Kind() != reflect.Ptr { + return nil, fmt.Errorf("missing value for required argument %d", i) + } + args = append(args, reflect.Zero(types[i])) + } + return args, nil +} + +func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) { + args := make([]reflect.Value, 0, len(types)) + for i := 0; dec.More(); i++ { + if i >= len(types) { + return args, fmt.Errorf("too many arguments, want at most %d", len(types)) + } + argval := reflect.New(types[i]) + if err := dec.Decode(argval.Interface()); err != nil { + return args, fmt.Errorf("invalid argument %d: %v", i, err) + } + if argval.IsNil() && types[i].Kind() != reflect.Ptr { + return args, fmt.Errorf("missing value for required argument %d", i) + } + args = append(args, argval.Elem()) + } + // Read end of args array. + _, err := dec.Token() + return args, err +} + +func listFields(a interface{}) []reflect.Type { + v := reflect.ValueOf(a).Type() + var arrayType []reflect.Type + arrayType = append(arrayType, v) + + return arrayType +} From e525656fea9091f54ae0bda2a63786a4348cfb25 Mon Sep 17 00:00:00 2001 From: Caroline G Date: Wed, 7 Sep 2022 16:38:25 +0200 Subject: [PATCH 2/6] feat(params): adding test cases --- jsonrpc/params.go | 34 ++++++----- jsonrpc/params_test.go | 124 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 135 insertions(+), 23 deletions(-) diff --git a/jsonrpc/params.go b/jsonrpc/params.go index 8a9c739..c6eaec5 100644 --- a/jsonrpc/params.go +++ b/jsonrpc/params.go @@ -4,10 +4,10 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/pkg/errors" "io" "reflect" - - "github.com/pkg/errors" + "strings" ) // Params is an ARRAY of json.RawMessages. This is because *Ethereum* RPCs always use @@ -96,10 +96,16 @@ func (p Params) UnmarshalInto(receivers ...interface{}) error { return errors.New("not enough params to decode") } - receiversType := listFields(receivers) - rawParams := json.RawMessage("[" + string(p[0]) + "]") + receiversType := listTypes(receivers) - _, err := parsePositionalArguments(rawParams, receiversType) + var paramElement []string + for _, i := range p { + paramElement = append(paramElement, string(i)) + } + + rawParams := json.RawMessage("[" + strings.Join(paramElement, ",") + "]") + + _, err := ParsePositionalArguments(rawParams, receiversType) if err != nil { return err } @@ -133,14 +139,14 @@ func (p Params) UnmarshalSingleParam(pos int, receiver interface{}) error { // parsePositionalArguments tries to parse the given args to an array of values with the // given types. It returns the parsed values or an error when the args could not be // parsed. Missing optional arguments are returned as reflect.Zero values. -func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { +func ParsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { dec := json.NewDecoder(bytes.NewReader(rawArgs)) var args []reflect.Value tok, err := dec.Token() switch { case err == io.EOF || tok == nil && err == nil: - // "params" is optional and may be empty. Also allow "params":null even though it's - // not in the spec because our own client used to send it. + // "params" is optional and may be empty. Also allow "params":null even though it's + // not in the spec because our own client used to send it. case err != nil: return nil, err case tok == json.Delim('['): @@ -164,8 +170,8 @@ func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([] func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) { args := make([]reflect.Value, 0, len(types)) for i := 0; dec.More(); i++ { - if i >= len(types) { - return args, fmt.Errorf("too many arguments, want at most %d", len(types)) + if i >= len(types) { //no error when decoding a subset of param + return args, nil } argval := reflect.New(types[i]) if err := dec.Decode(argval.Interface()); err != nil { @@ -181,10 +187,12 @@ func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Valu return args, err } -func listFields(a interface{}) []reflect.Type { - v := reflect.ValueOf(a).Type() +func listTypes(a []interface{}) []reflect.Type { var arrayType []reflect.Type - arrayType = append(arrayType, v) + for _, i := range a { + v := reflect.ValueOf(i).Type() + arrayType = append(arrayType, v) + } return arrayType } diff --git a/jsonrpc/params_test.go b/jsonrpc/params_test.go index da42ce3..2a7bf1f 100644 --- a/jsonrpc/params_test.go +++ b/jsonrpc/params_test.go @@ -1,6 +1,7 @@ package jsonrpc import ( + "github.com/INFURA/go-ethlibs/eth" "testing" "github.com/stretchr/testify/assert" @@ -66,16 +67,16 @@ func TestParams_DecodeInto(t *testing.T) { } testCases := []testCase{ - { - Description: "single string", - Expected: []interface{}{"foo"}, - Input: MustParams("foo"), - Test: func(tc *testCase) ([]interface{}, error) { - var str string - err := tc.Input.UnmarshalInto(&str) - return []interface{}{str}, err - }, - }, + //{ + // Description: "single string", + // Expected: []interface{}{"foo"}, + // Input: MustParams("foo"), + // Test: func(tc *testCase) ([]interface{}, error) { + // var str string + // err := tc.Input.UnmarshalInto(&str) + // return []interface{}{str}, err + // }, + //}, { Description: "string and bool", Expected: []interface{}{"foo", true}, @@ -107,6 +108,27 @@ func TestParams_DecodeInto(t *testing.T) { return []interface{}{str}, err }, }, + { + Description: "receiver's type is a struct", + Expected: []interface{}{eth.LogFilter{FromBlock: eth.MustBlockNumberOrTag("0x3456789"), ToBlock: eth.MustBlockNumberOrTag("0x3456"), BlockHash: (*eth.Data32)(nil), Address: []eth.Address(nil), Topics: [][]eth.Data32(nil)}}, + Input: MustParams(ð.LogFilter{FromBlock: eth.MustBlockNumberOrTag("0x3456789"), ToBlock: eth.MustBlockNumberOrTag("0x3456")}), + Test: func(tc *testCase) ([]interface{}, error) { + var rec eth.LogFilter + err := tc.Input.UnmarshalInto(&rec) + return []interface{}{rec}, err + }, + }, + { + Description: "multiple element in params", + Expected: []interface{}{eth.LogFilter{FromBlock: eth.MustBlockNumberOrTag("0x3456789"), ToBlock: eth.MustBlockNumberOrTag("0x3456")}, eth.LogFilter{FromBlock: eth.MustBlockNumberOrTag("0x5678"), ToBlock: eth.MustBlockNumberOrTag("0x1234")}}, + Input: MustParams(ð.LogFilter{FromBlock: eth.MustBlockNumberOrTag("0x3456789"), ToBlock: eth.MustBlockNumberOrTag("0x3456")}, ð.LogFilter{FromBlock: eth.MustBlockNumberOrTag("0x5678"), ToBlock: eth.MustBlockNumberOrTag("0x1234")}), + Test: func(tc *testCase) ([]interface{}, error) { + var rec1 eth.LogFilter + var rec2 eth.LogFilter + err := tc.Input.UnmarshalInto(&rec1, &rec2) + return []interface{}{rec1, rec2}, err + }, + }, } for _, testCase := range testCases { @@ -134,3 +156,85 @@ func TestParams_DecodeInto(t *testing.T) { object := Object{} assert.Error(t, multiple.UnmarshalSingleParam(3, &object), "should have failed") } + +func TestParams_DecodeInto_Fail(t *testing.T) { + + type testCase struct { + Description string + Expected []interface{} + Input Params + Test func(tc *testCase) ([]interface{}, error) + } + + testCases := []testCase{ + { + Description: "params null", + Expected: nil, + Input: nil, + Test: func(tc *testCase) ([]interface{}, error) { + var str string + err := tc.Input.UnmarshalInto(str) + return nil, err + }, + }, + { + Description: "len(p) Date: Thu, 15 Sep 2022 19:40:39 +0200 Subject: [PATCH 3/6] feat(params): adding test cases --- jsonrpc/params.go | 11 ++- jsonrpc/params_test.go | 189 +++++++++++++++++++++++++++++------------ 2 files changed, 146 insertions(+), 54 deletions(-) diff --git a/jsonrpc/params.go b/jsonrpc/params.go index c6eaec5..8ec2c02 100644 --- a/jsonrpc/params.go +++ b/jsonrpc/params.go @@ -164,23 +164,33 @@ func ParsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([] } args = append(args, reflect.Zero(types[i])) } + fmt.Println(args) return args, nil } +type Object struct { + Foo string `json:"foo"` + Bar int `json:"bar"` +} + func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) { args := make([]reflect.Value, 0, len(types)) + fmt.Println(dec) for i := 0; dec.More(); i++ { if i >= len(types) { //no error when decoding a subset of param return args, nil } argval := reflect.New(types[i]) + //fmt.Println(reflect.Indirect(argval).Interface().(*Object)) if err := dec.Decode(argval.Interface()); err != nil { return args, fmt.Errorf("invalid argument %d: %v", i, err) } + fmt.Println(argval) if argval.IsNil() && types[i].Kind() != reflect.Ptr { return args, fmt.Errorf("missing value for required argument %d", i) } args = append(args, argval.Elem()) + fmt.Println(args) } // Read end of args array. _, err := dec.Token() @@ -193,6 +203,5 @@ func listTypes(a []interface{}) []reflect.Type { v := reflect.ValueOf(i).Type() arrayType = append(arrayType, v) } - return arrayType } diff --git a/jsonrpc/params_test.go b/jsonrpc/params_test.go index 2a7bf1f..87de4d0 100644 --- a/jsonrpc/params_test.go +++ b/jsonrpc/params_test.go @@ -1,7 +1,11 @@ package jsonrpc import ( + "encoding/json" + "fmt" "github.com/INFURA/go-ethlibs/eth" + "github.com/pkg/errors" + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -67,16 +71,16 @@ func TestParams_DecodeInto(t *testing.T) { } testCases := []testCase{ - //{ - // Description: "single string", - // Expected: []interface{}{"foo"}, - // Input: MustParams("foo"), - // Test: func(tc *testCase) ([]interface{}, error) { - // var str string - // err := tc.Input.UnmarshalInto(&str) - // return []interface{}{str}, err - // }, - //}, + { + Description: "single string", + Expected: []interface{}{"foo"}, + Input: MustParams("foo"), + Test: func(tc *testCase) ([]interface{}, error) { + var str string + err := tc.Input.UnmarshalInto(&str) + return []interface{}{str}, err + }, + }, { Description: "string and bool", Expected: []interface{}{"foo", true}, @@ -159,9 +163,13 @@ func TestParams_DecodeInto(t *testing.T) { func TestParams_DecodeInto_Fail(t *testing.T) { + type expected struct { + output []interface{} + err error + } type testCase struct { Description string - Expected []interface{} + Expected expected Input Params Test func(tc *testCase) ([]interface{}, error) } @@ -169,7 +177,7 @@ func TestParams_DecodeInto_Fail(t *testing.T) { testCases := []testCase{ { Description: "params null", - Expected: nil, + Expected: expected{nil, nil}, Input: nil, Test: func(tc *testCase) ([]interface{}, error) { var str string @@ -179,7 +187,7 @@ func TestParams_DecodeInto_Fail(t *testing.T) { }, { Description: "len(p) Date: Mon, 26 Sep 2022 16:35:28 +0200 Subject: [PATCH 4/6] feat(params): adding test cases --- jsonrpc/params.go | 17 +++-------- jsonrpc/params_test.go | 69 ++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/jsonrpc/params.go b/jsonrpc/params.go index 8ec2c02..840982b 100644 --- a/jsonrpc/params.go +++ b/jsonrpc/params.go @@ -105,7 +105,7 @@ func (p Params) UnmarshalInto(receivers ...interface{}) error { rawParams := json.RawMessage("[" + strings.Join(paramElement, ",") + "]") - _, err := ParsePositionalArguments(rawParams, receiversType) + _, err := parsePositionalArguments(rawParams, receiversType) if err != nil { return err } @@ -139,7 +139,7 @@ func (p Params) UnmarshalSingleParam(pos int, receiver interface{}) error { // parsePositionalArguments tries to parse the given args to an array of values with the // given types. It returns the parsed values or an error when the args could not be // parsed. Missing optional arguments are returned as reflect.Zero values. -func ParsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { +func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { dec := json.NewDecoder(bytes.NewReader(rawArgs)) var args []reflect.Value tok, err := dec.Token() @@ -164,33 +164,26 @@ func ParsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([] } args = append(args, reflect.Zero(types[i])) } - fmt.Println(args) return args, nil } -type Object struct { - Foo string `json:"foo"` - Bar int `json:"bar"` -} - func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) { args := make([]reflect.Value, 0, len(types)) - fmt.Println(dec) + for i := 0; dec.More(); i++ { if i >= len(types) { //no error when decoding a subset of param return args, nil } argval := reflect.New(types[i]) - //fmt.Println(reflect.Indirect(argval).Interface().(*Object)) + if err := dec.Decode(argval.Interface()); err != nil { return args, fmt.Errorf("invalid argument %d: %v", i, err) } - fmt.Println(argval) + if argval.IsNil() && types[i].Kind() != reflect.Ptr { return args, fmt.Errorf("missing value for required argument %d", i) } args = append(args, argval.Elem()) - fmt.Println(args) } // Read end of args array. _, err := dec.Token() diff --git a/jsonrpc/params_test.go b/jsonrpc/params_test.go index 87de4d0..aac27eb 100644 --- a/jsonrpc/params_test.go +++ b/jsonrpc/params_test.go @@ -1,6 +1,7 @@ package jsonrpc import ( + "bytes" "encoding/json" "fmt" "github.com/INFURA/go-ethlibs/eth" @@ -197,7 +198,7 @@ func TestParams_DecodeInto_Fail(t *testing.T) { }, }, { - Description: "err parse", + Description: "parse err", Expected: expected{[]interface{}{}, errors.New("invalid argument 0: data types must start with 0x")}, Input: MustParams("2345T678"), Test: func(tc *testCase) ([]interface{}, error) { @@ -242,7 +243,7 @@ func TestParams_parsePositionalArguments(t *testing.T) { types: []reflect.Type{}, }, { - Description: "1st case", + Description: "params nil", Expected: expected{nil, nil}, rawArgs: []byte(nil), types: []reflect.Type{}, @@ -260,30 +261,29 @@ func TestParams_parsePositionalArguments(t *testing.T) { types: []reflect.Type{}, }, { - Description: "missing args", + Description: "missing value for arg", Expected: expected{nil, fmt.Errorf("missing value for required argument 0")}, rawArgs: []byte(nil), types: []reflect.Type{reflect.TypeOf("foo"), reflect.TypeOf(true)}, }, - //{ - // Description: "working", - // Expected: expected{args: []reflect.Value{reflect.ValueOf("")}, err: nil}, - // rawArgs: []byte(`["foo"]`), - // types: []reflect.Type{reflect.TypeOf("foo")}, - //}, + { + Description: "works", + Expected: expected{args: []reflect.Value{reflect.ValueOf("foo")}, err: nil}, + rawArgs: []byte(`["foo"]`), + types: []reflect.Type{reflect.TypeOf("foo")}, + }, } for _, testCase := range testCases { - actual, err := ParsePositionalArguments(testCase.rawArgs, testCase.types) - assert.Equal(t, testCase.Expected.args, actual, "%#v", testCase) + actual, err := parsePositionalArguments(testCase.rawArgs, testCase.types) + assert.ObjectsAreEqualValues(testCase.Expected.args, actual) if err != nil { assert.Equal(t, testCase.Expected.err.Error(), err.Error(), "%#v", testCase) } else { assert.Nil(t, testCase.Expected.err, "%#v", testCase) } } - } func TestParams_parseArgumentArray(t *testing.T) { @@ -300,24 +300,41 @@ func TestParams_parseArgumentArray(t *testing.T) { } testCases := []testCase{ - //{ - // Description: "works", - // Expected: expected{[]reflect.Value{}, nil}, - // dec: &json.Decoder{}, - // types: []reflect.Type{reflect.TypeOf("foo")}, - //}, - //{ - // Description: "works", - // Expected: expected{[]reflect.Value{reflect.ValueOf("foo")}, nil}, - // dec: json.NewDecoder(bytes.NewReader([]byte(`"foo"`))), - // types: []reflect.Type{reflect.TypeOf("foo")}, - //}, + { + Description: "decode subset of param", + Expected: expected{[]reflect.Value{reflect.ValueOf("foo")}, nil}, + dec: json.NewDecoder(bytes.NewReader([]byte(`["foo", 123]`))), + types: []reflect.Type{reflect.TypeOf("foo")}, + }, + { + Description: "works", + Expected: expected{[]reflect.Value{reflect.ValueOf("foo")}, nil}, + dec: json.NewDecoder(bytes.NewReader([]byte(`["foo"]`))), + types: []reflect.Type{reflect.TypeOf("foo")}, + }, + { + Description: "invalid argument", + Expected: expected{[]reflect.Value{reflect.ValueOf("foo")}, fmt.Errorf("invalid argument 0: invalid character 'o' in literal false (expecting 'a')")}, + dec: json.NewDecoder(bytes.NewReader([]byte(`[foo]`))), + types: []reflect.Type{reflect.TypeOf("foo")}, + }, + { + Description: "EOF", + Expected: expected{[]reflect.Value{reflect.ValueOf(nil)}, fmt.Errorf("EOF")}, + dec: json.NewDecoder(bytes.NewReader([]byte(``))), + types: []reflect.Type{reflect.TypeOf("foo")}, + }, } for _, testCase := range testCases { + _, _ = testCase.dec.Token() actual, err := parseArgumentArray(testCase.dec, testCase.types) - assert.Equal(t, testCase.Expected.args, actual, "%#v", testCase) - assert.Equal(t, testCase.Expected.err, err, "%#v", testCase) + assert.ObjectsAreEqualValues(testCase.Expected.args, actual) + if err != nil { + assert.Equal(t, testCase.Expected.err, err, "%#v", testCase) + } else { + assert.Nil(t, testCase.Expected.err, "%#v", testCase) + } } } From 8b1b4660caf8826e0c528a91a1d7181db24eb273 Mon Sep 17 00:00:00 2001 From: Caroline G Date: Tue, 27 Sep 2022 16:08:41 +0200 Subject: [PATCH 5/6] feat(params): adding comments and cleaning the code --- jsonrpc/params.go | 6 +++++- jsonrpc/params_test.go | 29 ++++++++++++----------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/jsonrpc/params.go b/jsonrpc/params.go index 840982b..2579b26 100644 --- a/jsonrpc/params.go +++ b/jsonrpc/params.go @@ -78,7 +78,8 @@ func MakeParams(params ...interface{}) (Params, error) { // UnmarshalInto will decode Params into the passed in values, which // must be pointer receivers. The type of the passed in value is used to Unmarshal the data. // UnmarshalInto will fail if the parameters cannot be converted to the passed-in types. -// +// Check each type of each param, return an error if it's not the right one and which argument. + // Example: // // var blockNum string @@ -87,6 +88,7 @@ func MakeParams(params ...interface{}) (Params, error) { // // IMPORTANT: While Go will compile with non-pointer receivers, the Unmarshal attempt will // *always* fail with an error. + func (p Params) UnmarshalInto(receivers ...interface{}) error { if p == nil { return nil @@ -96,6 +98,7 @@ func (p Params) UnmarshalInto(receivers ...interface{}) error { return errors.New("not enough params to decode") } + // Return an array of the receivers' types receiversType := listTypes(receivers) var paramElement []string @@ -103,6 +106,7 @@ func (p Params) UnmarshalInto(receivers ...interface{}) error { paramElement = append(paramElement, string(i)) } + // Return p Params in json.Rawmessage type with [] to be parsed rawParams := json.RawMessage("[" + strings.Join(paramElement, ",") + "]") _, err := parsePositionalArguments(rawParams, receiversType) diff --git a/jsonrpc/params_test.go b/jsonrpc/params_test.go index aac27eb..58b95da 100644 --- a/jsonrpc/params_test.go +++ b/jsonrpc/params_test.go @@ -178,7 +178,7 @@ func TestParams_DecodeInto_Fail(t *testing.T) { testCases := []testCase{ { Description: "params null", - Expected: expected{nil, nil}, + Expected: expected{output: nil, err: nil}, Input: nil, Test: func(tc *testCase) ([]interface{}, error) { var str string @@ -188,7 +188,7 @@ func TestParams_DecodeInto_Fail(t *testing.T) { }, { Description: "len(p) Date: Mon, 3 Oct 2022 12:46:49 +0200 Subject: [PATCH 6/6] feat(params): updated with the comments --- jsonrpc/params.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/jsonrpc/params.go b/jsonrpc/params.go index 2579b26..d3c8f9a 100644 --- a/jsonrpc/params.go +++ b/jsonrpc/params.go @@ -98,27 +98,30 @@ func (p Params) UnmarshalInto(receivers ...interface{}) error { return errors.New("not enough params to decode") } - // Return an array of the receivers' types - receiversType := listTypes(receivers) + // Return an array of the receivers' types and check if the receiver is a ptr + receiversType, err := listTypes(receivers) + if err != nil { + return err + } var paramElement []string for _, i := range p { paramElement = append(paramElement, string(i)) } - // Return p Params in json.Rawmessage type with [] to be parsed + // Return p Params in json.RawMessage type with [] to be parsed rawParams := json.RawMessage("[" + strings.Join(paramElement, ",") + "]") - _, err := parsePositionalArguments(rawParams, receiversType) + receiversValues, err := parsePositionalArguments(rawParams, receiversType) if err != nil { return err } for i, r := range receivers { - err := json.Unmarshal(p[i], r) - if err != nil { - return err + if receiversValues[i].IsZero() { + continue } + reflect.ValueOf(r).Elem().Set(receiversValues[i].Elem()) } return nil @@ -194,11 +197,14 @@ func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Valu return args, err } -func listTypes(a []interface{}) []reflect.Type { +func listTypes(a []interface{}) ([]reflect.Type, error) { var arrayType []reflect.Type for _, i := range a { v := reflect.ValueOf(i).Type() + if v.Kind() != reflect.Ptr { + return nil, fmt.Errorf("the receiver %d is not a pointer", i) + } arrayType = append(arrayType, v) } - return arrayType + return arrayType, nil }