Skip to content

Commit

Permalink
rethink errors and user messages
Browse files Browse the repository at this point in the history
Try to decouple domain from communication to the user.
  • Loading branch information
ianic committed Oct 25, 2021
1 parent e779379 commit b6e37b8
Show file tree
Hide file tree
Showing 20 changed files with 212 additions and 108 deletions.
43 changes: 21 additions & 22 deletions cli/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ func newAwsCommand() *cobra.Command {
}

func newAwsInstallCommand() *cobra.Command {
nextSteps := `
- Run mantil help to get started
- Run mantil new to start a new project
- Further documentation:
https://docs.mantil.io`
argumentsUsage := fmt.Sprintf(`
[account-name] Mantil account name reference.
If not provided default name %s will be used for the first account.`, workspace.DefaultAccountName)

a := &controller.SetupArgs{}
cmd := &cobra.Command{
Use: boldize(fmt.Sprintf(`install [account-name] [flags]
\bARGUMENTS\c
[account-name] Mantil account name reference.
If not provided default name %s will be used for the first account.`,
controller.DefaultAccountName())),
Use: "install [account-name] [flags]",
Short: "Install Mantil into AWS account",
Long: `Install Mantil into AWS account
Expand All @@ -44,7 +48,7 @@ You must provide credentials for Mantil to access your AWS account.
There is --dry-run flag which will show you what credentials will be used
and what account will be managed by command.`,
Args: cobra.MaximumNArgs(1),
Example: credentialsHelp("install"),
Example: setupExamples("install"),
RunE: func(cmd *cobra.Command, args []string) error {
a.ParseArgs(args)
stp, err := controller.NewSetup(a)
Expand All @@ -56,30 +60,22 @@ and what account will be managed by command.`,
return nil
}
if err := stp.Create(); err != nil {
return log.WithUserMessage(err, "Install failed!")
return log.Wrap(err)
}
ui.Info("==> Next steps:")
ui.Info("\t- Run mantil help to get started")
ui.Info("\t- Run mantil new to start a new project")
ui.Info("\t- Further documentation:")
ui.Info("\t https://docs.mantil.io")
ui.Info("") // new line
showNextSteps(nextSteps)
return nil
},
}
setUsageTemplate(cmd, argumentsUsage)
bindAwsInstallFlags(cmd, a)
cmd.Flags().BoolVar(&a.Override, "override", false, "force override access tokens on already installed account")
//cmd.Flags().BoolVar(&a.Override, "override", false, "force override access tokens on already installed account")
return cmd
}

func newAwsUninstallCommand() *cobra.Command {
a := &controller.SetupArgs{}
cmd := &cobra.Command{
Use: boldize(fmt.Sprintf(`uninstall [account-name] [flags]
\bARGUMENTS\c
[account-name] Mantil account name reference.
If not provided default name %s will be used for the first account.`, workspace.DefaultAccountName)),
Use: "uninstall [account-name] [flags]",
Short: "Uninstall Mantil from AWS account",
Long: `Uninstall Mantil from AWS account
Expand All @@ -89,7 +85,7 @@ You must provide credentials for Mantil to access your AWS account.
There is --dry-run flag which will show you what credentials will be used
and what account will be managed by command.`,
Args: cobra.MaximumNArgs(1),
Example: credentialsHelp("uninstall"),
Example: setupExamples("uninstall"),
RunE: func(cmd *cobra.Command, args []string) error {
a.ParseArgs(args)
stp, err := controller.NewSetup(a)
Expand All @@ -103,11 +99,14 @@ and what account will be managed by command.`,
return stp.Destroy()
},
}
cmd.SetUsageTemplate(usageTemplate(fmt.Sprintf(`
[account-name] Mantil account name reference.
If not provided default name %s will be used for the first account.`, workspace.DefaultAccountName)))
bindAwsInstallFlags(cmd, a)
return cmd
}

