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

Nexus handler error translation #1626

Merged
merged 3 commits into from
Sep 11, 2024

Conversation

bergundy
Copy link
Member

@bergundy bergundy commented Sep 9, 2024

What was changed

  • Handle serviceerrors, ApplicationErrors, and other expected client errors returned from Nexus handlers.
  • Also now exposing internal errors by default to match activity and child workflow behavior. We'll have to revisit hiding details from the caller when we start targeting cross org use cases.

Why?

Better out of the box developer experience.

  1. How was this tested:

Added integration tests.

// Temporal serviceerrors have a Status() method.
stGetter, ok := err.(interface{ Status() *status.Status })
if !ok {
// Not a serviceerror, passthrough.
Copy link
Contributor

Choose a reason for hiding this comment

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

What if the error is wrapping a service error?

Copy link
Member

Choose a reason for hiding this comment

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

I personally like only checking the outer one. I don't think we should treat errors the same just because they may have the error somewhere in their chain.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call, switched to errors.As instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I was debating what to do here but I think most users will probably wrap just to provide more context but will expect to maintain the semantics.

Copy link
Member Author

Choose a reason for hiding this comment

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

but internal is retryable right? Feels like it should be an UnsuccessfulOperationError?

Yes, internal is retryable. What do you mean by "it"?

Copy link
Contributor

Choose a reason for hiding this comment

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

"it" meaning the workflow error . If I have an operation handler that gets the result of a workflow and the workflow has failed wouldn't it make sense to not retry that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Decided to make it non retryable but I couldn't use UnsuccessfulOperationError because that's only usable in Start and GetResult. It's going to be a bad request for lack of better outcome.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also decided to not use errors.As and only consider errors directly returned by client methods.
If users wrap the errors, they should decide what error type they want.
Let's get user feedback on this and verify it matches their expectation before we remove the experimental label.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I would really like to get feedback from users here if this is helpful and behaves how they expect.

// Temporal serviceerrors have a Status() method.
stGetter, ok := err.(interface{ Status() *status.Status })
if !ok {
// Not a serviceerror, passthrough.
Copy link
Member

Choose a reason for hiding this comment

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

I personally like only checking the outer one. I don't think we should treat errors the same just because they may have the error somewhere in their chain.

// Roughly taken from https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
// and
// https://github.com/grpc-ecosystem/grpc-gateway/blob/a7cf811e6ffabeaddcfb4ff65602c12671ff326e/runtime/errors.go#L56.
func convertServiceError(err error, exposeDetails bool) error {
Copy link
Member

@cretz cretz Sep 9, 2024

Choose a reason for hiding this comment

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

Doesn't seem like there's much value in splitting convertServiceError and convertApplicationError, might as well just have convertError. Also doesn't seem like exposeDetails parameter has any value since it is never set to false on this private function. Can wait for the future need to hide details before adding that functionality, it has no value currently.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree there's not much value, for the split. Initially there was more logic in the application error conversion since I thought we may want to handle other TemporalErrors but I can merge.

I already know that we'll want to allow hiding details and had this logic in the server codebase (where I ported this status error conversion logic from) so I figured I'd keep it in here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Restructured slightly.

// flag.
func convertApplicationError(err error) error {
var appErr *ApplicationError
if errors.As(err, &appErr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Normally the Go SDK explicitly does does not check the error chain for ApplicationError

Copy link
Contributor

Choose a reason for hiding this comment

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

Same with the Server

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm... so yeah, we should be consistent here too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed this.


// Default to internal error. This should only happen for codes.OK, which is unexpected for serviceerrors.
if !exposeDetails {
return nexus.HandlerErrorf(nexus.HandlerErrorTypeInternal, "internal error")
Copy link
Member

Choose a reason for hiding this comment

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

Dead code. Can you wait until you need it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd rather keep this because I know I will need it later when we add the ability to control the level of details exposed from handlers (and callers).

Copy link
Contributor

Choose a reason for hiding this comment

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

I have no issue with the dead code, but It should be tested


// convertKnownErrors converts known errors to corresponding Nexus HandlerError.
func convertKnownErrors(err error) error {
// Handle common errors returned from various client methods.
Copy link
Contributor

Choose a reason for hiding this comment

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

So will each SDK need to decided what is a common error and how to map it ?

@bergundy bergundy merged commit 5f46ca8 into temporalio:master Sep 11, 2024
13 checks passed
@bergundy bergundy deleted the nexus-error-translation branch September 11, 2024 17:41
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.

4 participants