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

twirp.Erroer interface #320

Closed
wants to merge 2 commits into from
Closed

twirp.Erroer interface #320

wants to merge 2 commits into from

Conversation

marioizquierdo
Copy link
Contributor

Issue #300

Allow errors returned by service handlers to implement a different interface twirp.Erroer to be serialized as Twirp errors.

The interface doesn't need to be exported, but having it exported documents the alternative better and makes the option more explicit.

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

@cespare
Copy link
Contributor

cespare commented May 24, 2021

Thanks for sending this PR! I will review this today.

Copy link
Contributor

@cespare cespare left a comment

Choose a reason for hiding this comment

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

Logic looks good to me. The main change I'd like to see is renaming the interface before we lock it in.

It would be good to update the error documentation here as well: https://twitchtv.github.io/twirp/docs/errors.html.

// This may be useful if implementing all of the required methods for
// twirp.Error is too difficult or not possible.
// See https://github.com/twitchtv/twirp/issues/300 for reasoning.
type Erroer interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't it be Errorer or TwirpErrorer?

// as an alternative to implementing the Error interface.
// This may be useful if implementing all of the required methods for
// twirp.Error is too difficult or not possible.
// See https://github.com/twitchtv/twirp/issues/300 for reasoning.
Copy link
Contributor

Choose a reason for hiding this comment

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

To my mind, the key difference from the service author's perspective between this interface and Error is that the handler will attempt to unwrap an Erroer whereas the Error interface is only used if the returned handler error is an Error. I think that's the most important thing to explain.

For the reasons I described on the thread in #300, I don't think that callers should ever implement the Error interface themselves at all. So I don't think we should even mention that possibility here.

I also think that the documentation for something so fundamental should be self-contained rather than needing to refer to a whole other discussion.

Putting all that together, here's a rough suggestion of a doc comment:

// Erroer may be implemented by service errors in order to return specific
// Twirp errors from RPC handlers.
//
// If the error returned from an RPC handler is an Error, it is used as-is.
// If the error matches Erroer using errors.As, the result of calling TwirpError
// on the unwrapped Erroer is used.
// Otherwise, the error is wrapped using InternalErrorWith.

t.P(`// newTwirpError casts an error into the twirp.Error interface,`)
t.P(`// or calls .TwirpError() if it implements twirp.Erroer.`)
t.P(`// Otherwise it wraps the error as a twirp.Internal error.`)
t.P(`func newTwirpError(err error) `, t.pkgs["twirp"], `.Error {`)
Copy link
Contributor

Choose a reason for hiding this comment

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

newTwirpError sounds like it's a normal constructor function. Perhaps asTwirpError?

@@ -414,24 +416,37 @@ func (t *twirp) generateUtils() {
t.P(`}`)
t.P()

t.P(`// newTwirpError casts an error into the twirp.Error interface,`)
t.P(`// or calls .TwirpError() if it implements twirp.Erroer.`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested rephrasing of this sentence:

newTwirpError converts err to a twirp.Error, if possible, or else calls TwirpError if err wraps Erroer.

t.P(`// newTwirpError casts an error into the twirp.Error interface,`)
t.P(`// or calls .TwirpError() if it implements twirp.Erroer.`)
t.P(`// Otherwise it wraps the error as a twirp.Internal error.`)
t.P(`func newTwirpError(err error) `, t.pkgs["twirp"], `.Error {`)
Copy link
Contributor

Choose a reason for hiding this comment

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

As a side note, it's unfortunate that this helper function (along with many others) is duplicated in every generated twirp file. It'd be nice for generated code size (and binary size) to move those to a shared internal package (same as ctxsetters) at some point in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Twirp tries to keep as much functionality as possible on the generated file, by design. That allows to re-generate and upgrade to newer versions without having to worry about version incompatibilities, which may become complex when importing multiple clients that were generated at different times. The less dependencies between the generated code and any other package, the better. Code size is not too bad at this point.

t.P(` twerr = `, t.pkgs["twirp"], `.InternalErrorWith(err)`)
t.P(` }`)
t.P(``)
t.P(` // Cast to twirp.Error. Non-twirp errors are wrapped as Internal errors.`)
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 feels pretty unnecessary since it essentially repeats what the newTwirpError doc says and it bloats the generated code size.

@rhysh
Copy link
Contributor

rhysh commented May 27, 2021

It's not clear to me that this new interface is required, or how the patterns it enables are different or better than what errors.As provides. I gave a specific example in #300 (comment).

@marioizquierdo
Copy link
Contributor Author

closing in favor of #323 323

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