func credentialsHelp(commandName string) string {
func setupExamples(commandName string) string {
return strings.ReplaceAll(` You must provide credentials for Mantil to access your AWS account.
There are three ways to provide credentials.
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (d *Cmd) setAWSclient() error {

func (d *Cmd) Deploy() error {
if err := d.deploy(); err != nil {
return log.WithUserMessage(err, "Failed")
return log.Wrap(err)
}
return nil
}
Expand Down
8 changes: 3 additions & 5 deletions cli/cmd/deploy/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"

"github.com/mantil-io/mantil/cli/log"
"github.com/mantil-io/mantil/cli/ui"
Expand All @@ -34,7 +32,7 @@ func (d *Cmd) localFunctions() ([]workspace.Resource, error) {
binaryPath := path.Join(funcDir, BinaryName)
hash, err := fileHash(binaryPath)
if err != nil {
return nil, log.WithUserMessage(err, fmt.Sprintf("Hashing %s failed", binaryPath))
return nil, log.Wrap(err, "failed to hash %s", binaryPath)
}
localFuncs = append(localFuncs, workspace.Resource{
Name: n,
Expand Down Expand Up @@ -72,7 +70,7 @@ func (d *Cmd) buildFunction(name, funcDir string) error {
ShowShellCmd: false,
})
if err != nil {
return log.WithUserMessage(err, strings.Join(bl.Lines(), "\n"))
return &log.GoBuildError{Name: name, Dir: funcDir, Lines: bl.Lines()}
}
return nil
}
Expand All @@ -86,7 +84,7 @@ func (d *Cmd) uploadFunctions() error {
path := filepath.Join(d.path, FunctionsDir, n, BinaryName)
ui.Info(n)
if err := d.uploadBinaryToS3(f.S3Key, path); err != nil {
return log.WithUserMessage(err, "Failed to upload file to s3")
return log.Wrap(err, "failed to upload file %s to s3", path)
}
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/examples/errors_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func NewErrorsCommand() *cobra.Command {
var es errStack
err := es.run(pero)
if err != nil {
return log.WithUserMessage(err, "high level message")
return log.Wrap(err, "high level message")
}
return nil
},
Expand Down Expand Up @@ -50,7 +50,7 @@ func (e *errStack) first() error {
func (e *errStack) second() error {
ui.Debug("in second")
if err := e.third(); err != nil {
return log.WithUserMessage(err, "message that should be shown to the user")
return log.Wrap(err, "message that should be shown to the user")
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func Api(name string, methods []string) error {
return log.Wrap(fmt.Errorf("Could not generate api - name \"%s\" is reserved", name))
}
if err := workspace.ValidateName(name); err != nil {
return log.WithUserMessage(err, err.UserMessage())
return log.Wrap(err)
}
projectPath, err := workspace.FindProjectRoot(".")
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func newLogs(a logsArgs) (*logsCmd, error) {

fn := stage.FindFunction(a.function)
if fn == nil {
return nil, log.WithUserMessage(nil, "function %s not found", a.function)
return nil, log.Wrapf("function %s not found", a.function)
}

return &logsCmd{
Expand Down
16 changes: 6 additions & 10 deletions cli/cmd/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func newNew(a newArgs) (*newCmd, error) {

func (c *newCmd) run() error {
if err := workspace.ValidateName(c.name); err != nil {
return log.WithUserMessage(err, err.UserMessage())
return log.Wrap(err)
}
projectPath, _ := filepath.Abs(c.name)
repo, err := c.repoURL()
Expand All @@ -57,12 +57,10 @@ func (c *newCmd) run() error {
ui.Info("Cloning into %s and replacing import paths with %s...", projectPath, c.moduleName)
if err := git.CreateRepo(repo, c.name, c.moduleName); err != nil {
if errors.Is(err, git.ErrRepositoryNotFound) {
return log.WithUserMessage(err, c.sourceUserError())
return log.Wrap(err, c.sourceUserError())
}
return log.WithUserMessage(
err,
fmt.Sprintf("Could not initialize repository from source %s: %v", repo, err),
)
return log.Wrap(err, "Could not initialize repository from source %s: %v", repo, err)

}
fs, err := workspace.NewSingleDeveloperWorkspaceStore()
if err != nil {
Expand All @@ -82,10 +80,8 @@ func (c *newCmd) repoURL() (string, error) {
} else {
template := c.template()
if template == "" {
return "", log.WithUserMessage(
fmt.Errorf("invalid template %s", c.repo),
c.sourceUserError(),
)
return "", log.Wrap(fmt.Errorf("invalid template %s", c.repo))

}
repo = templateRepos[template]
ui.Info("Creating project %s from template %s...", c.name, template)
Expand Down
72 changes: 65 additions & 7 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package cmd

import (
"errors"
"fmt"
"strings"

"github.com/mantil-io/mantil/cli/build"
"github.com/mantil-io/mantil/cli/controller"
"github.com/mantil-io/mantil/cli/log"
"github.com/mantil-io/mantil/cli/ui"
"github.com/pkg/errors"
"github.com/mantil-io/mantil/workspace"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
Expand All @@ -33,8 +36,9 @@ func Execute() error {
return err
}

showError(ec, err)
// in other cases show error without usage
ui.Error(err)
//ui.Error(err)
return err
}

Expand All @@ -50,7 +54,7 @@ func root() *cobra.Command {
cmd.PersistentFlags().Bool("no-color", false, "don't use colors in output")
cmd.PersistentFlags().Bool("help", false, "show command help") // move help to global commands
cmd.Flags().Bool("version", false, "show mantil version") // remove -v shortcut for version
cmd.SetUsageTemplate(usageTemplate())
cmd.SetUsageTemplate(usageTemplate(""))

add := func(factory func() *cobra.Command) {
sub := factory()
Expand Down Expand Up @@ -84,8 +88,13 @@ func GenDoc(dir string) error {
return doc.GenMarkdownTree(cmd, dir)
}

func usageTemplate() string {
str := `\bUSAGE\c{{if .Runnable}}
func usageTemplate(argumentsUsage string) string {
if argumentsUsage != "" {
argumentsUsage = fmt.Sprintf(`
\bARGUMENTS\c%s`, argumentsUsage)
}

str := fmt.Sprintf(`\bUSAGE\c{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Expand All @@ -96,7 +105,7 @@ func usageTemplate() string {
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}{{if .HasAvailableLocalFlags}}
%s
\bFLAGS\c
{{.LocalFlags.FlagUsagesWrapped 120 | trimTrailingWhitespaces}}{{end}}{{if .HasExample}}
Expand All @@ -112,11 +121,19 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
\bLEARN MORE\c
Visit https://docs.mantil.io to learn more.
For further support contact us at [email protected].
`
`, argumentsUsage)

return boldize(str)
}

func setUsageTemplate(cmd *cobra.Command, argumentsUsage string) {
cmd.SetUsageTemplate(usageTemplate(argumentsUsage))
}

func showNextSteps(str string) {
fmt.Printf("==> Next steps:%s\n", str)
}

func boldize(str string) string {
return strings.ReplaceAll(strings.ReplaceAll(str,
`\b`, bold),
Expand All @@ -127,3 +144,44 @@ const (
bold = "\033[1m"
clear = "\033[0m"
)

func showError(cmd *cobra.Command, err error) {
if err == nil {
return
}
log.Error(err) // write to log file

var aee *workspace.AccountExistsError
if errors.As(err, &aee) {
ui.Errorf("account '%s' already exists", aee.Name)
if aee.Name == workspace.DefaultAccountName {
fmt.Printf(`
'%s' is default account name and it is already used.
Please specify another name in mantil command.
`, aee.Name)
}
return
}

var rerr *workspace.ErrReservedName
if errors.As(err, &rerr) {
ui.Errorf("'%s' is reserved name", rerr.Name)
return
}
var verr workspace.ValidationError
if errors.As(err, &verr) {
ui.Errorf(verr.UserMessage())
return
}

var gbe *log.GoBuildError
if errors.As(err, &gbe) {
for _, line := range gbe.Lines {
ui.ErrorLine(line)
}
return
}
//ok, _ := cmd.InheritedFlags().GetBool("no-color")

ui.Error(err)
}
10 changes: 2 additions & 8 deletions cli/cmd/stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func newStage(a stageArgs) (*stageCmd, error) {

func (c *stageCmd) new() error {
if err := workspace.ValidateName(c.stage); err != nil {
return log.WithUserMessage(err, err.UserMessage())
return log.Wrap(err)
}

if c.account == "" {
Expand All @@ -57,12 +57,6 @@ func (c *stageCmd) new() error {

stage, err := c.project.NewStage(c.stage, c.account)
if err != nil {
if err == workspace.ErrStageExists {
return log.WithUserMessage(err, "Stage %s already exists.", c.stage)
}
if err == workspace.ErrAccountNotFound {
return log.WithUserMessage(err, "Account %s not found.", c.account)
}
return log.Wrap(err)
}

Expand Down Expand Up @@ -90,7 +84,7 @@ func (s *stageCmd) selectAccount(accounts []string) (string, error) {

func (c *stageCmd) destroy() error {
if !c.destroyAll && c.stage == "" {
return log.WithUserMessage(nil, "No stage specified")
return log.Wrapf("No stage specified")
}
if !c.force {
if err := c.confirmDestroy(); err != nil {
Expand Down
Loading

0 comments on commit b6e37b8

Please sign in to comment.