github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/lib/server/parse_test.go (about) 1 package rpcserver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 12 types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" 13 ) 14 15 func TestParseJSONMap(t *testing.T) { 16 t.Parallel() 17 18 input := []byte(`{"value":"1234","height":22}`) 19 20 // naive is float,string 21 var p1 map[string]interface{} 22 err := json.Unmarshal(input, &p1) 23 if assert.Nil(t, err) { 24 h, ok := p1["height"].(float64) 25 if assert.True(t, ok, "%#v", p1["height"]) { 26 assert.EqualValues(t, 22, h) 27 } 28 v, ok := p1["value"].(string) 29 if assert.True(t, ok, "%#v", p1["value"]) { 30 assert.EqualValues(t, "1234", v) 31 } 32 } 33 34 // preloading map with values doesn't help 35 tmp := 0 36 p2 := map[string]interface{}{ 37 "value": &[]byte{}, 38 "height": &tmp, 39 } 40 err = json.Unmarshal(input, &p2) 41 if assert.Nil(t, err) { 42 h, ok := p2["height"].(float64) 43 if assert.True(t, ok, "%#v", p2["height"]) { 44 assert.EqualValues(t, 22, h) 45 } 46 v, ok := p2["value"].(string) 47 if assert.True(t, ok, "%#v", p2["value"]) { 48 assert.EqualValues(t, "1234", v) 49 } 50 } 51 52 // preload here with *pointers* to the desired types 53 // struct has unknown types, but hard-coded keys 54 tmp = 0 55 p3 := struct { 56 Value interface{} `json:"value"` 57 Height interface{} `json:"height"` 58 }{ 59 Height: &tmp, 60 Value: &[]byte{}, 61 } 62 err = json.Unmarshal(input, &p3) 63 if assert.Nil(t, err) { 64 h, ok := p3.Height.(*int) 65 if assert.True(t, ok, "%#v", p3.Height) { 66 assert.Equal(t, 22, *h) 67 } 68 v, ok := p3.Value.(*[]byte) 69 if assert.True(t, ok, "%#v", p3.Value) { 70 // "1234" is interpreted as base64, decodes to the following bytes. 71 assert.EqualValues(t, []byte{0xd7, 0x6d, 0xf8}, *v) 72 } 73 } 74 75 // simplest solution, but hard-coded 76 p4 := struct { 77 Value []byte `json:"value"` 78 Height int `json:"height"` 79 }{} 80 err = json.Unmarshal(input, &p4) 81 if assert.Nil(t, err) { 82 assert.EqualValues(t, 22, p4.Height) 83 assert.EqualValues(t, []byte{0xd7, 0x6d, 0xf8}, p4.Value) 84 } 85 86 // so, let's use this trick... 87 // dynamic keys on map, and we can deserialize to the desired types 88 var p5 map[string]*json.RawMessage 89 err = json.Unmarshal(input, &p5) 90 if assert.Nil(t, err) { 91 var h int 92 err = json.Unmarshal(*p5["height"], &h) 93 if assert.Nil(t, err) { 94 assert.Equal(t, 22, h) 95 } 96 97 var v []byte 98 err = json.Unmarshal(*p5["value"], &v) 99 if assert.Nil(t, err) { 100 assert.Equal(t, []byte{0xd7, 0x6d, 0xf8}, v) 101 } 102 } 103 } 104 105 func TestParseJSONArray(t *testing.T) { 106 t.Parallel() 107 108 input := []byte(`["1234",22]`) 109 110 // naive is float,string 111 var p1 []interface{} 112 err := json.Unmarshal(input, &p1) 113 if assert.Nil(t, err) { 114 v, ok := p1[0].(string) 115 if assert.True(t, ok, "%#v", p1[0]) { 116 assert.EqualValues(t, "1234", v) 117 } 118 h, ok := p1[1].(float64) 119 if assert.True(t, ok, "%#v", p1[1]) { 120 assert.EqualValues(t, 22, h) 121 } 122 } 123 124 // preloading map with values helps here (unlike map - p2 above) 125 tmp := 0 126 p2 := []interface{}{&[]byte{}, &tmp} 127 err = json.Unmarshal(input, &p2) 128 if assert.Nil(t, err) { 129 v, ok := p2[0].(*[]byte) 130 if assert.True(t, ok, "%#v", p2[0]) { 131 assert.EqualValues(t, []byte{0xd7, 0x6d, 0xf8}, *v) 132 } 133 h, ok := p2[1].(*int) 134 if assert.True(t, ok, "%#v", p2[1]) { 135 assert.EqualValues(t, 22, *h) 136 } 137 } 138 } 139 140 func TestParseJSONRPC(t *testing.T) { 141 t.Parallel() 142 143 demo := func(ctx *types.Context, height int, name string) {} 144 call := NewRPCFunc(demo, "height,name") 145 146 cases := []struct { 147 raw string 148 height int64 149 name string 150 fail bool 151 }{ 152 // should parse 153 {`["7", "flew"]`, 7, "flew", false}, 154 {`{"name": "john", "height": "22"}`, 22, "john", false}, 155 // defaults 156 {`{"name": "solo", "unused": "stuff"}`, 0, "solo", false}, 157 // should fail - wrong types/length 158 {`["flew", 7]`, 0, "", true}, 159 {`[7,"flew",100]`, 0, "", true}, 160 {`{"name": -12, "height": "fred"}`, 0, "", true}, 161 } 162 for idx, tc := range cases { 163 i := strconv.Itoa(idx) 164 data := []byte(tc.raw) 165 vals, err := jsonParamsToArgs(call, data) 166 if tc.fail { 167 assert.NotNil(t, err, i) 168 } else { 169 assert.Nil(t, err, "%s: %+v", i, err) 170 if assert.Equal(t, 2, len(vals), i) { 171 assert.Equal(t, tc.height, vals[0].Int(), i) 172 assert.Equal(t, tc.name, vals[1].String(), i) 173 } 174 } 175 } 176 } 177 178 func TestParseURI(t *testing.T) { 179 t.Parallel() 180 181 demo := func(ctx *types.Context, height int, name string) {} 182 call := NewRPCFunc(demo, "height,name") 183 184 cases := []struct { 185 raw []string 186 height int64 187 name string 188 fail bool 189 }{ 190 // can parse numbers unquoted and strings quoted 191 {[]string{"7", `"flew"`}, 7, "flew", false}, 192 {[]string{"22", `"john"`}, 22, "john", false}, 193 {[]string{"-10", `"bob"`}, -10, "bob", false}, 194 // can parse numbers quoted, too 195 {[]string{`"7"`, `"flew"`}, 7, "flew", false}, 196 {[]string{`"-10"`, `"bob"`}, -10, "bob", false}, 197 // cant parse strings uquoted 198 {[]string{`"-10"`, `bob`}, -10, "bob", true}, 199 } 200 for idx, tc := range cases { 201 i := strconv.Itoa(idx) 202 // data := []byte(tc.raw) 203 url := fmt.Sprintf( 204 "test.com/method?height=%v&name=%v", 205 tc.raw[0], tc.raw[1]) 206 req, err := http.NewRequest("GET", url, nil) 207 assert.NoError(t, err) 208 vals, err := httpParamsToArgs(call, req) 209 if tc.fail { 210 assert.NotNil(t, err, i) 211 } else { 212 assert.Nil(t, err, "%s: %+v", i, err) 213 if assert.Equal(t, 2, len(vals), i) { 214 assert.Equal(t, tc.height, vals[0].Int(), i) 215 assert.Equal(t, tc.name, vals[1].String(), i) 216 } 217 } 218 } 219 }