From c1e2c641ef4b46594e18ab42f9dde88e6e6e6380 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 16 Aug 2024 17:32:54 +0200 Subject: [PATCH] args: add new `UnknownCommandError` type This commit makes unknown commands a proper error type so that the caller can check for this specific error type without having to do string comparisons. This is useful in the context of https://github.com/spf13/cobra/issues/823 --- args.go | 15 +++++++++++++-- command_test.go | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/args.go b/args.go index ed1e70cea..81db4d858 100644 --- a/args.go +++ b/args.go @@ -21,6 +21,17 @@ import ( type PositionalArgs func(cmd *Command, args []string) error +// UnknownCommandError is returned for unknown command +type UnknownCommandError struct { + unknownCmd string + cmdPath string + suggestions string +} + +func (e UnknownCommandError) Error() string { + return fmt.Sprintf("unknown command %q for %q%s", e.unknownCmd, e.cmdPath, e.suggestions) +} + // legacyArgs validation has the following behaviour: // - root commands with no subcommands can take arbitrary arguments // - root commands with subcommands will do subcommand validity checking @@ -33,7 +44,7 @@ func legacyArgs(cmd *Command, args []string) error { // root command with subcommands, do subcommand checking. if !cmd.HasParent() && len(args) > 0 { - return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) + return UnknownCommandError{args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])} } return nil } @@ -41,7 +52,7 @@ func legacyArgs(cmd *Command, args []string) error { // NoArgs returns an error if any args are included. func NoArgs(cmd *Command, args []string) error { if len(args) > 0 { - return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()) + return UnknownCommandError{args[0], cmd.CommandPath(), ""} } return nil } diff --git a/command_test.go b/command_test.go index 9ce7a529b..dc3314c43 100644 --- a/command_test.go +++ b/command_test.go @@ -147,7 +147,10 @@ func TestRootExecuteUnknownCommand(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) - output, _ := executeCommand(rootCmd, "unknown") + output, err := executeCommand(rootCmd, "unknown") + if _, ok := err.(UnknownCommandError); !ok { + t.Errorf("Expected:\n %T\nGot:\n %T\n", err, UnknownCommandError{}) + } expected := "Error: unknown command \"unknown\" for \"root\"\nRun 'root --help' for usage.\n" @@ -156,6 +159,16 @@ func TestRootExecuteUnknownCommand(t *testing.T) { } } +func TestRootFindUnknownCommandErrorType(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) + + _, _, err := rootCmd.Find([]string{"unknown"}) + if _, ok := err.(UnknownCommandError); !ok { + t.Errorf("Expected:\n %T\nGot:\n %T\n", err, UnknownCommandError{}) + } +} + func TestSubcommandExecuteC(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun}