feat(textual): `Coin` and `Coins` value renderers (#12729)

* wip coins

* Make coin test pass

* Remove useless file

* wip

* Fix tests

* Small tweaks

* reviews

* Add comment

* Add back go mod

* Add more coins test

* Update coins test

* Add more coins tests

* Reference todo issue

* Add metadata querier test

* add more tests

* Fix test build

* Improve comments

* Update tx/textual/internal/testdata/coin.json

Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>

* json formatting

* add more test cases

* go mod tidy

* address review

Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>
This commit is contained in:
Amaury 2022-09-14 12:32:52 +02:00 committed by GitHub
parent 9dacaa4a19
commit fd028db79a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1480 additions and 1115 deletions

View File

@ -13,9 +13,15 @@ require (
require (
github.com/cosmos/gogoproto v1.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
golang.org/x/net v0.0.0-20220726230323-06994584191e // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b // indirect
google.golang.org/grpc v1.49.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
@ -34,8 +36,19 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/net v0.0.0-20220726230323-06994584191e h1:wOQNKh1uuDGRnmgF0jDxh7ctgGy/3P4rYWQRVJD4/Yg=
golang.org/x/net v0.0.0-20220726230323-06994584191e/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b h1:SfSkJugek6xm7lWywqth4r2iTrYLpD8lOj1nMIIhMNM=
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

357
tx/textual/internal/testdata/coin.json vendored Normal file
View File

@ -0,0 +1,357 @@
[
{
"proto": {"amount": "0", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0 COSM"
},
{
"proto": {"amount": "1", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.000001 COSM"
},
{
"proto": {"amount": "10", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.00001 COSM"
},
{
"proto": {"amount": "100", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.0001 COSM"
},
{
"proto": {"amount": "1000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.001 COSM"
},
{
"proto": {"amount": "10000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.01 COSM"
},
{
"proto": {"amount": "100000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.1 COSM"
},
{
"proto": {"amount": "1000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1 COSM"
},
{
"proto": {"amount": "10000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10 COSM"
},
{
"proto": {"amount": "0", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0 COSM"
},
{
"proto": {"amount": "1", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0.000001 COSM"
},
{
"proto": {"amount": "10", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0.00001 COSM"
},
{
"proto": {"amount": "100", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0.0001 COSM"
},
{
"proto": {"amount": "1000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0.001 COSM"
},
{
"proto": {"amount": "10000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0.01 COSM"
},
{
"proto": {"amount": "100000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "0.1 COSM"
},
{
"proto": {"amount": "1000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "1 COSM"
},
{
"proto": {"amount": "10000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
"text": "10 COSM"
},
{
"proto": {"amount": "0", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0 ucosm"
},
{
"proto": {"amount": "0.000001", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1 ucosm"
},
{
"proto": {"amount": "0.00001", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10 ucosm"
},
{
"proto": {"amount": "0.0001", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "100 ucosm"
},
{
"proto": {"amount": "0.001", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1'000 ucosm"
},
{
"proto": {"amount": "0.01", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10'000 ucosm"
},
{
"proto": {"amount": "0.1", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "100'000 ucosm"
},
{
"proto": {"amount": "1", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1'000'000 ucosm"
},
{
"proto": {"amount": "10", "denom": "COSM"},
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10'000'000 ucosm"
},
{
"proto": {"amount": "0", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "0 ucosm"
},
{
"proto": {"amount": "1", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1 ucosm"
},
{
"proto": {"amount": "10", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10 ucosm"
},
{
"proto": {"amount": "100", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "100 ucosm"
},
{
"proto": {"amount": "1000", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1'000 ucosm"
},
{
"proto": {"amount": "10000", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10'000 ucosm"
},
{
"proto": {"amount": "100000", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "100'000 ucosm"
},
{
"proto": {"amount": "1000000", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "1'000'000 ucosm"
},
{
"proto": {"amount": "10000000", "denom": "ucosm"},
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"text": "10'000'000 ucosm"
},
{
"proto": {"amount": "0", "denom": "ucosm"},
"text": "0 ucosm"
},
{
"proto": {"amount": "1", "denom": "ucosm"},
"text": "1 ucosm"
},
{
"proto": {"amount": "10", "denom": "ucosm"},
"text": "10 ucosm"
},
{
"proto": {"amount": "100", "denom": "ucosm"},
"text": "100 ucosm"
},
{
"proto": {"amount": "1000", "denom": "ucosm"},
"text": "1'000 ucosm"
},
{
"proto": {"amount": "10000", "denom": "ucosm"},
"text": "10'000 ucosm"
},
{
"proto": {"amount": "100000", "denom": "ucosm"},
"text": "100'000 ucosm"
},
{
"proto": {"amount": "1000000", "denom": "ucosm"},
"text": "1'000'000 ucosm"
},
{
"proto": {"amount": "10000000", "denom": "ucosm"},
"text": "10'000'000 ucosm"
},
{
"proto": {"amount": "0", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "0 ucosm"
},
{
"proto": {"amount": "1", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "1 ucosm"
},
{
"proto": {"amount": "10", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "10 ucosm"
},
{
"proto": {"amount": "100", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "100 ucosm"
},
{
"proto": {"amount": "1000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "1'000 ucosm"
},
{
"proto": {"amount": "10000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "10'000 ucosm"
},
{
"proto": {"amount": "100000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "100'000 ucosm"
},
{
"proto": {"amount": "1000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "1'000'000 ucosm"
},
{
"proto": {"amount": "10000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
"text": "10'000'000 ucosm"
},
{
"proto": {"amount": "0", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "0 COSM"
},
{
"proto": {"amount": "1", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.01 COSM"
},
{
"proto": {"amount": "10", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "0.1 COSM"
},
{
"proto": {"amount": "100", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "1 COSM"
},
{
"proto": {"amount": "1000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "10 COSM"
},
{
"proto": {"amount": "10000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "100 COSM"
},
{
"proto": {"amount": "100000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "1'000 COSM"
},
{
"proto": {"amount": "1000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "10'000 COSM"
},
{
"proto": {"amount": "10000000", "denom": "ucosm"},
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
"text": "100'000 COSM"
},
{
"proto": {"amount": "0", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "0 POINT"
},
{
"proto": {"amount": "1", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "1 POINT"
},
{
"proto": {"amount": "10", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "10 POINT"
},
{
"proto": {"amount": "100", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "100 POINT"
},
{
"proto": {"amount": "1000", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "1'000 POINT"
},
{
"proto": {"amount": "10000", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "10'000 POINT"
},
{
"proto": {"amount": "100000", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "100'000 POINT"
},
{
"proto": {"amount": "1000000", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "1'000'000 POINT"
},
{
"proto": {"amount": "10000000", "denom": "point"},
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"text": "10'000'000 POINT"
},
{"text":"", "error": true},
{"text":"1COSM", "error": true},
{"text":"1 COSM", "error": true},
{"text":" 1 COSM", "error": true}
]

72
tx/textual/internal/testdata/coins.json vendored Normal file
View File

@ -0,0 +1,72 @@
[
{
"proto": [],
"metadata":{},
"text": ""
},
{
"proto": [
{ "amount": "1", "denom": "ucosm" }
],
"metadata":{
"ucosm": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"ustake": {"display": "STAKE", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
},
"text": "0.000001 COSM"
},
{
"proto": [
{ "amount": "1", "denom": "ucosm" },
{ "amount": "3", "denom": "ustake" }
],
"metadata":{
"ucosm": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"ustake": {"display": "STAKE", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
},
"text": "0.000001 COSM, 0.000003 STAKE"
},
{
"proto": [
{ "amount": "3", "denom": "ustake" },
{ "amount": "1", "denom": "ucosm" }
],
"metadata": {
"ucosm": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
"ustake": {"display": "STAKE", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
},
"text": "0.000001 COSM, 0.000003 STAKE"
},
{
"proto": [
{ "amount": "1", "denom": "uaa" },
{ "amount": "2", "denom": "ubb" },
{ "amount": "3", "denom": "uatom" }
],
"metadata": {
"uaa": {"display": "AA", "denom_units": [{"denom": "AA", "exponent": 6}, {"denom": "uaa", "exponent": 0}]},
"ubb": {"display": "BB", "denom_units": [{"denom": "BB", "exponent": 6}, {"denom": "ubb", "exponent": 0}]},
"uatom": {"display": "atom", "denom_units": [{"denom": "atom", "exponent": 6}, {"denom": "uatom", "exponent": 0}]}
},
"text": "0.000001 AA, 0.000002 BB, 0.000003 atom"
},
{
"proto": [
{ "amount": "4", "denom": "uxc1" },
{ "amount": "3", "denom": "uxc" },
{ "amount": "2", "denom": "uxb" },
{ "amount": "1", "denom": "uxa" }
],
"metadata": {
"uxa": {"display": "xA", "denom_units": [{"denom": "xA", "exponent": 6}, {"denom": "uxa", "exponent": 0}]},
"uxb": {"display": "xB", "denom_units": [{"denom": "xB", "exponent": 6}, {"denom": "uxb", "exponent": 0}]},
"uxc": {"display": "xC", "denom_units": [{"denom": "xC", "exponent": 6}, {"denom": "uxc", "exponent": 0}]},
"uxc1": {"display": "xC1", "denom_units": [{"denom": "xC1", "exponent": 6}, {"denom": "uxc1", "exponent": 0}]}
},
"text": "0.000001 xA, 0.000002 xB, 0.000003 xC, 0.000004 xC1"
},
{"text": "0.000001AA, 0.000002 BB, 0.000003 atom", "error": true},
{"text": "0.000001 AA, 0.000002 BB, 0.000003 atom", "error": true},
{"text": "0.000001 AA, 0.000002 BB, 0.000003 atom", "error": true},
{"text": " 0.000001 AA, 0.000002 BB, 0.000003 atom", "error": true},
{"text": "0.000001 AA, 0.000002 BB, 0.000003 atom ", "error": true}
]

View File

@ -12,8 +12,9 @@ enum Enumeration {
Two = 1;
}
// A contains fields that are parseable by SIGN_MODE_TEXTUAL.
// A is used for testing value renderers.
message A {
// Fields that are parseable by SIGN_MODE_TEXTUAL.
uint32 UINT32 = 1;
uint64 UINT64 = 2;
int32 INT32 = 3;
@ -24,20 +25,15 @@ message A {
repeated cosmos.base.v1beta1.Coin COINS = 8;
bytes BYTES = 9;
google.protobuf.Timestamp TIMESTAMP = 10;
}
// B contains fields that are not parseable by SIGN_MODE_TEXTUAL, some fields
// may be moved to A at some point.
message B {
int32 INT32 = 1;
sint32 SINT32 = 2;
int64 INT64 = 3;
sint64 SING64 = 4;
sfixed32 SFIXED32 = 5;
fixed32 FIXED32 = 6;
float FLOAT = 7;
sfixed64 SFIXED64 = 8;
fixed64 FIXED64 = 9;
double DOUBLE = 10;
map<string, B> MAP = 11;
// Fields that are not handled by SIGN_MODE_TEXTUAL.
sint32 SINT32 = 101;
sint64 SINT64 = 102;
sfixed32 SFIXED32 = 105;
fixed32 FIXED32 = 106;
float FLOAT = 107;
sfixed64 SFIXED64 = 108;
fixed64 FIXED64 = 109;
double DOUBLE = 110;
map<string, A> MAP = 111;
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,12 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
)
// bytesValueRenderer implements ValueRenderer for bytes
// NewBytesValueRenderer returns a ValueRenderer for Protobuf bytes, which are
// encoded as capital-letter hexadecimal, without the '0x' prefix.
func NewBytesValueRenderer() ValueRenderer {
return bytesValueRenderer{}
}
type bytesValueRenderer struct{}
var _ ValueRenderer = bytesValueRenderer{}

View File

@ -8,6 +8,7 @@ import (
"strings"
"testing"
"cosmossdk.io/tx/textual/valuerenderer"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
)
@ -18,15 +19,16 @@ func TestBytesJsonTestCases(t *testing.T) {
// their expected results in hex.
raw, err := os.ReadFile("../internal/testdata/bytes.json")
require.NoError(t, err)
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
for _, tc := range testcases {
data, err := base64.StdEncoding.DecodeString(tc.base64)
require.NoError(t, err)
valrend, err := valueRendererOf(data)
valrend, err := textual.GetValueRenderer(fieldDescriptorFromName("BYTES"))
require.NoError(t, err)
b := new(strings.Builder)

View File

@ -0,0 +1,101 @@
package valuerenderer_test
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/tx/textual/valuerenderer"
)
// mockCoinMetadataKey is used in the mock coin metadata querier.
func mockCoinMetadataKey(denom string) string {
return fmt.Sprintf("%s-%s", "coin-metadata", denom)
}
// mockCoinMetadataQuerier is a mock querier for coin metadata used for test
// purposes.
func mockCoinMetadataQuerier(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) {
v := ctx.Value(mockCoinMetadataKey(denom))
if v == nil {
return nil, nil
}
return v.(*bankv1beta1.Metadata), nil
}
func TestMetadataQuerier(t *testing.T) {
b := new(strings.Builder)
// Errors on nil metadata querier
textual := valuerenderer.NewTextual(nil)
vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COIN"))
require.NoError(t, err)
err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect()), b)
require.Error(t, err)
// Errors if metadata querier returns an error
expErr := fmt.Errorf("mock error")
textual = valuerenderer.NewTextual(func(_ context.Context, _ string) (*bankv1beta1.Metadata, error) {
return nil, expErr
})
vr, err = textual.GetValueRenderer(fieldDescriptorFromName("COIN"))
require.NoError(t, err)
err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect()), b)
require.ErrorIs(t, err, expErr)
err = vr.Format(context.Background(), protoreflect.ValueOf(NewGenericList([]*basev1beta1.Coin{{}})), b)
require.ErrorIs(t, err, expErr)
}
func TestCoinJsonTestcases(t *testing.T) {
var testcases []coinJsonTest
raw, err := os.ReadFile("../internal/testdata/coin.json")
require.NoError(t, err)
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COIN"))
require.NoError(t, err)
for _, tc := range testcases {
t.Run(tc.Text, func(t *testing.T) {
if tc.Proto != nil {
ctx := context.WithValue(context.Background(), mockCoinMetadataKey(tc.Proto.Denom), tc.Metadata)
b := new(strings.Builder)
err = vr.Format(ctx, protoreflect.ValueOf(tc.Proto.ProtoReflect()), b)
if tc.Error {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.Text, b.String())
}
// TODO Add parsing tests
// https://github.com/cosmos/cosmos-sdk/issues/13153
})
}
}
// coinJsonTest is the type of test cases in the testdata file.
// If the test case has a Proto, try to Format() it. If Error is set, expect
// an error, otherwise match Text, then Parse() the text and expect it to
// match (via proto.Equals()) the original Proto. If the test case has no
// Proto, try to Parse() the Text and expect an error if Error is set.
type coinJsonTest struct {
Proto *basev1beta1.Coin
Metadata *bankv1beta1.Metadata
Error bool
Text string
}

View File

@ -0,0 +1,168 @@
package valuerenderer
import (
"context"
"fmt"
"io"
"sort"
"strings"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/math"
"google.golang.org/protobuf/reflect/protoreflect"
)
// NewCoinsValueRenderer returns a ValueRenderer for SDK Coin and Coins.
func NewCoinsValueRenderer(q CoinMetadataQueryFn) ValueRenderer {
return coinsValueRenderer{q}
}
type coinsValueRenderer struct {
// coinMetadataQuerier defines a function to query the coin metadata from
// state. It should use bank module's `DenomsMetadata` gRPC query to fetch
// each denom's associated metadata, either using the bank keeper (for
// server-side code) or a gRPC query client (for client-side code).
coinMetadataQuerier CoinMetadataQueryFn
}
var _ ValueRenderer = coinsValueRenderer{}
func (vr coinsValueRenderer) Format(ctx context.Context, v protoreflect.Value, w io.Writer) error {
if vr.coinMetadataQuerier == nil {
return fmt.Errorf("expected non-nil coin metadata querier")
}
// Check whether we have a Coin or some Coins.
switch protoCoins := v.Interface().(type) {
// If it's a repeated Coin:
case protoreflect.List:
{
coins, metadatas := make([]*basev1beta1.Coin, protoCoins.Len()), make([]*bankv1beta1.Metadata, protoCoins.Len())
var err error
for i := 0; i < protoCoins.Len(); i++ {
coin := protoCoins.Get(i).Interface().(protoreflect.Message).Interface().(*basev1beta1.Coin)
coins[i] = coin
metadatas[i], err = vr.coinMetadataQuerier(ctx, coin.Denom)
if err != nil {
return err
}
}
formatted, err := formatCoins(coins, metadatas)
if err != nil {
return err
}
_, err = w.Write([]byte(formatted))
return err
}
// If it's a single Coin:
case protoreflect.Message:
{
coin := v.Interface().(protoreflect.Message).Interface().(*basev1beta1.Coin)
metadata, err := vr.coinMetadataQuerier(ctx, coin.Denom)
if err != nil {
return err
}
formatted, err := formatCoin(coin, metadata)
if err != nil {
return err
}
_, err = w.Write([]byte(formatted))
return err
}
default:
return fmt.Errorf("got invalid type %t for coins", v.Interface())
}
}
func (vr coinsValueRenderer) Parse(_ context.Context, r io.Reader) (protoreflect.Value, error) {
// ref: https://github.com/cosmos/cosmos-sdk/issues/13153
panic("implement me, see #13153")
}
// formatCoin formats a sdk.Coin into a value-rendered string, using the
// given metadata about the denom. It returns the formatted coin string, the
// display denom, and an optional error.
func formatCoin(coin *basev1beta1.Coin, metadata *bankv1beta1.Metadata) (string, error) {
coinDenom := coin.Denom
// Return early if no display denom or display denom is the current coin denom.
if metadata == nil || metadata.Display == "" || coinDenom == metadata.Display {
vr, err := formatDecimal(coin.Amount)
return vr + " " + coin.Denom, err
}
dispDenom := metadata.Display
// Find exponents of both denoms.
var coinExp, dispExp uint32
foundCoinExp, foundDispExp := false, false
for _, unit := range metadata.DenomUnits {
if coinDenom == unit.Denom {
coinExp = unit.Exponent
foundCoinExp = true
}
if dispDenom == unit.Denom {
dispExp = unit.Exponent
foundDispExp = true
}
}
// If we didn't find either exponent, then we return early.
if !foundCoinExp || !foundDispExp {
vr, err := formatInteger(coin.Amount)
return vr + " " + coin.Denom, err
}
exponentDiff := int64(coinExp) - int64(dispExp)
dispAmount, err := math.LegacyNewDecFromStr(coin.Amount)
if err != nil {
return "", err
}
if exponentDiff > 0 {
dispAmount = dispAmount.Mul(math.LegacyNewDec(10).Power(uint64(exponentDiff)))
} else {
dispAmount = dispAmount.Quo(math.LegacyNewDec(10).Power(uint64(-exponentDiff)))
}
vr, err := formatDecimal(dispAmount.String())
return vr + " " + dispDenom, err
}
// formatCoins formats Coins into a value-rendered string, which uses
// `formatCoin` separated by ", " (a comma and a space), and sorted
// alphabetically by value-rendered denoms. It expects an array of metadata
// (optionally nil), where each metadata at index `i` MUST match the coin denom
// at the same index.
func formatCoins(coins []*basev1beta1.Coin, metadata []*bankv1beta1.Metadata) (string, error) {
if len(coins) != len(metadata) {
panic(fmt.Errorf("formatCoins expect one metadata for each coin; expected %d, got %d", len(coins), len(metadata)))
}
formatted := make([]string, len(coins))
for i, coin := range coins {
var err error
formatted[i], err = formatCoin(coin, metadata[i])
if err != nil {
return "", err
}
}
// Sort the formatted coins by display denom.
sort.SliceStable(formatted, func(i, j int) bool {
denomI := strings.Split(formatted[i], " ")[1]
denomJ := strings.Split(formatted[j], " ")[1]
return denomI < denomJ
})
return strings.Join(formatted, ", "), nil
}

View File

@ -0,0 +1,67 @@
package valuerenderer_test
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/tx/textual/valuerenderer"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
)
func TestCoinsJsonTestcases(t *testing.T) {
var testcases []coinsJsonTest
raw, err := os.ReadFile("../internal/testdata/coins.json")
require.NoError(t, err)
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COINS"))
require.NoError(t, err)
for _, tc := range testcases {
t.Run(tc.Text, func(t *testing.T) {
if tc.Proto != nil {
// Create a context.Context containing all coins metadata, to simulate
// that they are in state.
ctx := context.Background()
for _, coin := range tc.Proto {
ctx = context.WithValue(ctx, mockCoinMetadataKey(coin.Denom), tc.Metadata[coin.Denom])
}
b := new(strings.Builder)
listValue := NewGenericList(tc.Proto)
err = vr.Format(ctx, protoreflect.ValueOf(listValue), b)
if tc.Error {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.Text, b.String())
}
// TODO Add parsing tests
// https://github.com/cosmos/cosmos-sdk/issues/13153
})
}
}
// coinsJsonTest is the type of test cases in the testdata file.
// If the test case has a Proto, try to Format() it. If Error is set, expect
// an error, otherwise match Text, then Parse() the text and expect it to
// match (via proto.Equals()) the original Proto. If the test case has no
// Proto, try to Parse() the Text and expect an error if Error is set.
type coinsJsonTest struct {
Proto []*basev1beta1.Coin
Metadata map[string]*bankv1beta1.Metadata
Text string
Error bool
}

View File

@ -11,6 +11,12 @@ import (
const thousandSeparator string = "'"
// NewDecValueRenderer returns a ValueRenderer for encoding sdk.Dec cosmos
// scalars.
func NewDecValueRenderer() ValueRenderer {
return decValueRenderer{}
}
type decValueRenderer struct{}
var _ ValueRenderer = decValueRenderer{}

View File

@ -9,6 +9,12 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
)
// NewIntValueRenderer returns a ValueRenderer for uint32, uint64, int32 and
// int64, and sdk.Int scalars.
func NewIntValueRenderer() ValueRenderer {
return intValueRenderer{}
}
type intValueRenderer struct{}
var _ ValueRenderer = intValueRenderer{}

View File

@ -0,0 +1,64 @@
package valuerenderer_test
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
var _ protoreflect.List = (*genericList[proto.Message])(nil)
// NewGenericList creates a empty list that satisfies the protoreflect.List
// interface.
func NewGenericList[T proto.Message](list []T) protoreflect.List {
return &genericList[T]{&list}
}
// genericList is an implementation of protoreflect.List for a generic
type genericList[T proto.Message] struct {
list *[]T
}
func (x *genericList[T]) Len() int {
if x.list == nil {
return 0
}
return len(*x.list)
}
func (x *genericList[T]) Get(i int) protoreflect.Value {
return protoreflect.ValueOfMessage((*x.list)[i].ProtoReflect())
}
func (x *genericList[T]) Set(i int, value protoreflect.Value) {
valueUnwrapped := value.Message()
concreteValue := valueUnwrapped.Interface().(T)
(*x.list)[i] = concreteValue
}
func (x *genericList[T]) Append(value protoreflect.Value) {
valueUnwrapped := value.Message()
concreteValue := valueUnwrapped.Interface().(T)
*x.list = append(*x.list, concreteValue)
}
func (x *genericList[T]) AppendMutable() protoreflect.Value {
v := *new(T)
*x.list = append(*x.list, v)
return protoreflect.ValueOfMessage(v.ProtoReflect())
}
func (x *genericList[T]) Truncate(n int) {
for i := n; i < len(*x.list); i++ {
(*x.list)[i] = *new(T)
}
*x.list = (*x.list)[:n]
}
func (x *genericList[T]) NewElement() protoreflect.Value {
v := *new(T)
return protoreflect.ValueOfMessage(v.ProtoReflect())
}
func (x *genericList[T]) IsValid() bool {
return x.list != nil
}

View File

@ -1,25 +1,45 @@
package valuerenderer
import (
"context"
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/timestamppb"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
cosmos_proto "github.com/cosmos/cosmos-proto"
)
// CoinMetadataQueryFn defines a function that queries state for the coin denom
// metadata. It is meant to be passed as an argument into `NewTextual`.
type CoinMetadataQueryFn func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error)
// Textual holds the configuration for dispatching
// to specific value renderers for SIGN_MODE_TEXTUAL.
type Textual struct {
// coinMetadataQuerier defines a function to query the coin metadata from
// state. It should use bank module's `DenomsMetadata` gRPC query to fetch
// each denom's associated metadata, either using the bank keeper (for
// server-side code) or a gRPC query client (for client-side code).
coinMetadataQuerier CoinMetadataQueryFn
// scalars defines a registry for Cosmos scalars.
scalars map[string]ValueRenderer
// messages defines a registry for custom message renderers, as defined in
// point #9 in the spec. Note that we also use this same registry for the
// following messages, as they can be thought of custom message rendering:
// - SDK coin and coins
// - Protobuf timestamp
// - Protobuf duration
messages map[protoreflect.FullName]ValueRenderer
}
// NewTextual returns a new Textual which provides
// value renderers.
func NewTextual() Textual {
t := Textual{}
func NewTextual(q CoinMetadataQueryFn) Textual {
t := Textual{coinMetadataQuerier: q}
t.init()
return t
}
@ -43,7 +63,7 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
return vr, nil
}
case fd.Kind() == protoreflect.BytesKind:
return bytesValueRenderer{}, nil
return NewBytesValueRenderer(), nil
// Integers
case fd.Kind() == protoreflect.Uint32Kind ||
@ -51,7 +71,7 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
fd.Kind() == protoreflect.Int32Kind ||
fd.Kind() == protoreflect.Int64Kind:
{
return intValueRenderer{}, nil
return NewIntValueRenderer(), nil
}
case fd.Kind() == protoreflect.MessageKind:
@ -73,12 +93,13 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
func (r *Textual) init() {
if r.scalars == nil {
r.scalars = map[string]ValueRenderer{}
r.scalars["cosmos.Int"] = intValueRenderer{}
r.scalars["cosmos.Dec"] = decValueRenderer{}
r.scalars["cosmos.Int"] = NewIntValueRenderer()
r.scalars["cosmos.Dec"] = NewDecValueRenderer()
}
if r.messages == nil {
r.messages = map[protoreflect.FullName]ValueRenderer{}
r.messages["google.protobuf.Timestamp"] = NewTimestampValueRenderer()
r.messages[(&basev1beta1.Coin{}).ProtoReflect().Descriptor().FullName()] = NewCoinsValueRenderer(r.coinMetadataQuerier)
r.messages[(&timestamppb.Timestamp{}).ProtoReflect().Descriptor().FullName()] = NewTimestampValueRenderer()
}
}

View File

@ -11,7 +11,6 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
tspb "google.golang.org/protobuf/types/known/timestamppb"
"cosmossdk.io/math"
"cosmossdk.io/tx/textual/internal/testpb"
@ -26,11 +25,13 @@ func TestFormatInteger(t *testing.T) {
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
textual := valuerenderer.NewTextual(nil)
for _, tc := range testcases {
// Parse test case strings as protobuf uint64
i, err := strconv.ParseUint(tc[0], 10, 64)
if err == nil {
r, err := valueRendererOf(i)
r, err := textual.GetValueRenderer(fieldDescriptorFromName("UINT64"))
require.NoError(t, err)
b := new(strings.Builder)
err = r.Format(context.Background(), protoreflect.ValueOf(i), b)
@ -42,7 +43,7 @@ func TestFormatInteger(t *testing.T) {
// Parse test case strings as protobuf uint32
i, err = strconv.ParseUint(tc[0], 10, 32)
if err == nil {
r, err := valueRendererOf(i)
r, err := textual.GetValueRenderer(fieldDescriptorFromName("UINT32"))
require.NoError(t, err)
b := new(strings.Builder)
err = r.Format(context.Background(), protoreflect.ValueOf(i), b)
@ -52,9 +53,9 @@ func TestFormatInteger(t *testing.T) {
}
// Parse test case strings as sdk.Ints
sdkInt, ok := math.NewIntFromString(tc[0])
_, ok := math.NewIntFromString(tc[0])
if ok {
r, err := valueRendererOf(sdkInt)
r, err := textual.GetValueRenderer(fieldDescriptorFromName("SDKINT"))
require.NoError(t, err)
b := new(strings.Builder)
err = r.Format(context.Background(), protoreflect.ValueOf(tc[0]), b)
@ -73,12 +74,12 @@ func TestFormatDecimal(t *testing.T) {
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
textual := valuerenderer.NewTextual(nil)
for _, tc := range testcases {
tc := tc
t.Run(tc[0], func(t *testing.T) {
d, err := math.LegacyNewDecFromStr(tc[0])
require.NoError(t, err)
r, err := valueRendererOf(d)
r, err := textual.GetValueRenderer(fieldDescriptorFromName("SDKDEC"))
require.NoError(t, err)
b := new(strings.Builder)
err = r.Format(context.Background(), protoreflect.ValueOf(tc[0]), b)
@ -89,74 +90,47 @@ func TestFormatDecimal(t *testing.T) {
}
}
func TestGetADR050ValueRenderer(t *testing.T) {
func TestDispatcher(t *testing.T) {
testcases := []struct {
name string
v interface{}
expErr bool
expValueRenderer valuerenderer.ValueRenderer
}{
{"uint32", uint32(1), false},
{"uint64", uint64(1), false},
{"sdk.Int", math.NewInt(1), false},
{"sdk.Dec", math.LegacyNewDec(1), false},
{"[]byte", []byte{1}, false},
{"float32", float32(1), true},
{"float64", float64(1), true},
{"UINT32", false, valuerenderer.NewIntValueRenderer()},
{"UINT64", false, valuerenderer.NewIntValueRenderer()},
{"SDKINT", false, valuerenderer.NewIntValueRenderer()},
{"SDKDEC", false, valuerenderer.NewDecValueRenderer()},
{"BYTES", false, valuerenderer.NewBytesValueRenderer()},
{"TIMESTAMP", false, valuerenderer.NewTimestampValueRenderer()},
{"COIN", false, valuerenderer.NewCoinsValueRenderer(nil)},
{"COINS", false, valuerenderer.NewCoinsValueRenderer(nil)},
{"FLOAT", true, nil},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
_, err := valueRendererOf(tc.v)
textual := valuerenderer.NewTextual(nil)
rend, err := textual.GetValueRenderer(fieldDescriptorFromName(tc.name))
if tc.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.IsType(t, tc.expValueRenderer, rend)
}
})
}
}
func TestTimestampDispatch(t *testing.T) {
a := (&testpb.A{}).ProtoReflect().Descriptor().Fields()
textual := valuerenderer.NewTextual()
rend, err := textual.GetValueRenderer(a.ByName(protoreflect.Name("TIMESTAMP")))
require.NoError(t, err)
require.IsType(t, valuerenderer.NewTimestampValueRenderer(), rend)
}
// valueRendererOf is like GetADR050ValueRenderer, but taking a Go type
// fieldDescriptorFromName is like GetADR050ValueRenderer, but taking a Go type
// as input instead of a protoreflect.FieldDescriptor.
func valueRendererOf(v interface{}) (valuerenderer.ValueRenderer, error) {
a, b := (&testpb.A{}).ProtoReflect().Descriptor().Fields(), (&testpb.B{}).ProtoReflect().Descriptor().Fields()
textual := valuerenderer.NewTextual()
switch v := v.(type) {
// Valid types for SIGN_MODE_TEXTUAL
case uint32:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("UINT32")))
case uint64:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("UINT64")))
case int32:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("INT32")))
case int64:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("INT64")))
case []byte:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("BYTES")))
case math.Int:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("SDKINT")))
case math.LegacyDec:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("SDKDEC")))
case tspb.Timestamp:
return textual.GetValueRenderer(a.ByName(protoreflect.Name("TIMESTAMP")))
// Invalid types for SIGN_MODE_TEXTUAL
case float32:
return textual.GetValueRenderer(b.ByName(protoreflect.Name("FLOAT")))
case float64:
return textual.GetValueRenderer(b.ByName(protoreflect.Name("FLOAT")))
default:
return nil, fmt.Errorf("value %s of type %T not recognized", v, v)
func fieldDescriptorFromName(name string) protoreflect.FieldDescriptor {
a := (&testpb.A{}).ProtoReflect().Descriptor().Fields()
fd := a.ByName(protoreflect.Name(name))
if fd == nil {
panic(fmt.Errorf("no field descriptor for %s", name))
}
return fd
}