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

Inspect non-error types to produce helpful error messages for failing resolvers #1600

Merged

Conversation

kommander
Copy link
Contributor

@kommander kommander commented Dec 9, 2018

This is a proposal to handle previously serialised error objects according to section 7.1.2 of the spec. The result of receiving an error as object, without being an instance of the Error type, is an error message of [object Object]. The serialisation problem can be observed when working with remote executable schemas in Apollo GraphQL Tools, when throwing an Error in a remote resolver, it bubbles up as deserialised GraphQL Error according to the spec, but is not an Error instance.

A local resolver can also accidentally throw an Object/literal, which is valid JS, but and an error none-the-less.

try { throw { message:'woohaa!' }; } catch(e) { console.log(e, 'Is Error:', e instanceof Error); }
// > { message: 'woohaa!' } 'Is Error:' false

My take is that GraphQL should handle valid error Objects, as the spec states:

Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.

This may be fixed in graphql-tools, but I guess this is haunting other implementations as well. I'd event go as far as to deserialise the original stack trace.

I'll try to find a fix for remote executable schemas in apollograqphql/graphql-tools in the meantime, which could be a correct deserialisation in delegateToSchema.

EDIT: Heres a reproduction with apollo, resulting in an [object Object] error message in both cases https://github.com/kommander/graphql-bug

@facebook-github-bot
Copy link

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@Neitsch
Copy link

Neitsch commented Jan 6, 2019

Hey @kommander , thank you for the PR. I understand that it is valid JS to throw an arbitrary object. However in that case we should rather look into stringifying the object maybe? This will solve it more generally and not assume that message contains sufficient information and is present on the object.

@kommander
Copy link
Contributor Author

@Neitsch Thanks for getting back to me on this. Do you mean stringifying as in just JSON.stringify it if it's not an Error instance? That could work. Not a nice message, but at least something to debug on.

@kommander kommander force-pushed the fix-remote-result-error-handling branch from fa26adc to c5a139d Compare January 16, 2019 08:21
@kommander
Copy link
Contributor Author

@Neitsch I tried with JSON.stringify and doesn't look too bad. I think I found a good place to test cover it as well.

@Neitsch
Copy link

Neitsch commented Jan 18, 2019

@IvanGoncharov - do you have thoughts here? I don't have any particular investment in this, but can't hurt if it helps Apollo?

return error;
}
if (error instanceof Object) {
return new Error(JSON.stringify(error));
Copy link
Member

Choose a reason for hiding this comment

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

We have special inspect function for serializing objects to a string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for your input. What are you using to inspect ?

Copy link
Member

Choose a reason for hiding this comment

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

Function called inspect :)
It's already imported here:

import inspect from '../jsutils/inspect';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hahaha, thanks for pointing that out 👍

