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 0000000000..fb6b34ce29 --- /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 6334a8a40e..176db23589 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 4bccac1d2d..767cf042f5 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 12c9d2282b..2b477ef04e 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 780b0cabd8..5f3c874686 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 dbdf3aca99..60dd7f9316 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 4a22b6e763..d76cdd1315 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 aca581f2e8..97071d785d 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 754e8c5c85..71c3940e65 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(