diff --git a/client/context.go b/client/context.go index 9077643333..6b4282d793 100644 --- a/client/context.go +++ b/client/context.go @@ -2,6 +2,7 @@ package client import ( "bufio" + "encoding/json" "fmt" "io" "os" @@ -321,67 +322,57 @@ func (ctx Context) WithAccountRetriever(retriever AccountRetriever) Context { return ctx } -// Println outputs toPrint to the ctx.Output based on ctx.OutputFormat which is +// PrintOutput outputs toPrint to the ctx.Output based on ctx.OutputFormat which is // either text or json. If text, toPrint will be YAML encoded. Otherwise, toPrint // will be JSON encoded using ctx.JSONMarshaler. An error is returned upon failure. -func (ctx Context) Println(toPrint interface{}) error { - var ( - out []byte - err error - ) +func (ctx Context) PrintOutput(toPrint interface{}) error { + // always serialize JSON initially because proto json can't be directly YAML encoded + out, err := ctx.JSONMarshaler.MarshalJSON(toPrint) + if err != nil { + return err + } - switch ctx.OutputFormat { - case "text": - out, err = yaml.Marshal(&toPrint) - - case "json": - out, err = ctx.JSONMarshaler.MarshalJSON(toPrint) + if ctx.OutputFormat == "text" { + // handle text format by decoding and re-encoding JSON as YAML + var j interface{} + err = json.Unmarshal(out, &j) + if err != nil { + return err + } + out, err = yaml.Marshal(j) + if err != nil { + return err + } + } else if ctx.Indent { // To JSON indent, we re-encode the already encoded JSON given there is no // error. The re-encoded JSON uses the standard library as the initial encoded // JSON should have the correct output produced by ctx.JSONMarshaler. - if ctx.Indent && err == nil { - out, err = codec.MarshalIndentFromJSON(out) + out, err = codec.MarshalIndentFromJSON(out) + if err != nil { + return err } } + writer := ctx.Output + // default to stdout + if writer == nil { + writer = os.Stdout + } + + _, err = writer.Write(out) if err != nil { return err } - _, err = fmt.Fprintf(ctx.Output, "%s\n", out) - return err -} - -// PrintOutput prints output while respecting output and indent flags -// NOTE: pass in marshalled structs that have been unmarshaled -// because this function will panic on marshaling errors. -// -// TODO: Remove once client-side Protobuf migration has been completed. -// ref: https://github.com/cosmos/cosmos-sdk/issues/5864 -func (ctx Context) PrintOutput(toPrint interface{}) error { - var ( - out []byte - err error - ) - - switch ctx.OutputFormat { - case "text": - out, err = yaml.Marshal(&toPrint) - - case "json": - out, err = ctx.JSONMarshaler.MarshalJSON(toPrint) - - if ctx.Indent { - out, err = codec.MarshalIndentFromJSON(out) + if ctx.OutputFormat != "text" { + // append new-line for formats besides YAML + _, err = writer.Write([]byte("\n")) + if err != nil { + return err } } - if err != nil { - return err - } - - fmt.Println(string(out)) return nil } diff --git a/client/context_test.go b/client/context_test.go index fee6d55873..e5598cbcd8 100644 --- a/client/context_test.go +++ b/client/context_test.go @@ -1,9 +1,14 @@ package client_test import ( + "bytes" "os" "testing" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/testdata" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" @@ -76,3 +81,126 @@ func TestMain(m *testing.M) { viper.Set(flags.FlagKeyringBackend, keyring.BackendMemory) os.Exit(m.Run()) } + +func TestContext_PrintOutput(t *testing.T) { + ctx := client.Context{} + + animal := &testdata.Dog{ + Size_: "big", + Name: "Spot", + } + any, err := types.NewAnyWithValue(animal) + require.NoError(t, err) + hasAnimal := &testdata.HasAnimal{ + Animal: any, + X: 10, + } + + // + // proto + // + registry := testdata.NewTestInterfaceRegistry() + ctx = ctx.WithJSONMarshaler(codec.NewProtoCodec(registry)) + + // json + buf := &bytes.Buffer{} + ctx = ctx.WithOutput(buf) + ctx.OutputFormat = "json" + ctx.Indent = false + err = ctx.PrintOutput(hasAnimal) + require.NoError(t, err) + require.Equal(t, + `{"animal":{"@type":"/cosmos_sdk.codec.v1.Dog","size":"big","name":"Spot"},"x":"10"} +`, string(buf.Bytes())) + + // json indent + buf = &bytes.Buffer{} + ctx = ctx.WithOutput(buf) + ctx.OutputFormat = "json" + ctx.Indent = true + err = ctx.PrintOutput(hasAnimal) + require.NoError(t, err) + require.Equal(t, + `{ + "animal": { + "@type": "/cosmos_sdk.codec.v1.Dog", + "name": "Spot", + "size": "big" + }, + "x": "10" +} +`, string(buf.Bytes())) + + // yaml + buf = &bytes.Buffer{} + ctx = ctx.WithOutput(buf) + ctx.OutputFormat = "text" + ctx.Indent = false + err = ctx.PrintOutput(hasAnimal) + require.NoError(t, err) + require.Equal(t, + `animal: + '@type': /cosmos_sdk.codec.v1.Dog + name: Spot + size: big +x: "10" +`, string(buf.Bytes())) + + // + // amino + // + amino := testdata.NewTestAmino() + ctx = ctx.WithJSONMarshaler(codec.NewAminoCodec(&codec.Codec{Amino: amino})) + + // json + buf = &bytes.Buffer{} + ctx = ctx.WithOutput(buf) + ctx.OutputFormat = "json" + ctx.Indent = false + err = ctx.PrintOutput(hasAnimal) + require.NoError(t, err) + require.Equal(t, + `{"type":"testdata/HasAnimal","value":{"animal":{"type":"testdata/Dog","value":{"size":"big","name":"Spot"}},"x":"10"}} +`, string(buf.Bytes())) + + // json indent + buf = &bytes.Buffer{} + ctx = ctx.WithOutput(buf) + ctx.OutputFormat = "json" + ctx.Indent = true + err = ctx.PrintOutput(hasAnimal) + require.NoError(t, err) + require.Equal(t, + `{ + "type": "testdata/HasAnimal", + "value": { + "animal": { + "type": "testdata/Dog", + "value": { + "name": "Spot", + "size": "big" + } + }, + "x": "10" + } +} +`, string(buf.Bytes())) + + // yaml + buf = &bytes.Buffer{} + ctx = ctx.WithOutput(buf) + ctx.OutputFormat = "text" + ctx.Indent = false + err = ctx.PrintOutput(hasAnimal) + require.NoError(t, err) + require.Equal(t, + `type: testdata/HasAnimal +value: + animal: + type: testdata/Dog + value: + name: Spot + size: big + x: "10" +`, string(buf.Bytes())) +} diff --git a/client/tx/tx.go b/client/tx/tx.go index ced9039119..ea9563f3a6 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -61,7 +61,7 @@ func GenerateTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { return err } - return clientCtx.Println(tx.GetTx()) + return clientCtx.PrintOutput(tx.GetTx()) } // BroadcastTx attempts to generate, sign and broadcast a transaction with the @@ -125,7 +125,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { return err } - return clientCtx.Println(res) + return clientCtx.PrintOutput(res) } // WriteGeneratedTxResponse writes a generated unsigned transaction to the diff --git a/codec/testdata/test_helper.go b/codec/testdata/test_helper.go new file mode 100644 index 0000000000..b19d06fa96 --- /dev/null +++ b/codec/testdata/test_helper.go @@ -0,0 +1,40 @@ +package testdata + +import ( + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/tendermint/go-amino" +) + +func NewTestInterfaceRegistry() types.InterfaceRegistry { + registry := types.NewInterfaceRegistry() + registry.RegisterInterface("Animal", (*Animal)(nil)) + registry.RegisterImplementations( + (*Animal)(nil), + &Dog{}, + &Cat{}, + ) + registry.RegisterImplementations( + (*HasAnimalI)(nil), + &HasAnimal{}, + ) + registry.RegisterImplementations( + (*HasHasAnimalI)(nil), + &HasHasAnimal{}, + ) + return registry +} + +func NewTestAmino() *amino.Codec { + cdc := amino.NewCodec() + cdc.RegisterInterface((*Animal)(nil), nil) + cdc.RegisterConcrete(&Dog{}, "testdata/Dog", nil) + cdc.RegisterConcrete(&Cat{}, "testdata/Cat", nil) + + cdc.RegisterInterface((*HasAnimalI)(nil), nil) + cdc.RegisterConcrete(&HasAnimal{}, "testdata/HasAnimal", nil) + + cdc.RegisterInterface((*HasHasAnimalI)(nil), nil) + cdc.RegisterConcrete(&HasHasAnimal{}, "testdata/HasHasAnimal", nil) + + return cdc +} diff --git a/codec/types/compat_test.go b/codec/types/compat_test.go index bf9875d282..31f53d27a1 100644 --- a/codec/types/compat_test.go +++ b/codec/types/compat_test.go @@ -26,7 +26,7 @@ type Suite struct { func (s *Suite) SetupTest() { s.cdc = amino.NewCodec() s.cdc.RegisterInterface((*testdata.Animal)(nil), nil) - s.cdc.RegisterConcrete(&testdata.Dog{}, "testdata/Dob", nil) + s.cdc.RegisterConcrete(&testdata.Dog{}, "testdata/Dog", nil) s.spot = &testdata.Dog{Size_: "small", Name: "Spot"} s.a = TypeWithInterface{Animal: s.spot} diff --git a/codec/types/types_test.go b/codec/types/types_test.go index 3a49d937b2..2089fc4c54 100644 --- a/codec/types/types_test.go +++ b/codec/types/types_test.go @@ -13,27 +13,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec/testdata" ) -func NewTestInterfaceRegistry() types.InterfaceRegistry { - registry := types.NewInterfaceRegistry() - registry.RegisterInterface("Animal", (*testdata.Animal)(nil)) - registry.RegisterImplementations( - (*testdata.Animal)(nil), - &testdata.Dog{}, - &testdata.Cat{}, - ) - registry.RegisterImplementations( - (*testdata.HasAnimalI)(nil), - &testdata.HasAnimal{}, - ) - registry.RegisterImplementations( - (*testdata.HasHasAnimalI)(nil), - &testdata.HasHasAnimal{}, - ) - return registry -} - func TestPackUnpack(t *testing.T) { - registry := NewTestInterfaceRegistry() + registry := testdata.NewTestInterfaceRegistry() spot := &testdata.Dog{Name: "Spot"} any := types.Any{} @@ -81,7 +62,7 @@ func TestRegister(t *testing.T) { } func TestUnpackInterfaces(t *testing.T) { - registry := NewTestInterfaceRegistry() + registry := testdata.NewTestInterfaceRegistry() spot := &testdata.Dog{Name: "Spot"} any, err := types.NewAnyWithValue(spot) @@ -105,7 +86,7 @@ func TestUnpackInterfaces(t *testing.T) { } func TestNested(t *testing.T) { - registry := NewTestInterfaceRegistry() + registry := testdata.NewTestInterfaceRegistry() spot := &testdata.Dog{Name: "Spot"} any, err := types.NewAnyWithValue(spot) @@ -145,7 +126,7 @@ func TestAny_ProtoJSON(t *testing.T) { require.NoError(t, err) require.Equal(t, "{\"@type\":\"/cosmos_sdk.codec.v1.Dog\",\"name\":\"Spot\"}", json) - registry := NewTestInterfaceRegistry() + registry := testdata.NewTestInterfaceRegistry() jum := &jsonpb.Unmarshaler{} var any2 types.Any err = jum.Unmarshal(strings.NewReader(json), &any2) diff --git a/x/bank/client/cli/query.go b/x/bank/client/cli/query.go index 2d7ce79e0a..87ec8a28f8 100644 --- a/x/bank/client/cli/query.go +++ b/x/bank/client/cli/query.go @@ -1,6 +1,7 @@ package cli import ( + "context" "fmt" "strings" @@ -9,7 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -19,17 +19,7 @@ const ( flagDenom = "denom" ) -// --------------------------------------------------------------------------- -// Deprecated -// -// TODO: Remove once client-side Protobuf migration has been completed. -// --------------------------------------------------------------------------- - -// GetQueryCmd returns the parent querying command for the bank module. -// -// TODO: Remove once client-side Protobuf migration has been completed. -// ref: https://github.com/cosmos/cosmos-sdk/issues/5864 -func GetQueryCmd(cdc *codec.Codec) *cobra.Command { +func GetQueryCmd(clientCtx client.Context) *cobra.Command { cmd := &cobra.Command{ Use: types.ModuleName, Short: "Querying commands for the bank module", @@ -39,73 +29,44 @@ func GetQueryCmd(cdc *codec.Codec) *cobra.Command { } cmd.AddCommand( - GetBalancesCmd(cdc), - GetCmdQueryTotalSupply(cdc), + GetBalancesCmd(clientCtx), + GetCmdQueryTotalSupply(clientCtx), ) return cmd } -// GetAccountCmd returns a CLI command handler that facilitates querying for a -// single or all account balances by address. -// -// TODO: Remove once client-side Protobuf migration has been completed. -// ref: https://github.com/cosmos/cosmos-sdk/issues/5864 -func GetBalancesCmd(cdc *codec.Codec) *cobra.Command { +func GetBalancesCmd(clientCtx client.Context) *cobra.Command { cmd := &cobra.Command{ Use: "balances [address]", Short: "Query for account balances by address", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - clientCtx := client.NewContext().WithCodec(cdc).WithJSONMarshaler(cdc) + queryClient := types.NewQueryClient(clientCtx.Init()) addr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } - var ( - params interface{} - result interface{} - route string - ) - denom := viper.GetString(flagDenom) + if denom == "" { - params = types.NewQueryAllBalancesRequest(addr) - route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAllBalances) - } else { - params = types.NewQueryBalanceRequest(addr, denom) - route = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance) + params := types.NewQueryAllBalancesRequest(addr) + res, err := queryClient.AllBalances(context.Background(), params) + if err != nil { + return err + } + return clientCtx.PrintOutput(res.Balances) + } - bz, err := cdc.MarshalJSON(params) - if err != nil { - return fmt.Errorf("failed to marshal params: %w", err) - } - - res, _, err := clientCtx.QueryWithData(route, bz) + params := types.NewQueryBalanceRequest(addr, denom) + res, err := queryClient.Balance(context.Background(), params) if err != nil { return err } - - if denom == "" { - var balances sdk.Coins - if err := cdc.UnmarshalJSON(res, &balances); err != nil { - return err - } - - result = balances - } else { - var balance sdk.Coin - if err := cdc.UnmarshalJSON(res, &balance); err != nil { - return err - } - - result = balance - } - - return clientCtx.PrintOutput(result) + return clientCtx.PrintOutput(res.Balance) }, } @@ -114,9 +75,7 @@ func GetBalancesCmd(cdc *codec.Codec) *cobra.Command { return flags.GetCommands(cmd)[0] } -// TODO: Remove once client-side Protobuf migration has been completed. -// ref: https://github.com/cosmos/cosmos-sdk/issues/5864 -func GetCmdQueryTotalSupply(cdc *codec.Codec) *cobra.Command { +func GetCmdQueryTotalSupply(clientCtx client.Context) *cobra.Command { cmd := &cobra.Command{ Use: "total [denom]", Args: cobra.MaximumNArgs(1), @@ -135,13 +94,21 @@ $ %s query %s total stake ), ), RunE: func(cmd *cobra.Command, args []string) error { - clientCtx := client.NewContext().WithCodec(cdc).WithJSONMarshaler(cdc) + queryClient := types.NewQueryClient(clientCtx.Init()) if len(args) == 0 { - return queryTotalSupply(clientCtx, cdc) + res, err := queryClient.TotalSupply(context.Background(), &types.QueryTotalSupplyRequest{}) + if err != nil { + return err + } + return clientCtx.PrintOutput(res.Supply) } - return querySupplyOf(clientCtx, cdc, args[0]) + res, err := queryClient.SupplyOf(context.Background(), &types.QuerySupplyOfRequest{Denom: args[0]}) + if err != nil { + return err + } + return clientCtx.PrintOutput(res.Amount) }, } diff --git a/x/bank/client/cli/util.go b/x/bank/client/cli/util.go deleted file mode 100644 index c2b68accd2..0000000000 --- a/x/bank/client/cli/util.go +++ /dev/null @@ -1,52 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -func queryTotalSupply(clientCtx client.Context, cdc *codec.Codec) error { - params := types.NewQueryTotalSupplyParams(1, 0) // no pagination - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTotalSupply), bz) - if err != nil { - return err - } - - var totalSupply sdk.Coins - err = cdc.UnmarshalJSON(res, &totalSupply) - if err != nil { - return err - } - - return clientCtx.PrintOutput(totalSupply) -} - -func querySupplyOf(clientCtx client.Context, cdc *codec.Codec, denom string) error { - params := types.NewQuerySupplyOfParams(denom) - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySupplyOf), bz) - if err != nil { - return err - } - - var supply sdk.Int - err = cdc.UnmarshalJSON(res, &supply) - if err != nil { - return err - } - - return clientCtx.PrintOutput(supply) -} diff --git a/x/bank/module.go b/x/bank/module.go index d6cc378657..c103af2d8d 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -70,7 +70,7 @@ func (AppModuleBasic) GetTxCmd(clientCtx client.Context) *cobra.Command { // GetQueryCmd returns no root query command for the bank module. func (AppModuleBasic) GetQueryCmd(clientCtx client.Context) *cobra.Command { - return cli.GetQueryCmd(clientCtx.Codec) + return cli.GetQueryCmd(clientCtx) } // RegisterInterfaceTypes registers interfaces and implementations of the bank module. @@ -88,7 +88,9 @@ type AppModule struct { accountKeeper types.AccountKeeper } -func (am AppModule) RegisterQueryService(grpc.Server) {} +func (am AppModule) RegisterQueryService(server grpc.Server) { + types.RegisterQueryServer(server, am.keeper) +} // NewAppModule creates a new AppModule object func NewAppModule(cdc codec.Marshaler, keeper keeper.Keeper, accountKeeper types.AccountKeeper) AppModule {