if (error instanceof Error) {
return error;
}
if (error instanceof Object) {
Copy link
Member

Choose a reason for hiding this comment

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

error instanceof Object doesn't handle some corner cases, e.g. Object.create(null)
better to user typeof error === 'object'

@IvanGoncharov
Copy link
Member

In general you shouldn't throw non-Error.

The serialisation problem can be observed when working with remote executable schemas in Apollo GraphQL Tools, when throwing an Error in a remote resolver, it bubbles up as deserialised GraphQL Error according to the spec, but is not an Error instance.

Note you can return instanceof Error from your resolvers or you can embedded remote errors directly into rootValue. For example here is how I implemented it:
https://github.com/APIs-guru/graphql-faker/blob/master/src/proxy.ts#L81-L103

This may be fixed in graphql-tools, but I guess this is haunting other implementations as well. I'd event go as far as to deserialise the original stack trace.

It should be definetly fixed in graphql-tools since you will loose a lot of info. For example error extensions.

My take is that GraphQL should handle valid error Objects, as the spec states:

Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.

This is citation from Section 7.1 Response Format so it's describe how error should be reported back to a client so it's not related to how resolver errors catched.
For that please see this section:
https://facebook.github.io/graphql/June2018/#sec-Errors-and-Non-Nullability


Even though I think it's incorrect behaviour to throw non-Error objects we should definetly handle it I propose to do the following:

function asErrorInstance(error: mixed): Error {
  if (error instanceof Error) {
    return error;
  }
  return new Error('Unexpected error value: ' + inspect(error));
}

@kommander
Copy link
Contributor Author

kommander commented Jan 18, 2019

In general you shouldn't throw non-Error.

Completely agree.

It should be definetly fixed in graphql-tools since you will loose a lot of info. For example error extensions.

Most definitely agreed as well.

I think this might have uncovered something else... resolveType reject with a valid Error comes through as string.

EDIT: I pushed the broken test so you can see whats happening. I couldn't find a lead right away.

@@ -393,6 +397,10 @@ describe('Execute: Handles basic execution tasks', () => {
// eslint-disable-next-line no-throw-literal
throw 'Error getting syncRawError';
},
syncObjectError() {
Copy link
Member

Choose a reason for hiding this comment

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

Since we don't distinguish between different types of non-Errors I don't think we need to add this test.

@kommander
Copy link
Contributor Author

I think I found the mistake, the test itself was rejecting with a non-error... fixing.

@IvanGoncharov IvanGoncharov added the PR: bug fix 🐞 requires increase of "patch" version number label Jan 18, 2019
locations: [{ line: 10, column: 9 }],
path: ['asyncRawReject'],
},
{
message: '',
message: 'Unexpected error value: undefined',
Copy link
Member

Choose a reason for hiding this comment

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

During troubleshooting, I think it would be really helpful to receive this error instead of the empty string 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Yes! Just had a situation today again where the error was just empty... debugging something like that is hell.

@kommander
Copy link
Contributor Author

I think travis had a problem with coverage reports. Tests are running fine. Maybe someone can retrigger it.

@IvanGoncharov
Copy link
Member

I think travis had a problem with coverage reports. Tests are running fine. Maybe someone can retrigger it.

It's my fault I broke the coveralls 😢

@IvanGoncharov
Copy link
Member

IvanGoncharov commented Jan 18, 2019

@kommander I think it's ready to merge.
One last issue is with PR title:

Handle previously serialized error in field resolver

I want to squash and merge this PR into a single commit but can't come up with a good commit message. Do you have any suggestions?

@IvanGoncharov
Copy link
Member

@kommander Something that describes PR content instead of original intention behind it.

@kommander
Copy link
Contributor Author

"Inspect non-error types to produce helpful error messages for failing resolvers (even though you should not throw literals, ever)"? Maybe drop the last part.

@IvanGoncharov IvanGoncharov changed the title Handle previously serialized error in field resolver Inspect non-error types to produce helpful error messages for failing resolvers Jan 18, 2019
@IvanGoncharov IvanGoncharov merged commit 5b17d41 into graphql:master Jan 18, 2019
@kommander
Copy link
Contributor Author

@IvanGoncharov Thanks, may Dijkstra be with you!

@IvanGoncharov
Copy link
Member

@kommander Thanks for taking the time to submit PR and sorry for the long review.

@KrisSiegel
Copy link

KrisSiegel commented Mar 25, 2019

Hey @IvanGoncharov, do you know when this code change will be released? I tested it in the npm branch and this fixes an issue I am currently having so I would love to be able to pull it from npm versus github :)

@IvanGoncharov
Copy link
Member

@KrisSiegel I'm working on it ⏳
As a temporary solution, you can try to use NPM package built from master:
https://github.com/graphql/graphql-js#want-to-ride-the-bleeding-edge

@KrisSiegel
Copy link

Thanks. Yeah, I'm currently doing that but targeting a specific commit hash so my build doesn't change each time CI kicks off :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed PR: bug fix 🐞 requires increase of "patch" version number
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants