From 6e2f5f310216044c8885dcbf1a1fd74034a80e80 Mon Sep 17 00:00:00 2001 From: colin axner Date: Tue, 4 Jun 2019 10:07:12 -0700 Subject: [PATCH] R4R: Support "unknown commands" for subcommands (#4465) Fixes #4284 Now prints: gaiacli query distr comission --trust-node cosmos1234 ERROR: unknown command "comission" for "distr" Did you mean this? commission Adds custom argument validation for subcommands with subcommands. Doesn't affect "query" or "tx" subcommands since they reside in gaia repo. All flags except help are disabled for these commands. --- .../improvements/sdk/4465-Unknown-subcomm | 1 + client/utils/utils.go | 31 ++++++++++++++ client/utils/utils_test.go | 42 +++++++++++++++++++ x/crisis/client/module_client.go | 8 +++- x/distribution/client/module_client.go | 16 +++++-- x/gov/client/module_client.go | 15 +++++-- x/mint/client/module_client.go | 15 +++++-- x/slashing/client/module_client.go | 15 +++++-- x/staking/client/module_client.go | 15 +++++-- 9 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 .pending/improvements/sdk/4465-Unknown-subcomm diff --git a/.pending/improvements/sdk/4465-Unknown-subcomm b/.pending/improvements/sdk/4465-Unknown-subcomm new file mode 100644 index 000000000000..fb6b34ce294b --- /dev/null +++ b/.pending/improvements/sdk/4465-Unknown-subcomm @@ -0,0 +1 @@ +#4465 Unknown subcommands print relevant error message \ No newline at end of file diff --git a/client/utils/utils.go b/client/utils/utils.go index 6334a8a40e8e..176db23589a9 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -7,6 +7,7 @@ import ( "os" "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/common" @@ -352,3 +353,33 @@ func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { return false } + +// ValidateCmd returns unknown command error or Help display if help flag set +func ValidateCmd(cmd *cobra.Command, args []string) error { + var cmds []string + var help bool + + // construct array of commands and search for help flag + for _, arg := range args { + if arg == "--help" || arg == "-h" { + help = true + } else if len(arg) > 0 && !(arg[0] == '-') { + cmds = append(cmds, arg) + } + } + + if !help && len(cmds) > 0 { + err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", cmds[0], cmd.CalledAs()) + + // build suggestions for unknown argument + if suggestions := cmd.SuggestionsFor(cmds[0]); len(suggestions) > 0 { + err += "\n\nDid you mean this?\n" + for _, s := range suggestions { + err += fmt.Sprintf("\t%v\n", s) + } + } + return errors.New(err) + } + + return cmd.Help() +} diff --git a/client/utils/utils_test.go b/client/utils/utils_test.go index 4bccac1d2def..767cf042f5f4 100644 --- a/client/utils/utils_test.go +++ b/client/utils/utils_test.go @@ -7,6 +7,7 @@ import ( "os" "testing" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" @@ -112,6 +113,47 @@ func TestReadStdTxFromFile(t *testing.T) { require.Equal(t, decodedTx.Memo, "foomemo") } +func TestValidateCmd(t *testing.T) { + // Setup root and subcommands + rootCmd := &cobra.Command{ + Use: "root", + } + queryCmd := &cobra.Command{ + Use: "query", + } + rootCmd.AddCommand(queryCmd) + + // Command being tested + distCmd := &cobra.Command{ + Use: "distr", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + queryCmd.AddCommand(distCmd) + + commissionCmd := &cobra.Command{ + Use: "commission", + } + distCmd.AddCommand(commissionCmd) + + tests := []struct { + reason string + args []string + wantErr bool + }{ + {"misspelled command", []string{"comission"}, true}, + {"no command provided", []string{}, false}, + {"help flag", []string{"comission", "--help"}, false}, + {"shorthand help flag", []string{"comission", "-h"}, false}, + } + + for _, tt := range tests { + err := ValidateCmd(distCmd, tt.args) + assert.Equal(t, tt.wantErr, err != nil, tt.reason) + } + +} + // aux functions func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) { diff --git a/x/crisis/client/module_client.go b/x/crisis/client/module_client.go index 12c9d2282b49..2b477ef04e5f 100644 --- a/x/crisis/client/module_client.go +++ b/x/crisis/client/module_client.go @@ -5,6 +5,7 @@ import ( amino "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/x/crisis" "github.com/cosmos/cosmos-sdk/x/crisis/client/cli" ) @@ -31,8 +32,11 @@ func (ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { txCmd := &cobra.Command{ - Use: crisis.ModuleName, - Short: "crisis transactions subcommands", + Use: crisis.ModuleName, + Short: "crisis transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } txCmd.AddCommand(client.PostCommands( diff --git a/x/distribution/client/module_client.go b/x/distribution/client/module_client.go index 780b0cabd8f5..5f3c874686c2 100644 --- a/x/distribution/client/module_client.go +++ b/x/distribution/client/module_client.go @@ -5,6 +5,8 @@ import ( amino "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/utils" + dist "github.com/cosmos/cosmos-sdk/x/distribution" distCmds "github.com/cosmos/cosmos-sdk/x/distribution/client/cli" ) @@ -21,8 +23,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { // GetQueryCmd returns the cli query commands for this module func (mc ModuleClient) GetQueryCmd() *cobra.Command { distQueryCmd := &cobra.Command{ - Use: "distr", - Short: "Querying commands for the distribution module", + Use: dist.ModuleName, + Short: "Querying commands for the distribution module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } distQueryCmd.AddCommand(client.GetCommands( @@ -40,8 +45,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { distTxCmd := &cobra.Command{ - Use: "distr", - Short: "Distribution transactions subcommands", + Use: dist.ModuleName, + Short: "Distribution transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } distTxCmd.AddCommand(client.PostCommands( diff --git a/x/gov/client/module_client.go b/x/gov/client/module_client.go index dbdf3aca99d5..60dd7f931639 100644 --- a/x/gov/client/module_client.go +++ b/x/gov/client/module_client.go @@ -5,6 +5,7 @@ import ( amino "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/x/gov" govCli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" ) @@ -28,8 +29,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec, pcmds ...*cobra.Command) func (mc ModuleClient) GetQueryCmd() *cobra.Command { // Group gov queries under a subcommand govQueryCmd := &cobra.Command{ - Use: gov.ModuleName, - Short: "Querying commands for the governance module", + Use: gov.ModuleName, + Short: "Querying commands for the governance module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } govQueryCmd.AddCommand(client.GetCommands( @@ -50,8 +54,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { govTxCmd := &cobra.Command{ - Use: gov.ModuleName, - Short: "Governance transactions subcommands", + Use: gov.ModuleName, + Short: "Governance transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } cmdSubmitProp := govCli.GetCmdSubmitProposal(mc.cdc) diff --git a/x/mint/client/module_client.go b/x/mint/client/module_client.go index 4a22b6e763b8..d76cdd1315a3 100644 --- a/x/mint/client/module_client.go +++ b/x/mint/client/module_client.go @@ -5,6 +5,7 @@ import ( amino "github.com/tendermint/go-amino" sdkclient "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/mint/client/cli" ) @@ -21,8 +22,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { // GetQueryCmd returns the cli query commands for the minting module. func (mc ModuleClient) GetQueryCmd() *cobra.Command { mintingQueryCmd := &cobra.Command{ - Use: mint.ModuleName, - Short: "Querying commands for the minting module", + Use: mint.ModuleName, + Short: "Querying commands for the minting module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } mintingQueryCmd.AddCommand( @@ -39,8 +43,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for the minting module. func (mc ModuleClient) GetTxCmd() *cobra.Command { mintTxCmd := &cobra.Command{ - Use: mint.ModuleName, - Short: "Minting transaction subcommands", + Use: mint.ModuleName, + Short: "Minting transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } return mintTxCmd diff --git a/x/slashing/client/module_client.go b/x/slashing/client/module_client.go index aca581f2e8a8..97071d785d63 100644 --- a/x/slashing/client/module_client.go +++ b/x/slashing/client/module_client.go @@ -5,6 +5,7 @@ import ( amino "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" ) @@ -23,8 +24,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { func (mc ModuleClient) GetQueryCmd() *cobra.Command { // Group slashing queries under a subcommand slashingQueryCmd := &cobra.Command{ - Use: slashing.ModuleName, - Short: "Querying commands for the slashing module", + Use: slashing.ModuleName, + Short: "Querying commands for the slashing module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } slashingQueryCmd.AddCommand( @@ -41,8 +45,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { slashingTxCmd := &cobra.Command{ - Use: slashing.ModuleName, - Short: "Slashing transactions subcommands", + Use: slashing.ModuleName, + Short: "Slashing transactions subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } slashingTxCmd.AddCommand(client.PostCommands( diff --git a/x/staking/client/module_client.go b/x/staking/client/module_client.go index 754e8c5c8500..71c3940e6511 100644 --- a/x/staking/client/module_client.go +++ b/x/staking/client/module_client.go @@ -5,6 +5,7 @@ import ( amino "github.com/tendermint/go-amino" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/x/staking/client/cli" "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -22,8 +23,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { // GetQueryCmd returns the cli query commands for this module func (mc ModuleClient) GetQueryCmd() *cobra.Command { stakingQueryCmd := &cobra.Command{ - Use: types.ModuleName, - Short: "Querying commands for the staking module", + Use: types.ModuleName, + Short: "Querying commands for the staking module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } stakingQueryCmd.AddCommand(client.GetCommands( cli.GetCmdQueryDelegation(mc.storeKey, mc.cdc), @@ -47,8 +51,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for this module func (mc ModuleClient) GetTxCmd() *cobra.Command { stakingTxCmd := &cobra.Command{ - Use: types.ModuleName, - Short: "Staking transaction subcommands", + Use: types.ModuleName, + Short: "Staking transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: utils.ValidateCmd, } stakingTxCmd.AddCommand(client.PostCommands(