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(