Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User-facing errors in example runner #2741

Merged
merged 12 commits into from
Sep 15, 2023
Merged

User-facing errors in example runner #2741

merged 12 commits into from
Sep 15, 2023

Conversation

MDrakos
Copy link
Member

@MDrakos MDrakos commented Sep 1, 2023

StoryDX-2151 User facing errors for example runner

@github-actions github-actions bot changed the base branch from master to version/0-41-0-RC1 September 1, 2023 22:09
@MDrakos MDrakos marked this pull request as ready for review September 1, 2023 22:20
@MDrakos MDrakos requested a review from Naatan September 1, 2023 22:33
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
Comment on lines 72 to 99
func (h *Hello) Run(params *RunParams) error {
err := h.run(params)
if err != nil {
for _, unpackedError := range errs.Unpack(err) {
switch unpackedError.(type) {
case *runbits.NoNameProvidedError:
// Errors that we are looking for should be wrapped in a user-facing error.
// Ensure we wrap the top-level error returned from the runner and not
// the unpacked error that we are inspecting.
return &HelloUserFacingError{
locale.WrapInputError(
err,
"hello_err_no_name",
"Cannot say hello because no name was provided.",
),
}

default:
// If this is not a specific error we are looking for, do nothing.
continue
}
}

return locale.WrapError(err, "hello_cannot_say", "Cannot say hello.")
}

return nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the approach of having a Run calling another run, where the first one is basically a post-processor of the latter. That feels like an inverse of responsibilities in how you traverse the code when reading.

I think we should have a single Run function, and we defer the error "post processor". So at the start of the run function we'd have something like

func processError(err *error) {
	...
	*err = errs.WrapUserFacing(err, "You broke it!")
    ...
}

func (h *Hello) Run(params *RunParams) (rerr error) {
    defer processError(&rerr)
    ...
}

Copy link
Member Author

@MDrakos MDrakos Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the approach that is currently in the PR. While having a function that does post-processing + defer fits nicely in a mental model in practice I find that when reading the code it requires more jumping around, or at least remembering that you have to jump to the defer function after reading the runner logic. This isn't a problem with the current code as you read the Run function, then the function that it calls, and return to the caller.

Post-processing with a defer also requires a named return value which is different from the vast majority of the functions in our codebase and also adds another layer of complexity when reading the code.

This is all subjective and in the end, it's your call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern of the named return is not uncommon. It is used frequently in our codebase.

I'm open to other solutions, but between the current implementation and what I am suggesting I'd prefer my suggestion. Giving each runner a redundant Run() function that you have to interpret to understand does not feel like the better solution to me, but you're right, that is subjective. But, given this is meant as a reference for our codebase rather than a one-off piece of code I'm gonna be nitpicky and insist on either my solution or an alternate yet to be determined one.

internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
@@ -93,8 +93,6 @@ func ParseUserFacing(err error) (int, error) {
return 0, nil
}

_, hasMarshaller := err.(output.Marshaller)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is breaking the original intend of the function. Just way predated the new design and isn't looking to address the same use-case. What you effectively found here is that the term "User Facing" was already in use, and we need to either resolve the conflict of overlapping terms, or ensure that they are compatible.

Looking through the code it seems hasMarshaller will mainly identify whether we already have an OutputError on our hands, so we don't redundantly wrap it. While I don't see any scenario's in which this could be true, do we want to risk breaking this behaviour for this PR?

Please take some time to understand what OutputError{} is doing and how the new concept of user facing errors plays into this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can grok the OutputError type adds extra post-processing and the handling in this function ensures that errors returned all have a similar UX. I've wrapped the user-facing error in this OutputError type as well to ensure it's consistent with all of the other errors we are presenting to users.

I've also added back the hasMarshaller check as you were right, we don't want to wrap the error again in the OutputError type.

Adjust how we handle errors in example runner
Add back marshaller handling in user facing error parsing
@MDrakos MDrakos requested a review from Naatan September 7, 2023 22:37
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/runbits/hello_example.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
uerr, ok := errs.IsUserFacing(err)
if ok {
logging.Debug("Returning user facing error, error stack: \n%s", errs.JoinMessage(err))
return code, &OutputError{uerr}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think OutputError produces the error string from UserFacingError()?

Please make sure you run this code to ensure it produces the error message you expect.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does here: https://github.com/ActiveState/cli/blob/master/pkg/cmdlets/errors/errors.go. I've confirmed this from running the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand, you linked to code on master which definitely would not be handling this new concept. Wrong link?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, yes wrong link.

I've run the code and confirmed the output from UserError() is there. I believe it's done via the output mediator here: https://github.com/ActiveState/cli/blob/DX-2151/internal/output/mediator.go#L54

@MDrakos MDrakos requested a review from Naatan September 11, 2023 22:16
Copy link
Member

@Naatan Naatan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we try to do some pair programming tomorrow to resolve this PR faster.

internal/errs/centralized.go Outdated Show resolved Hide resolved
}

func (e *userFacingError) Error() string {
return JoinMessage(Wrap(e.wrapped, e.message))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return JoinMessage(Wrap(e.wrapped, e.message))
return "User Facing Error: " + e.UserError()

internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
internal/errs/errs.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
internal/runners/hello/hello_example.go Outdated Show resolved Hide resolved
pkg/cmdlets/errors/errors.go Show resolved Hide resolved
Update error tip handling
@MDrakos MDrakos requested a review from Naatan September 15, 2023 21:29
internal/errs/userfacing.go Outdated Show resolved Hide resolved
pkg/cmdlets/errors/errors.go Show resolved Hide resolved
@MDrakos MDrakos requested a review from Naatan September 15, 2023 22:15
pkg/cmdlets/errors/errors.go Outdated Show resolved Hide resolved
@MDrakos MDrakos requested a review from Naatan September 15, 2023 22:59
@MDrakos MDrakos merged commit ef72a8c into version/0-41-0-RC1 Sep 15, 2023
6 checks passed
@MDrakos MDrakos deleted the DX-2151 branch September 15, 2023 23:06
type NoNameProvidedError struct {
*errs.WrapperError
}

func SayHello(out output.Outputer, name string) error {
if name == "" {
// Errors that are due to USER input should use `NewInputError` or `WrapInputError`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is now incorrect and causing confusion @Naatan @MDrakos

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants