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

POC: custom error handlers #49

Merged
merged 1 commit into from
Jan 11, 2017

Conversation

nicksrandall
Copy link
Member

This proof of concept will likely need changes before it can be merged but I hope it can serve as a starting point for a conversation around how we can elegantly handle errors in this library.

This change gives users an optional hook to handle errors. Access to this hook opens up the door for several use cases:

  • Send more information rich errors to the client (related to Ability to return errors with extra fields #37) like sending error codes or other data related to error.
  • Single place to filter out "non-user-safe" errors from being sent to the client.
  • Integrate/send errors created in my resolves or graphql to a logging/monitoring service.

One benefit of this approach is that it is non-breaking. Users are able to "opt-in" when needed.

@nicksrandall
Copy link
Member Author

@jargv What are you're thoughts on this?

@neelance
Copy link
Collaborator

Schema.Exec returns the list of errors in the Errors field of its Response value. There is the chance to modify them before returning them to the client. Maybe the QueryError struct should have an additional ResolverError error field that contains the original error so you can post-process.

For adding custom fields to the errors I'd suggest creating a new error type that has QueryError embedded and a new Response type that allows those custom errors to be encoded as JSON. This should only require a few lines of code and seems like the most lightweight solution to me.

What do you think?

@nicksrandall
Copy link
Member Author

@neelance I've made that change. What do you think?

@neelance
Copy link
Collaborator

Yes, that's the direction I wanted to go. But I think we should only set ResolverError to errors actually returned by a resolver. Otherwise it can be nil. No need to always wrap an error.

@neelance
Copy link
Collaborator

So I wouldn't even change the signature of Errorf but simply set the ResolverError field in the single instance where we call the resolver.

@nicksrandall nicksrandall force-pushed the custom-error-handler branch 2 times, most recently from 3dd6c84 to 24b2045 Compare January 11, 2017 17:14
@nicksrandall
Copy link
Member Author

Ok, I made that change. What do you think? Thanks again for your help!

r.addError(errors.Errorf("%s", err))
queryError := errors.Errorf("%s", err)
queryError.ResolverError = err
r.addError(queryError)
addResult(f.Alias, nil) // TODO handle non-nil
Copy link
Member Author

Choose a reason for hiding this comment

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

This may be a discussion for another thread but shouldn't this be addResult(f.Alias, result) if the handler was able to resolve a partial amount of data, shouldn't we still send that to client and allow them to figure out what to do with it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The spec says that null should be returned on error: http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability

Actually the null should even propagate to the parent if the current field has a non-null type. This is not yet implemented.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting. Ok! Thanks!

@@ -554,7 +557,9 @@ func (e *fieldExec) execField(ctx context.Context, r *request, f *query.Field, r
result, err := e.execField2(spanCtx, r, f, resolver, span)

if err != nil {
r.addError(errors.Errorf("%s", err))
queryError := errors.Errorf("%s", err)
queryError.ResolverError = err
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice and simple. ❤️

@@ -303,6 +303,9 @@ func (r *request) addError(err *errors.QueryError) {
func (r *request) handlePanic() {
if err := recover(); err != nil {
execErr := errors.Errorf("graphql: panic occured: %v", err)
if err, ok := err.(error); ok {
execErr.ResolverError = err
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, is there a good scenario on why we should set the field on a panic? A panic should always be an unexpected internal error, so there should be nothing to tell the user except "internal error".

Right now the error message includes the panic's error value, but eventually I want to change it to only say "internal error" and not leak internals. However right now I don't want to do that yet because there are still some graphql-go error conditions that cause a panic and they would be hard to understand if the message was omitted.

Copy link
Member Author

Choose a reason for hiding this comment

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

My thought here is that this could help your use case of not leaking internals. The error message could say something like "internal error" but more information (not available to client) could be included on the ResolverError (maybe thats a bad name) which won't get sent to client because it wont be serialized to json.

Also, if something panics in a resolver, won't it eventually be caught by this func? I don't have a use case for this yet but I could see myself wanting to have access to the originating error?

I don't feel very strongly about this so if you'd like me to remove it then I will.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want access to the originating error to extract information from it, then the error is not unexpected and thus it does not qualify for a panic, right? ;-)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thats true.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed.

@nicksrandall
Copy link
Member Author

I removed changes from the panic Handler. Are we good to merge now?

@neelance
Copy link
Collaborator

Yes, we are!

@neelance neelance merged commit eccedde into graph-gophers:master Jan 11, 2017
@neelance
Copy link
Collaborator

@nicksrandall Thanks for contributing!

@F21
Copy link

F21 commented May 2, 2017

Is there an example for using this?

@felixfbecker
Copy link

Is there an example of how to use this?

@blasterpistol
Copy link

we should write some docs 📝

@felixfbecker
Copy link

@DenisNeustroev in lieu of docs, do you have a minimal example? 🙇

@jpascal
Copy link

jpascal commented May 5, 2018

@DenisNeustroev I explored all code and I can't find way to provide custom errors to client. 😕

@sanae10001
Copy link
Contributor

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.

7 participants