-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Allow buildASTSchema to throw errors with source locations. #722
Conversation
fa6ee06
to
f5d48d7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tcr I think this is great. Lee and I just chatted about having a general util function for handling type errors with source location, used in extending/building the schema - and I think this is very close to (or exactly) what we need.
src/error/syntaxError.js
Outdated
@@ -33,6 +34,25 @@ export function syntaxError( | |||
} | |||
|
|||
/** | |||
* Produces a string for the invarant(...) function that renders the location |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can even refactor these functions to be more generic:
export function formatError(errorType: string) { ... }
export function syntaxError() { return formatError('syntax'); }
export function validationError() { return formatError('validation'); }
Not exactly like that, but you get the gist
src/error/syntaxError.js
Outdated
return message; | ||
} | ||
const location = getLocation(source, position); | ||
return `${message} (${location.line}:${location.column}) ` + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can actually return new GraphQLError(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asiandrummer This prints strange output from the invariant
function, which inlined would be:
if (!condition) {
throw new Error(new GraphQLError(...));
}
But, I could change invariant(...)
to check first if its message argument is a string, and if it's actually an error, just rethrow. Then I could make validationError
return a GraphQLError
. Sensible?
@@ -518,7 +526,8 @@ export function getDescription(node: { loc?: Location }): ?string { | |||
* document. | |||
*/ | |||
export function buildSchema(source: string | Source): GraphQLSchema { | |||
return buildASTSchema(parse(source)); | |||
const sourceObj = typeof source === 'string' ? new Source(source) : source; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to avoid recreating the Source object many times in invariantError, buildASTSchema now does so explicitly. But is this necessary?
Maybe we could construct a custom function globally to do this. I don't have a good answer for this as well, but yeah, passing an argument just for invariantError
(or whatever we'll end up with) seems a bit unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that ast nodes include a reference to the source they come from, so you shouldn't need to pass the second argument around
src/error/syntaxError.js
Outdated
* where an error occurred. If no source is passed in, it renders the message | ||
* without context. | ||
*/ | ||
export function invariantError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eh, maybe typeError/validationError?
d86f752
to
be5244f
Compare
Updated this PR:
The caveat still exists that buildASTSchema requires both "Source" and "ast" to return validation errors with full source body. |
@@ -77,6 +77,16 @@ export function GraphQLError( // eslint-disable-line no-redeclare | |||
path?: ?Array<string | number>, | |||
originalError?: ?Error | |||
) { | |||
// Define message so it can be captured in stack trace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I misunderstood, but any reason why this was moved to the top?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asiandrummer Ah, yeah this is the rebase over #718. I needed this to add the test case for the validation error—otherwise the message reports as just "GraphQLError" and we can't meaningfully test it.
src/error/syntaxError.js
Outdated
* returns an error containing the message, without context. | ||
*/ | ||
export function validationError( | ||
message: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, but should we order the argument similar to the functions above? That probably means to move source
to the top.
This looks good to me - maybe @leebyron should have another look at this |
if (!condition) { | ||
if (message instanceof Error) { | ||
throw message; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bit is unnecessary - no need for this change, then you can change the invariant
s below to just be if (!condition) { throw validationError() }
src/error/syntaxError.js
Outdated
* Produces a string for formatting a syntax or validation error with an | ||
* embedded location. | ||
*/ | ||
export function formatError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have another function called formatError
which does something slightly different.
How about printLocatedError
?
* location where a validation error occurred. If no source is passed in, it | ||
* returns an error containing the message, without context. | ||
*/ | ||
export function validationError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used from buildASTSchema, so perhaps it should live there? - Also perhaps this should be "Schema Error" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, in the rebased diff it is now used in buildASTSchema.js and definition.js.
Sorry for not seeing this sooner! This is great! There is also some discussion in #746 which might lead to even more coverage of schema validation improvements like this one. Do you mind rebasing? |
Rebased—let me know if anything got lost in the transition. |
@tcr was your intention to replace all other cases of graphql-js/src/utilities/buildASTSchema.js Lines 153 to 159 in 37717b8
For example, in the above snippet it would be great that the error that is thrown includes the location of both schema definition AST nodes. This would allow tools (like schema linters) to surface this information to the user. At the moment this isn't possible because a generic Happy to help if you don't have time to ship this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really want to have this, but perhaps we could make this more generic to apply to all schema, not just those created in buildASTSchema?
validationError( | ||
source, | ||
typeNode, | ||
`Expected ${String(type)} to be a GraphQL input type.`)); | ||
return type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These methods are used throughout and not only in validation, so instead anywhere doing schema validation should be using isInputType
and throwing the appropriate error instead of this general purpose function changing
@@ -518,7 +526,8 @@ export function getDescription(node: { loc?: Location }): ?string { | |||
* document. | |||
*/ | |||
export function buildSchema(source: string | Source): GraphQLSchema { | |||
return buildASTSchema(parse(source)); | |||
const sourceObj = typeof source === 'string' ? new Source(source) : source; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that ast nodes include a reference to the source they come from, so you shouldn't need to pass the second argument around
Lifted from / inspired by a similar change in #722, this creates a new function `printError()` (and uses it as the implementation for `GraphQLError#toString()`) which prints location information in the context of an error. This is moved from the syntax error where it used to be hard-coded, so it may now be used to format validation errors, value coercion errors, or any other error which may be associated with a location.
Lifted from / inspired by a similar change in #722, this creates a new function `printError()` (and uses it as the implementation for `GraphQLError#toString()`) which prints location information in the context of an error. This is moved from the syntax error where it used to be hard-coded, so it may now be used to format validation errors, value coercion errors, or any other error which may be associated with a location.
Lifted from / inspired by a similar change in #722, this creates a new function `printError()` (and uses it as the implementation for `GraphQLError#toString()`) which prints location information in the context of an error. This is moved from the syntax error where it used to be hard-coded, so it may now be used to format validation errors, value coercion errors, or any other error which may be associated with a location.
See #546 for context. This patch is one possible implementation of having type errors in a schema list their source location. Given the following code:
Instead of this error:
It would throw this:
Caveats:
Source
object many times ininvariantError
,buildASTSchema
now does so explicitly. But is this necessary?syntaxError
module knowledge of the AST, though with a bit of work this implementation could pass in a position instead.invariantError
I chose here is pretty weak, there's probably a better pattern for wrappinginvariant(...)
we could use.syntaxError
to useinvariantError
?