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

New Error Constructors: code.Error(msg) and code.Errorf(msg, a...) #339

Merged
merged 1 commit into from
Nov 15, 2021

Conversation

marioizquierdo
Copy link
Contributor

@marioizquierdo marioizquierdo commented Nov 9, 2021

Build Twirp Errors from the Code

The standard error constructor twirp.NewError(twirp.ErrorCode, msg) stutters and is a bit verbose.

Since the code constants are already defined at the top level of the twirp package, they can be used to build all the error types. It is easier to read and consistent for all codes:

twirp.Internal.Error("oops")
twirp.NotFound.Error("resource not found")
twirp.PermissionDenied.Error("thou shall no pass")

Errorf to format and wrap other errors

The signature Errorf is a well-known idiom in Go and could be used to format errors exactly as it would be expected:

twirp.Internal.Errorf("oops: %w", err) // wrapping another error
twirp.NotFound.Errorf("resource with id: %d", id) // interpolation
twirp.PermissionDenied.Errorf("user %q with id %d not allowed: %w", login, id, err) // all of the above

Wrapping errors with "%w" is specially useful to prefix internal errors. APIs that use middleware to report errors often need to unwrap those errors to know the originals, but having a prefix in front of them is useful for debugging. Example:

record, err := s.DB.LoadRecord(ctx, resourceID)
if err != nil {
    return nil, twirp.Internal.Errorf("failed to load resource %q: %w", resourceID, err)
}

Existing constructors enhanced

Existing constructors still apply and are consistent with the new ones. An Errorf version of those constructors was also added for consistency.

twirp.InternalError("oops")
twirp.InternalErrorf("oops: %w", err)
twirp.InternalErrorWith(err)

twirp.NotFoundError("resource not found")
twirp.NewError(twirp.NotFound, "resource not found")

twirp.InvalidArgumentError("arg", "is invalid")
twirp.InvalidArgument.Error("arg is invalid").WithMeta("argument", "arg")
twirp.NewError(twirp.InvalidArgument, "arg is invalid").WithMeta("argument", "arg")

Usage Example

Let's see how the new constructors look like on an example endpoint implementation:

func (s *MyService) MyMethod(ctx context.Context, req *mysvc.MyRequest) (*mysvc.MyResponse, error) {
  if req.UserId == "" {
    return nil, twirp.InvalidArgument.Error("user_id is required")
  }
  
  user, err := s.DB.LoadUser(ctx, req.UserId)
  if err != nil {
    return nil, twirp.Internal.Errorf("failed to load user %q: %w", req.UserId, err)
  }
  if user == nil {
    return nil, twirp.NotFound.Errorf("user %q not found", req.UserId)
  }

  if !auth.Check(ctx, req.UserId) {
    return nil, twirp.PermissionDenied.Error("authorization check")
  }

  return &mysvc.MyResponse{UserName: user.Name}, nil
}

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@marioizquierdo marioizquierdo changed the title Improved Error Constructors New Error Constructors: code.Error(msg) and code.Errorf(msg, a...) Nov 9, 2021
@wmatveyenko wmatveyenko merged commit 218743b into main Nov 15, 2021
@wmatveyenko
Copy link
Contributor

Thanks for the contribution.

@wmatveyenko wmatveyenko deleted the error_code_new_error branch November 15, 2021 22:23
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.

2 participants