From f2cbe7760970e2da0827eac0ac82110f1fe72441 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 14:20:50 -0500 Subject: [PATCH 1/9] chore: add command to easily visualize different errors --- cli/errors.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ cli/exp.go | 1 + 2 files changed, 84 insertions(+) create mode 100644 cli/errors.go diff --git a/cli/errors.go b/cli/errors.go new file mode 100644 index 0000000000000..eb10b701c9391 --- /dev/null +++ b/cli/errors.go @@ -0,0 +1,83 @@ +package cli + +import ( + "io" + "os" + + "github.com/coder/coder/v2/cli/clibase" + "github.com/coder/coder/v2/codersdk" + "golang.org/x/xerrors" +) + +func (r *RootCmd) errorExample() *clibase.Cmd { + errorCmd := func(use string, err error) *clibase.Cmd { + return &clibase.Cmd{ + Use: use, + Handler: func(inv *clibase.Invocation) error { + return err + }, + } + } + + cmd := &clibase.Cmd{ + Use: "example-error", + Short: "Shows what different error messages look like", + Long: "This command is pretty pointless, but without it testing errors is" + + "difficult to visually inspect. Error message formatting is inherently" + + "visual, so we need a way to quickly see what they look like.", + Handler: func(inv *clibase.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Children: []*clibase.Cmd{ + // Typical codersdk error + errorCmd("sdk", &codersdk.Error{ + Response: codersdk.Response{ + Message: "Top level sdk error message.", + Detail: "magic dust unavailable, please try again later", + Validations: []codersdk.ValidationError{ + { + Field: "region", + Detail: "magic dust is not available in your region", + }, + }, + }, + Helper: "Have you tried turning it off and on again?", + }), + + // Typical cli error + errorCmd("cmd", xerrors.Errorf("some error: %w", errorWithStackTrace())), + + // A multi-error + { + Use: "multi-error", + Handler: func(inv *clibase.Invocation) error { + // Closing the stdin file descriptor will cause the next close + // to fail. This is joined to the returned Command error. + if f, ok := inv.Stdin.(*os.File); ok { + _ = f.Close() + } + + return xerrors.Errorf("some error: %w", errorWithStackTrace()) + }, + }, + }, + } + + return cmd +} + +type errorClose struct { + io.ReadCloser +} + +func (e errorClose) Close() error { + err := e.ReadCloser.Close() + if err == nil { + return xerrors.Errorf("always close error") + } + return err +} + +func errorWithStackTrace() error { + return xerrors.Errorf("function decided not to work, and it never will") +} diff --git a/cli/exp.go b/cli/exp.go index 815d334256414..e190653f0f321 100644 --- a/cli/exp.go +++ b/cli/exp.go @@ -12,6 +12,7 @@ func (r *RootCmd) expCmd() *clibase.Cmd { Hidden: true, Children: []*clibase.Cmd{ r.scaletestCmd(), + r.errorExample(), }, } return cmd From 7bf961af9199e0ed977a9b69335659c18254b1f8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 14:43:28 -0500 Subject: [PATCH 2/9] chore: add verbose error printing to cli --- cli/errors.go | 34 +++++---- cli/root.go | 178 +++++++++++++++++++++++++++++++++------------ codersdk/client.go | 10 +++ 3 files changed, 163 insertions(+), 59 deletions(-) diff --git a/cli/errors.go b/cli/errors.go index eb10b701c9391..11af492c3d7a2 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -2,6 +2,8 @@ package cli import ( "io" + "net/http" + "net/http/httptest" "os" "github.com/coder/coder/v2/cli/clibase" @@ -18,6 +20,22 @@ func (r *RootCmd) errorExample() *clibase.Cmd { }, } } + recorder := httptest.NewRecorder() + recorder.WriteHeader(http.StatusBadRequest) + resp := recorder.Result() + resp.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) + apiError := codersdk.ReadBodyAsError(resp) + apiError.(*codersdk.Error).Response = codersdk.Response{ + Message: "Top level sdk error message.", + Detail: "magic dust unavailable, please try again later", + Validations: []codersdk.ValidationError{ + { + Field: "region", + Detail: "magic dust is not available in your region", + }, + }, + } + apiError.(*codersdk.Error).Helper = "Have you tried turning it off and on again?" cmd := &clibase.Cmd{ Use: "example-error", @@ -29,20 +47,8 @@ func (r *RootCmd) errorExample() *clibase.Cmd { return inv.Command.HelpHandler(inv) }, Children: []*clibase.Cmd{ - // Typical codersdk error - errorCmd("sdk", &codersdk.Error{ - Response: codersdk.Response{ - Message: "Top level sdk error message.", - Detail: "magic dust unavailable, please try again later", - Validations: []codersdk.ValidationError{ - { - Field: "region", - Detail: "magic dust is not available in your region", - }, - }, - }, - Helper: "Have you tried turning it off and on again?", - }), + // Typical codersdk api error + errorCmd("api", apiError), // Typical cli error errorCmd("cmd", xerrors.Errorf("some error: %w", errorWithStackTrace())), diff --git a/cli/root.go b/cli/root.go index 1530318e13144..b903edd75250e 100644 --- a/cli/root.go +++ b/cli/root.go @@ -131,14 +131,13 @@ func (r *RootCmd) RunMain(subcommands []*clibase.Cmd) { if err != nil { panic(err) } - err = cmd.Invoke().WithOS().Run() if err != nil { if errors.Is(err, cliui.Canceled) { //nolint:revive os.Exit(1) } - f := prettyErrorFormatter{w: os.Stderr} + f := prettyErrorFormatter{w: os.Stderr, verbose: r.verbose} f.format(err) //nolint:revive os.Exit(1) @@ -951,67 +950,156 @@ func isConnectionError(err error) bool { type prettyErrorFormatter struct { w io.Writer + // verbose turns on more detailed error logs, such as stack traces. + verbose bool } +// format formats the error to the console. This error should be human +// readable. func (p *prettyErrorFormatter) format(err error) { - errTail := errors.Unwrap(err) + output := cliHumanFormatError(err, &formatOpts{ + Verbose: p.verbose, + }) + // always trail with a newline + _, _ = p.w.Write([]byte(output + "\n")) +} - //nolint:errorlint - if _, ok := err.(*clibase.RunCommandError); ok && errTail != nil { - // Avoid extra nesting. - p.format(errTail) - return +func (p *prettyErrorFormatter) printf(style lipgloss.Style, format string, a ...interface{}) { + s := style.Render(fmt.Sprintf(format, a...)) + _, _ = p.w.Write( + []byte( + s, + ), + ) +} + +type formatOpts struct { + Verbose bool +} + +const indent = " " + +// cliHumanFormatError formats an error for the CLI. Newlines and styling are +// included. +func cliHumanFormatError(err error, opts *formatOpts) string { + if opts == nil { + opts = &formatOpts{} } - var headErr string - if errTail != nil { - headErr = strings.TrimSuffix(err.Error(), ": "+errTail.Error()) - } else { - headErr = err.Error() + if multi, ok := err.(interface{ Unwrap() []error }); ok { + multiErrors := multi.Unwrap() + if len(multiErrors) == 1 { + // Format as a single error + return cliHumanFormatError(multiErrors[0], opts) + } + return formatMultiError(multiErrors, opts) } - var msg string + // First check for sentinel errors that we want to handle specially. + // Order does matter! We want to check for the most specific errors first. var sdkError *codersdk.Error if errors.As(err, &sdkError) { - // We don't want to repeat the same error message twice, so we - // only show the SDK error on the top of the stack. - msg = sdkError.Message - if sdkError.Helper != "" { - msg = msg + "\n" + sdkError.Helper - } else if sdkError.Detail != "" { - msg = msg + "\n" + sdkError.Detail + return formatCoderSDKError(sdkError, opts) + } + + var cmdErr *clibase.RunCommandError + if errors.As(err, &cmdErr) { + return formatRunCommandError(cmdErr, opts) + } + + // Default just printing the error. Use +v for verbose to handle stack + // traces of xerrors. + if opts.Verbose { + return headLineStyle().Render(fmt.Sprintf("%+v", err)) + } + + return headLineStyle().Render(fmt.Sprintf("%v", err)) +} + +// formatMultiError formats a multi-error. It formats it as a list of errors. +// +// Multiple Errors: +// <# errors encountered>: +// 1. +// +// 2. +// +func formatMultiError(multi []error, opts *formatOpts) string { + var errorStrings []string + for _, err := range multi { + errorStrings = append(errorStrings, cliHumanFormatError(err, opts)) + } + + // Write errors out + var str strings.Builder + str.WriteString(headLineStyle().Render(fmt.Sprintf("%d errors encountered:", len(multi)))) + for i, errStr := range errorStrings { + // Indent each error + errStr = strings.ReplaceAll(errStr, "\n", "\n"+indent) + // Error now looks like + // | + // | + var prefix = fmt.Sprintf("%d. ", i+1) + if len(prefix) < len(indent) { + // Indent the prefix to match the indent + prefix = prefix + strings.Repeat(" ", len(indent)-len(prefix)) } - // The SDK error is usually good enough, and we don't want to overwhelm - // the user with output. - errTail = nil - } else { - msg = headErr + errStr = prefix + errStr + // Now looks like + // |1. + // | + str.WriteString("\n" + errStr) } + return str.String() +} - headStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#D16644")) - p.printf( - headStyle, - "%s", - msg, - ) +// formatRunCommandError are cli command errors. This kind of error is very +// broad, as it contains all errors that occur when running a command. +// If you know the error is something else, like a codersdk.Error, make a new +// formatter and add it to cliHumanFormatError function. +func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) string { + var str strings.Builder + str.WriteString(headLineStyle().Render(fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName()))) + + msgString := fmt.Sprintf("%v", err.Err) + if opts.Verbose { + // '%+v' includes stack traces + msgString = fmt.Sprintf("%+v", err.Err) + } + str.WriteString("\n") + str.WriteString(tailLineStyle().Render(msgString)) + return str.String() +} - tailStyle := headStyle.Copy().Foreground(lipgloss.Color("#969696")) +// formatCoderSDKError come from API requests. In verbose mode, add the +// request debug information. +func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string { + var str strings.Builder + if opts.Verbose { + str.WriteString(headLineStyle().Render(fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode()))) + str.WriteString("\n") + } - if errTail != nil { - p.printf(headStyle, ": ") - // Grey out the less important, deep errors. - p.printf(tailStyle, "%s", errTail.Error()) + str.WriteString(headLineStyle().Render(err.Message)) + if err.Helper != "" { + str.WriteString("\n") + str.WriteString(tailLineStyle().Render(err.Helper)) + } + // By default we do not show the Detail with the helper. + if opts.Verbose || (err.Helper == "" && err.Detail != "") { + str.WriteString("\n") + str.WriteString(tailLineStyle().Render(err.Detail)) } - p.printf(tailStyle, "\n") + return str.String() } -func (p *prettyErrorFormatter) printf(style lipgloss.Style, format string, a ...interface{}) { - s := style.Render(fmt.Sprintf(format, a...)) - _, _ = p.w.Write( - []byte( - s, - ), - ) +// These styles are arbitrary. +func headLineStyle() lipgloss.Style { + return lipgloss.NewStyle().Foreground(lipgloss.Color("#D16644")) +} + +func tailLineStyle() lipgloss.Style { + return headLineStyle().Copy().Foreground(lipgloss.Color("#969696")) } //nolint:unused diff --git a/codersdk/client.go b/codersdk/client.go index aa2f95bcc3822..965f2653d9e12 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -359,6 +359,8 @@ func ReadBodyAsError(res *http.Response) error { } return &Error{ statusCode: res.StatusCode, + method: requestMethod, + url: requestURL, Response: Response{ Message: fmt.Sprintf("unexpected non-JSON response %q", contentType), Detail: string(resp), @@ -414,6 +416,14 @@ func (e *Error) StatusCode() int { return e.statusCode } +func (e *Error) Method() string { + return e.method +} + +func (e *Error) URL() string { + return e.url +} + func (e *Error) Friendly() string { var sb strings.Builder _, _ = fmt.Fprintf(&sb, "%s. %s", strings.TrimSuffix(e.Message, "."), e.Helper) From c7efd8f4b6f43b9cc4328f7b28e8f6db7180958b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 14:51:54 -0500 Subject: [PATCH 3/9] Linting --- cli/errors.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/cli/errors.go b/cli/errors.go index 11af492c3d7a2..f5073007294b3 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -1,7 +1,6 @@ package cli import ( - "io" "net/http" "net/http/httptest" "os" @@ -11,7 +10,7 @@ import ( "golang.org/x/xerrors" ) -func (r *RootCmd) errorExample() *clibase.Cmd { +func (RootCmd) errorExample() *clibase.Cmd { errorCmd := func(use string, err error) *clibase.Cmd { return &clibase.Cmd{ Use: use, @@ -72,18 +71,6 @@ func (r *RootCmd) errorExample() *clibase.Cmd { return cmd } -type errorClose struct { - io.ReadCloser -} - -func (e errorClose) Close() error { - err := e.ReadCloser.Close() - if err == nil { - return xerrors.Errorf("always close error") - } - return err -} - func errorWithStackTrace() error { return xerrors.Errorf("function decided not to work, and it never will") } From 1af63f9c13d043974db1eb396bd4e505a442a00b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 15:06:37 -0500 Subject: [PATCH 4/9] Add validation errors --- cli/errors.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/cli/errors.go b/cli/errors.go index f5073007294b3..cfc071f1dcd80 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "net/http" "net/http/httptest" "os" @@ -19,6 +20,8 @@ func (RootCmd) errorExample() *clibase.Cmd { }, } } + + // Make an api error recorder := httptest.NewRecorder() recorder.WriteHeader(http.StatusBadRequest) resp := recorder.Result() @@ -36,6 +39,9 @@ func (RootCmd) errorExample() *clibase.Cmd { } apiError.(*codersdk.Error).Helper = "Have you tried turning it off and on again?" + // Some flags + var magicWord clibase.String + cmd := &clibase.Cmd{ Use: "example-error", Short: "Shows what different error messages look like", @@ -65,6 +71,26 @@ func (RootCmd) errorExample() *clibase.Cmd { return xerrors.Errorf("some error: %w", errorWithStackTrace()) }, }, + + { + Use: "validation", + Options: clibase.OptionSet{ + clibase.Option{ + Name: "magic-word", + Description: "Take a good guess.", + Required: true, + Flag: "magic-word", + Default: "", + Value: clibase.Validate(&magicWord, func(value *clibase.String) error { + return xerrors.Errorf("magic word is incorrect") + }), + }, + }, + Handler: func(i *clibase.Invocation) error { + _, _ = fmt.Fprint(i.Stdout, "Try setting the --magic-word flag\n") + return nil + }, + }, }, } From 828737d595a88618f134df53e5f07f0df6c64910 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 15:08:27 -0500 Subject: [PATCH 5/9] Linting --- cli/root.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cli/root.go b/cli/root.go index b903edd75250e..b50907c5075d9 100644 --- a/cli/root.go +++ b/cli/root.go @@ -1032,7 +1032,7 @@ func formatMultiError(multi []error, opts *formatOpts) string { // Write errors out var str strings.Builder - str.WriteString(headLineStyle().Render(fmt.Sprintf("%d errors encountered:", len(multi)))) + _, _ = str.WriteString(headLineStyle().Render(fmt.Sprintf("%d errors encountered:", len(multi)))) for i, errStr := range errorStrings { // Indent each error errStr = strings.ReplaceAll(errStr, "\n", "\n"+indent) @@ -1048,7 +1048,7 @@ func formatMultiError(multi []error, opts *formatOpts) string { // Now looks like // |1. // | - str.WriteString("\n" + errStr) + _, _ = str.WriteString("\n" + errStr) } return str.String() } @@ -1059,15 +1059,15 @@ func formatMultiError(multi []error, opts *formatOpts) string { // formatter and add it to cliHumanFormatError function. func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) string { var str strings.Builder - str.WriteString(headLineStyle().Render(fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName()))) + _, _ = str.WriteString(headLineStyle().Render(fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName()))) msgString := fmt.Sprintf("%v", err.Err) if opts.Verbose { // '%+v' includes stack traces msgString = fmt.Sprintf("%+v", err.Err) } - str.WriteString("\n") - str.WriteString(tailLineStyle().Render(msgString)) + _, _ = str.WriteString("\n") + _, _ = str.WriteString(tailLineStyle().Render(msgString)) return str.String() } @@ -1076,19 +1076,19 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string { var str strings.Builder if opts.Verbose { - str.WriteString(headLineStyle().Render(fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode()))) - str.WriteString("\n") + _, _ = str.WriteString(headLineStyle().Render(fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode()))) + _, _ = str.WriteString("\n") } - str.WriteString(headLineStyle().Render(err.Message)) + _, _ = str.WriteString(headLineStyle().Render(err.Message)) if err.Helper != "" { - str.WriteString("\n") - str.WriteString(tailLineStyle().Render(err.Helper)) + _, _ = str.WriteString("\n") + _, _ = str.WriteString(tailLineStyle().Render(err.Helper)) } // By default we do not show the Detail with the helper. if opts.Verbose || (err.Helper == "" && err.Detail != "") { - str.WriteString("\n") - str.WriteString(tailLineStyle().Render(err.Detail)) + _, _ = str.WriteString("\n") + _, _ = str.WriteString(tailLineStyle().Render(err.Detail)) } return str.String() } From 958e2a405c9bd71bf4ab1a2cdfbb3b88f5e1d180 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 15:14:23 -0500 Subject: [PATCH 6/9] Linting --- cli/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/root.go b/cli/root.go index b50907c5075d9..da9f3cd834492 100644 --- a/cli/root.go +++ b/cli/root.go @@ -1039,7 +1039,7 @@ func formatMultiError(multi []error, opts *formatOpts) string { // Error now looks like // | // | - var prefix = fmt.Sprintf("%d. ", i+1) + prefix := fmt.Sprintf("%d. ", i+1) if len(prefix) < len(indent) { // Indent the prefix to match the indent prefix = prefix + strings.Repeat(" ", len(indent)-len(prefix)) From ef67b3d9b18f6a07a504dd373edf5f6857f9ba2d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 15:48:12 -0500 Subject: [PATCH 7/9] remove lipgloss --- cli/errors.go | 3 +++ cli/root.go | 37 ++++++++++++++----------------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/cli/errors.go b/cli/errors.go index cfc071f1dcd80..3907fe9e30dd2 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -25,8 +25,10 @@ func (RootCmd) errorExample() *clibase.Cmd { recorder := httptest.NewRecorder() recorder.WriteHeader(http.StatusBadRequest) resp := recorder.Result() + _ = resp.Body.Close() resp.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) apiError := codersdk.ReadBodyAsError(resp) + //nolint:errorlint apiError.(*codersdk.Error).Response = codersdk.Response{ Message: "Top level sdk error message.", Detail: "magic dust unavailable, please try again later", @@ -37,6 +39,7 @@ func (RootCmd) errorExample() *clibase.Cmd { }, }, } + //nolint:errorlint apiError.(*codersdk.Error).Helper = "Have you tried turning it off and on again?" // Some flags diff --git a/cli/root.go b/cli/root.go index da9f3cd834492..454808d0e5b48 100644 --- a/cli/root.go +++ b/cli/root.go @@ -23,7 +23,6 @@ import ( "text/tabwriter" "time" - "github.com/charmbracelet/lipgloss" "github.com/mattn/go-isatty" "github.com/mitchellh/go-wordwrap" "golang.org/x/exp/slices" @@ -964,15 +963,6 @@ func (p *prettyErrorFormatter) format(err error) { _, _ = p.w.Write([]byte(output + "\n")) } -func (p *prettyErrorFormatter) printf(style lipgloss.Style, format string, a ...interface{}) { - s := style.Render(fmt.Sprintf(format, a...)) - _, _ = p.w.Write( - []byte( - s, - ), - ) -} - type formatOpts struct { Verbose bool } @@ -986,6 +976,7 @@ func cliHumanFormatError(err error, opts *formatOpts) string { opts = &formatOpts{} } + //nolint:errorlint if multi, ok := err.(interface{ Unwrap() []error }); ok { multiErrors := multi.Unwrap() if len(multiErrors) == 1 { @@ -1010,10 +1001,10 @@ func cliHumanFormatError(err error, opts *formatOpts) string { // Default just printing the error. Use +v for verbose to handle stack // traces of xerrors. if opts.Verbose { - return headLineStyle().Render(fmt.Sprintf("%+v", err)) + return pretty.Sprint(headLineStyle(), fmt.Sprintf("%+v", err)) } - return headLineStyle().Render(fmt.Sprintf("%v", err)) + return pretty.Sprint(headLineStyle(), fmt.Sprintf("%v", err)) } // formatMultiError formats a multi-error. It formats it as a list of errors. @@ -1032,7 +1023,7 @@ func formatMultiError(multi []error, opts *formatOpts) string { // Write errors out var str strings.Builder - _, _ = str.WriteString(headLineStyle().Render(fmt.Sprintf("%d errors encountered:", len(multi)))) + _, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered:", len(multi)))) for i, errStr := range errorStrings { // Indent each error errStr = strings.ReplaceAll(errStr, "\n", "\n"+indent) @@ -1059,7 +1050,7 @@ func formatMultiError(multi []error, opts *formatOpts) string { // formatter and add it to cliHumanFormatError function. func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) string { var str strings.Builder - _, _ = str.WriteString(headLineStyle().Render(fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName()))) + _, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName()))) msgString := fmt.Sprintf("%v", err.Err) if opts.Verbose { @@ -1067,7 +1058,7 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin msgString = fmt.Sprintf("%+v", err.Err) } _, _ = str.WriteString("\n") - _, _ = str.WriteString(tailLineStyle().Render(msgString)) + _, _ = str.WriteString(pretty.Sprint(tailLineStyle(), msgString)) return str.String() } @@ -1076,30 +1067,30 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string { var str strings.Builder if opts.Verbose { - _, _ = str.WriteString(headLineStyle().Render(fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode()))) + _, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode()))) _, _ = str.WriteString("\n") } - _, _ = str.WriteString(headLineStyle().Render(err.Message)) + _, _ = str.WriteString(pretty.Sprint(headLineStyle(), err.Message)) if err.Helper != "" { _, _ = str.WriteString("\n") - _, _ = str.WriteString(tailLineStyle().Render(err.Helper)) + _, _ = str.WriteString(pretty.Sprint(tailLineStyle(), err.Helper)) } // By default we do not show the Detail with the helper. if opts.Verbose || (err.Helper == "" && err.Detail != "") { _, _ = str.WriteString("\n") - _, _ = str.WriteString(tailLineStyle().Render(err.Detail)) + _, _ = str.WriteString(pretty.Sprint(tailLineStyle(), err.Detail)) } return str.String() } // These styles are arbitrary. -func headLineStyle() lipgloss.Style { - return lipgloss.NewStyle().Foreground(lipgloss.Color("#D16644")) +func headLineStyle() pretty.Style { + return cliui.DefaultStyles.Error } -func tailLineStyle() lipgloss.Style { - return headLineStyle().Copy().Foreground(lipgloss.Color("#969696")) +func tailLineStyle() pretty.Style { + return pretty.Style{pretty.Nop} } //nolint:unused From 42536159419086edaded4617335a841a38385a97 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 15:51:15 -0500 Subject: [PATCH 8/9] Lipgloss is now indirect --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 119c24090b53d..0c56c0689e233 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( github.com/charmbracelet/glamour v0.6.0 // In later at least v0.7.1, lipgloss changes its terminal detection // which breaks most of our CLI golden files tests. - github.com/charmbracelet/lipgloss v0.8.0 + github.com/charmbracelet/lipgloss v0.8.0 // indirect github.com/cli/safeexec v1.0.1 github.com/codeclysm/extract/v3 v3.1.1 github.com/coder/flog v1.1.0 From b28c0cc84ecb117a3afed5664b5e5ad606df348c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 29 Sep 2023 16:14:07 -0500 Subject: [PATCH 9/9] Linting --- cli/errors.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/errors.go b/cli/errors.go index 3907fe9e30dd2..6f873f06f8045 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -28,7 +28,7 @@ func (RootCmd) errorExample() *clibase.Cmd { _ = resp.Body.Close() resp.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) apiError := codersdk.ReadBodyAsError(resp) - //nolint:errorlint + //nolint:errorlint,forcetypeassert apiError.(*codersdk.Error).Response = codersdk.Response{ Message: "Top level sdk error message.", Detail: "magic dust unavailable, please try again later", @@ -39,7 +39,7 @@ func (RootCmd) errorExample() *clibase.Cmd { }, }, } - //nolint:errorlint + //nolint:errorlint,forcetypeassert apiError.(*codersdk.Error).Helper = "Have you tried turning it off and on again?" // Some flags