github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/amino/json_test.go (about) 1 package amino_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strconv" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 amino "github.com/gnolang/gno/tm2/pkg/amino" 17 "github.com/gnolang/gno/tm2/pkg/amino/pkg" 18 ) 19 20 type Dummy struct{} 21 22 var gopkg = reflect.TypeOf(Dummy{}).PkgPath() 23 24 var transportPackage = pkg.NewPackage(gopkg, "amino_test", ""). 25 WithTypes(&Transport{}, Car(""), insurancePlan(0), Boat(""), Plane{}) 26 27 func registerTransports(cdc *amino.Codec) { 28 cdc.RegisterPackage(transportPackage) 29 } 30 31 func TestMarshalJSON(t *testing.T) { 32 t.Parallel() 33 34 cdc := amino.NewCodec() 35 registerTransports(cdc) 36 cases := []struct { 37 in interface{} 38 want string 39 wantErr string 40 }{ 41 {&noFields{}, "{}", ""}, // #0 42 {&noExportedFields{a: 10, b: "foo"}, "{}", ""}, // #1 43 {nil, "null", ""}, // #2 44 {&oneExportedField{}, `{"A":""}`, ""}, // #3 45 {Car(""), `""`, ""}, // #4 46 {Car("Tesla"), `"Tesla"`, ""}, // #5 47 {&oneExportedField{A: "Z"}, `{"A":"Z"}`, ""}, // #6 48 {[]string{"a", "bc"}, `["a","bc"]`, ""}, // #7 49 { 50 []interface{}{"a", "bc", 10, 10.93, 1e3}, 51 ``, "unregistered", 52 }, // #8 53 { 54 aPointerField{Foo: new(int), Name: "name"}, 55 `{"Foo":"0","nm":"name"}`, "", 56 }, // #9 57 { 58 aPointerFieldAndEmbeddedField{intPtr(11), "ap", nil, &oneExportedField{A: "foo"}}, 59 `{"Foo":"11","nm":"ap","bz":{"A":"foo"}}`, "", 60 }, // #10 61 { 62 doublyEmbedded{ 63 Inner: &aPointerFieldAndEmbeddedField{ 64 intPtr(11), "ap", nil, &oneExportedField{A: "foo"}, 65 }, 66 }, 67 `{"Inner":{"Foo":"11","nm":"ap","bz":{"A":"foo"}},"year":0}`, "", 68 }, // #11 69 { 70 struct{}{}, `{}`, "", 71 }, // #12 72 { 73 struct{ A int }{A: 10}, `{"A":"10"}`, "", 74 }, // #13 75 { 76 Transport{}, 77 `{"Vehicle":null,"Capacity":"0"}`, "", 78 }, // #14 79 { 80 Transport{Vehicle: Car("Bugatti")}, 81 `{"Vehicle":{"@type":"/amino_test.Car","value":"Bugatti"},"Capacity":"0"}`, "", 82 }, // #15 83 { 84 BalanceSheet{Assets: []Asset{Car("Corolla"), insurancePlan(1e7)}}, 85 `{"assets":[{"@type":"/amino_test.Car","value":"Corolla"},{"@type":"/amino_test.insurancePlan","value":"10000000"}]}`, "", 86 }, // #16 87 { 88 Transport{Vehicle: Boat("Poseidon"), Capacity: 1789}, 89 `{"Vehicle":{"@type":"/amino_test.Boat","value":"Poseidon"},"Capacity":"1789"}`, "", 90 }, // #17 91 { 92 withCustomMarshaler{A: &aPointerField{Foo: intPtr(12)}, F: customJSONMarshaler(10)}, 93 `{"fx":"10","A":{"Foo":"12"}}`, "", 94 }, // #18 (NOTE: MarshalJSON of customJSONMarshaler has no effect) 95 { 96 func() json.Marshaler { v := customJSONMarshaler(10); return &v }(), 97 `"10"`, "", 98 }, // #19 (NOTE: MarshalJSON of customJSONMarshaler has no effect) 99 { 100 interfacePtr("a"), `{"@type":"/google.protobuf.StringValue","value":"a"}`, "", 101 }, // #20 102 {&fp{"Foo", 10}, `"Foo@10"`, ""}, // #21 103 {(*fp)(nil), "null", ""}, // #22 104 { 105 struct { 106 FP *fp 107 Package string 108 }{FP: &fp{"Foo", 10}, Package: "bytes"}, 109 `{"FP":"Foo@10","Package":"bytes"}`, "", 110 }, // #23 111 } 112 113 for i, tt := range cases { 114 t.Logf("Trying case #%v", i) 115 blob, err := cdc.MarshalJSON(tt.in) 116 if tt.wantErr != "" { 117 if err == nil || !strings.Contains(err.Error(), tt.wantErr) { 118 t.Errorf("#%d:\ngot:\n\t%v\nwant non-nil error containing\n\t%q", i, 119 err, tt.wantErr) 120 } 121 continue 122 } 123 124 if err != nil { 125 t.Errorf("#%d: unexpected error: %v\nblob: %v", i, err, tt.in) 126 continue 127 } 128 if g, w := string(blob), tt.want; g != w { 129 t.Errorf("#%d:\ngot:\n\t%s\nwant:\n\t%s", i, g, w) 130 } 131 } 132 } 133 134 func TestMarshalJSONTime(t *testing.T) { 135 t.Parallel() 136 137 cdc := amino.NewCodec() 138 registerTransports(cdc) 139 140 type SimpleStruct struct { 141 String string 142 Bytes []byte 143 Time time.Time 144 } 145 146 s := SimpleStruct{ 147 String: "hello", 148 Bytes: []byte("goodbye"), 149 Time: time.Now().Round(0).UTC(), // strip monotonic. 150 } 151 152 b, err := cdc.MarshalJSON(s) 153 assert.Nil(t, err) 154 155 var s2 SimpleStruct 156 err = cdc.UnmarshalJSON(b, &s2) 157 assert.Nil(t, err) 158 assert.Equal(t, s, s2) 159 } 160 161 type fp struct { 162 Name string 163 Version int 164 } 165 166 func (f fp) MarshalAmino() (string, error) { 167 return fmt.Sprintf("%v@%v", f.Name, f.Version), nil 168 } 169 170 func (f *fp) UnmarshalAmino(repr string) (err error) { 171 parts := strings.Split(repr, "@") 172 if len(parts) != 2 { 173 return fmt.Errorf("invalid format %v", repr) 174 } 175 f.Name = parts[0] 176 f.Version, err = strconv.Atoi(parts[1]) 177 return 178 } 179 180 type innerFP struct { 181 PC uint64 182 FP *fp 183 } 184 185 // We don't support maps. 186 func TestUnmarshalMap(t *testing.T) { 187 t.Parallel() 188 189 jsonBytes := []byte("dontcare") 190 obj := new(map[string]int) 191 cdc := amino.NewCodec() 192 assert.Panics(t, func() { 193 err := cdc.UnmarshalJSON(jsonBytes, &obj) 194 assert.Fail(t, "should have panicked but got err: %v", err) 195 }) 196 assert.Panics(t, func() { 197 err := cdc.UnmarshalJSON(jsonBytes, obj) 198 assert.Fail(t, "should have panicked but got err: %v", err) 199 }) 200 assert.Panics(t, func() { 201 bz, err := cdc.MarshalJSON(obj) 202 assert.Fail(t, "should have panicked but got bz: %X err: %v", bz, err) 203 }) 204 } 205 206 func TestUnmarshalFunc(t *testing.T) { 207 t.Parallel() 208 209 jsonBytes := []byte(`"dontcare"`) 210 obj := func() {} 211 cdc := amino.NewCodec() 212 assert.Panics(t, func() { 213 err := cdc.UnmarshalJSON(jsonBytes, &obj) 214 assert.Fail(t, "should have panicked but got err: %v", err) 215 }) 216 217 err := cdc.UnmarshalJSON(jsonBytes, obj) 218 // UnmarshalJSON expects a pointer 219 assert.Error(t, err) 220 221 // ... nor encoding it. 222 assert.Panics(t, func() { 223 bz, err := cdc.MarshalJSON(obj) 224 assert.Fail(t, "should have panicked but got bz: %X err: %v", bz, err) 225 }) 226 } 227 228 func TestUnmarshalJSON(t *testing.T) { 229 t.Parallel() 230 231 cdc := amino.NewCodec() 232 registerTransports(cdc) 233 cases := []struct { 234 blob string 235 in interface{} 236 want interface{} 237 wantErr string 238 }{ 239 { // #0 240 `null`, 2, nil, "expected a pointer", 241 }, 242 { // #1 243 `null`, new(int), new(int), "", 244 }, 245 { // #2 246 `"2"`, new(int), intPtr(2), "", 247 }, 248 { // #3 249 `{"null"}`, new(int), nil, "invalid character", 250 }, 251 { // #4 252 `{"Vehicle":null,"Capacity":"0"}`, new(Transport), new(Transport), "", 253 }, 254 { // #5 255 `{"Vehicle":{"@type":"/amino_test.Car","value":"Bugatti"},"Capacity":"10"}`, 256 new(Transport), 257 &Transport{ 258 Vehicle: Car("Bugatti"), 259 Capacity: 10, 260 }, "", 261 }, 262 { // #6 263 `"Bugatti"`, new(Car), func() *Car { c := Car("Bugatti"); return &c }(), "", 264 }, 265 { // #7 266 `["1", "2", "3"]`, new([]int), func() interface{} { 267 v := []int{1, 2, 3} 268 return &v 269 }(), "", 270 }, 271 { // #8 272 `["1", "2", "3"]`, new([]string), func() interface{} { 273 v := []string{"1", "2", "3"} 274 return &v 275 }(), "", 276 }, 277 { // #9 278 `[{"@type":"/google.protobuf.Int32Value","value":1},{"@type":"/google.protobuf.StringValue","value":"2"}]`, 279 new([]interface{}), &([]interface{}{int32(1), string("2")}), "", 280 }, 281 { // #10 282 `2.34`, floatPtr(2.34), nil, "float* support requires", 283 }, 284 { // #11 285 `"FooBar@1"`, new(fp), &fp{"FooBar", 1}, "", 286 }, 287 { // #12 288 `"10@0"`, new(fp), &fp{Name: "10"}, "", 289 }, 290 { // #13 291 `{"PC":"125","FP":"10@0"}`, new(innerFP), &innerFP{PC: 125, FP: &fp{Name: `10`}}, "", 292 }, 293 { // #14 294 `{"PC":"125","FP":"<FP-FOO>@0"}`, new(innerFP), &innerFP{PC: 125, FP: &fp{Name: `<FP-FOO>`}}, "", 295 }, 296 } 297 298 for i, tt := range cases { 299 err := cdc.UnmarshalJSON([]byte(tt.blob), tt.in) 300 if tt.wantErr != "" { 301 if err == nil || !strings.Contains(err.Error(), tt.wantErr) { 302 t.Errorf("#%d:\ngot:\n\t%q\nwant non-nil error containing\n\t%q", i, 303 err, tt.wantErr) 304 } 305 continue 306 } 307 308 if err != nil { 309 t.Errorf("#%d: unexpected error: %v\nblob: %s\nin: %+v\n", i, err, tt.blob, tt.in) 310 continue 311 } 312 if g, w := tt.in, tt.want; !reflect.DeepEqual(g, w) { 313 gb, err := json.MarshalIndent(g, "", " ") 314 require.NoError(t, err) 315 wb, err := json.MarshalIndent(w, "", " ") 316 require.NoError(t, err) 317 t.Errorf("#%d:\ngot:\n\t%#v\n(%s)\n\nwant:\n\t%#v\n(%s)", i, g, gb, w, wb) 318 } 319 } 320 } 321 322 func TestJSONCodecRoundTrip(t *testing.T) { 323 t.Parallel() 324 325 cdc := amino.NewCodec() 326 registerTransports(cdc) 327 type allInclusive struct { 328 Tr Transport `json:"trx"` 329 Vehicle Vehicle `json:"v,omitempty"` 330 Comment string 331 Data []byte 332 } 333 334 cases := []struct { 335 in interface{} 336 want interface{} 337 out interface{} 338 wantErr string 339 }{ 340 0: { 341 in: &allInclusive{ 342 Tr: Transport{ 343 Vehicle: Boat("Oracle"), 344 }, 345 Comment: "To the Cosmos! баллинг в космос", 346 Data: []byte("祝你好运"), 347 }, 348 out: new(allInclusive), 349 want: &allInclusive{ 350 Tr: Transport{ 351 Vehicle: Boat("Oracle"), 352 }, 353 Comment: "To the Cosmos! баллинг в космос", 354 Data: []byte("祝你好运"), 355 }, 356 }, 357 358 1: { 359 in: Transport{Vehicle: Plane{Name: "G6", MaxAltitude: 51e3}, Capacity: 18}, 360 out: new(Transport), 361 want: &Transport{Vehicle: Plane{Name: "G6", MaxAltitude: 51e3}, Capacity: 18}, 362 }, 363 } 364 365 for i, tt := range cases { 366 mBlob, err := cdc.MarshalJSON(tt.in) 367 if tt.wantErr != "" { 368 if err == nil || !strings.Contains(err.Error(), tt.wantErr) { 369 t.Errorf("#%d:\ngot:\n\t%q\nwant non-nil error containing\n\t%q", i, 370 err, tt.wantErr) 371 } 372 continue 373 } 374 375 if err != nil { 376 t.Errorf("#%d: unexpected error after MarshalJSON: %v", i, err) 377 continue 378 } 379 380 if err = cdc.UnmarshalJSON(mBlob, tt.out); err != nil { 381 t.Errorf("#%d: unexpected error after UnmarshalJSON: %v\nmBlob: %s", i, err, mBlob) 382 continue 383 } 384 385 // Now check that the input is exactly equal to the output 386 uBlob, err := cdc.MarshalJSON(tt.out) 387 assert.NoError(t, err) 388 if err := cdc.UnmarshalJSON(mBlob, tt.out); err != nil { 389 t.Errorf("#%d: unexpected error after second MarshalJSON: %v", i, err) 390 continue 391 } 392 if !reflect.DeepEqual(tt.want, tt.out) { 393 t.Errorf("#%d: After roundtrip UnmarshalJSON\ngot: \t%v\nwant:\t%v", i, tt.out, tt.want) 394 } 395 if !bytes.Equal(mBlob, uBlob) { 396 t.Errorf("#%d: After roundtrip MarshalJSON\ngot: \t%s\nwant:\t%s", i, uBlob, mBlob) 397 } 398 } 399 } 400 401 func intPtr(i int) *int { 402 return &i 403 } 404 405 func floatPtr(f float64) *float64 { 406 return &f 407 } 408 409 type ( 410 noFields struct{} 411 noExportedFields struct { 412 a int 413 b string 414 } 415 ) 416 417 type oneExportedField struct { 418 A string 419 } 420 421 type aPointerField struct { 422 Foo *int 423 Name string `json:"nm,omitempty"` 424 } 425 426 type doublyEmbedded struct { 427 Inner *aPointerFieldAndEmbeddedField 428 Year int32 `json:"year"` 429 } 430 431 type aPointerFieldAndEmbeddedField struct { 432 Foo *int 433 Name string `json:"nm,omitempty"` 434 *oneExportedField 435 B *oneExportedField `json:"bz,omitempty"` 436 } 437 438 type customJSONMarshaler int 439 440 var _ json.Marshaler = (*customJSONMarshaler)(nil) 441 442 func (cm customJSONMarshaler) MarshalJSON() ([]byte, error) { 443 return []byte(`"WRONG"`), nil 444 } 445 446 type withCustomMarshaler struct { 447 F customJSONMarshaler `json:"fx"` 448 A *aPointerField 449 } 450 451 type Transport struct { 452 Vehicle 453 Capacity int 454 } 455 456 type Vehicle interface { 457 Move() error 458 } 459 460 type Asset interface { 461 Value() float64 462 } 463 464 func (c Car) Value() float64 { 465 return 60000.0 466 } 467 468 type BalanceSheet struct { 469 Assets []Asset `json:"assets"` 470 } 471 472 type ( 473 Car string 474 Boat string 475 Plane struct { 476 Name string 477 MaxAltitude int64 478 } 479 ) 480 type insurancePlan int 481 482 func (ip insurancePlan) Value() float64 { return float64(ip) } 483 484 func (c Car) Move() error { return nil } 485 func (b Boat) Move() error { return nil } 486 func (p Plane) Move() error { return nil } 487 488 func interfacePtr(v interface{}) *interface{} { 489 return &v 490 } 491 492 // Test to ensure that Amino codec's time encoding/decoding roundtrip 493 // produces the same result as the standard library json's. 494 func TestAminoJSONTimeEncodeDecodeRoundTrip(t *testing.T) { 495 t.Parallel() 496 497 loc, err := time.LoadLocation("America/Los_Angeles") 498 require.NoError(t, err) 499 din := time.Date(2008, 9, 15, 14, 13, 12, 11109876, loc).Round(time.Millisecond).UTC() 500 501 cdc := amino.NewCodec() 502 blobAmino, err := cdc.MarshalJSON(din) 503 require.Nil(t, err, "amino.Codec.MarshalJSON should succeed") 504 var tAminoOut time.Time 505 require.Nil(t, cdc.UnmarshalJSON(blobAmino, &tAminoOut), "amino.Codec.UnmarshalJSON should succeed") 506 require.NotEqual(t, tAminoOut, time.Time{}, "amino.marshaled definitely isn't equal to zero time") 507 require.Equal(t, tAminoOut, din, "expecting marshaled in to be equal to marshaled out") 508 509 blobStdlib, err := json.Marshal(din) 510 require.Nil(t, err, "json.Marshal should succeed") 511 var tStdlibOut time.Time 512 require.Nil(t, json.Unmarshal(blobStdlib, &tStdlibOut), "json.Unmarshal should succeed") 513 require.NotEqual(t, tStdlibOut, time.Time{}, "stdlib.marshaled definitely isn't equal to zero time") 514 require.Equal(t, tStdlibOut, din, "expecting stdlib.marshaled to be equal to time in") 515 516 require.Equal(t, tAminoOut, tStdlibOut, "expecting amino.unmarshalled to be equal to json.unmarshalled") 517 } 518 519 func TestMarshalJSONIndent(t *testing.T) { 520 t.Parallel() 521 522 cdc := amino.NewCodec() 523 registerTransports(cdc) 524 obj := Transport{Vehicle: Car("Tesla")} 525 expected := fmt.Sprintf(`{ 526 "Vehicle": { 527 "@type": "/amino_test.Car", 528 "value": "Tesla" 529 }, 530 "Capacity": "0" 531 }`) 532 533 blob, err := cdc.MarshalJSONIndent(obj, "", " ") 534 assert.Nil(t, err) 535 assert.Equal(t, expected, string(blob)) 536 }