-
Notifications
You must be signed in to change notification settings - Fork 2k
Allow buildASTSchema to throw errors with source locations. #722
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,29 @@ | |
import { getLocation } from '../language/location'; | ||
import type { Source } from '../language/source'; | ||
import { GraphQLError } from './GraphQLError'; | ||
import type { ASTNode } from '../language/ast'; | ||
|
||
/** | ||
* Produces a string for formatting a syntax or validation error with an | ||
* embedded location. | ||
*/ | ||
export function printLocatedError( | ||
source: Source, | ||
position: number, | ||
message: string, | ||
description?: string, | ||
): GraphQLError { | ||
const location = getLocation(source, position); | ||
const body = `${message} (${location.line}:${location.column})` + | ||
(description ? ' ' + description : '') + | ||
'\n\n' + highlightSourceAtLocation(source, location); | ||
return new GraphQLError( | ||
body, | ||
undefined, | ||
source, | ||
[ position ] | ||
); | ||
} | ||
|
||
/** | ||
* Produces a GraphQLError representing a syntax error, containing useful | ||
|
@@ -21,15 +44,29 @@ export function syntaxError( | |
position: number, | ||
description: string | ||
): GraphQLError { | ||
const location = getLocation(source, position); | ||
const error = new GraphQLError( | ||
`Syntax Error ${source.name} (${location.line}:${location.column}) ` + | ||
description + '\n\n' + highlightSourceAtLocation(source, location), | ||
undefined, | ||
return printLocatedError( | ||
source, | ||
[ position ] | ||
position, | ||
`Syntax Error ${source.name}`, | ||
description, | ||
); | ||
return error; | ||
} | ||
|
||
/** | ||
* Produces a GraphQLError for the invariant(...) function that renders the | ||
* 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 commentThe 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 commentThe 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. |
||
source: ?Source, | ||
node: ?ASTNode, | ||
message: string, | ||
): GraphQLError { | ||
const position = node ? (node.loc ? node.loc.start : null) : null; | ||
if (position == null || source == null) { | ||
return new GraphQLError(message); | ||
} | ||
return printLocatedError(source, position, `Validation Error: ${message}`); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,11 @@ | |
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
export default function invariant(condition: mixed, message: string) { | ||
export default function invariant(condition: mixed, message: string | Error) { | ||
if (!condition) { | ||
if (message instanceof Error) { | ||
throw message; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
throw new Error(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,10 @@ import type { | |
ValueNode, | ||
} from '../language/ast'; | ||
import type { GraphQLSchema } from './schema'; | ||
import { validationError } from '../error/syntaxError'; | ||
|
||
import type { Source } from '../language/source'; | ||
import type { ASTNode } from '../language/ast'; | ||
|
||
// Predicates & Assertions | ||
|
||
|
@@ -82,11 +85,16 @@ export function isInputType(type: ?GraphQLType): boolean %checks { | |
); | ||
} | ||
|
||
export function assertInputType(type: ?GraphQLType): GraphQLInputType { | ||
invariant( | ||
isInputType(type), | ||
`Expected ${String(type)} to be a GraphQL input type.` | ||
); | ||
export function assertInputType( | ||
type: ?GraphQLType, | ||
typeNode?: ASTNode, | ||
source?: Source | ||
): GraphQLInputType { | ||
invariant(isInputType(type), | ||
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 commentThe 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 |
||
} | ||
|
||
|
@@ -121,11 +129,16 @@ export function isOutputType(type: ?GraphQLType): boolean %checks { | |
); | ||
} | ||
|
||
export function assertOutputType(type: ?GraphQLType): GraphQLOutputType { | ||
invariant( | ||
isOutputType(type), | ||
`Expected ${String(type)} to be a GraphQL output type.`, | ||
); | ||
export function assertOutputType( | ||
type: ?GraphQLType, | ||
typeNode?: ASTNode, | ||
source?: Source | ||
): GraphQLOutputType { | ||
invariant(isOutputType(type), | ||
validationError( | ||
source, | ||
typeNode, | ||
`Expected ${String(type)} to be a GraphQL output type.`)); | ||
return type; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,8 +14,9 @@ import keyValMap from '../jsutils/keyValMap'; | |
import { valueFromAST } from './valueFromAST'; | ||
import { TokenKind } from '../language/lexer'; | ||
import { parse } from '../language/parser'; | ||
import type { Source } from '../language/source'; | ||
import { Source } from '../language/source'; | ||
import { getArgumentValues } from '../execution/values'; | ||
import { validationError } from '../error/syntaxError'; | ||
|
||
import { | ||
LIST_TYPE, | ||
|
@@ -135,7 +136,10 @@ function getNamedTypeNode(typeNode: TypeNode): NamedTypeNode { | |
* Given that AST it constructs a GraphQLSchema. The resulting schema | ||
* has no resolve methods, so execution will use default resolvers. | ||
*/ | ||
export function buildASTSchema(ast: DocumentNode): GraphQLSchema { | ||
export function buildASTSchema( | ||
ast: DocumentNode, | ||
source?: Source | ||
): GraphQLSchema { | ||
if (!ast || ast.kind !== DOCUMENT) { | ||
throw new Error('Must provide a document ast.'); | ||
} | ||
|
@@ -303,22 +307,24 @@ export function buildASTSchema(ast: DocumentNode): GraphQLSchema { | |
} | ||
|
||
function produceInputType(typeNode: TypeNode): GraphQLInputType { | ||
return assertInputType(produceType(typeNode)); | ||
return assertInputType(produceType(typeNode), typeNode, source); | ||
} | ||
|
||
function produceOutputType(typeNode: TypeNode): GraphQLOutputType { | ||
return assertOutputType(produceType(typeNode)); | ||
return assertOutputType(produceType(typeNode), typeNode, source); | ||
} | ||
|
||
function produceObjectType(typeNode: TypeNode): GraphQLObjectType { | ||
const type = produceType(typeNode); | ||
invariant(type instanceof GraphQLObjectType, 'Expected Object type.'); | ||
invariant(type instanceof GraphQLObjectType, | ||
validationError(source, typeNode, 'Expected Object type')); | ||
return type; | ||
} | ||
|
||
function produceInterfaceType(typeNode: TypeNode): GraphQLInterfaceType { | ||
const type = produceType(typeNode); | ||
invariant(type instanceof GraphQLInterfaceType, 'Expected Interface type.'); | ||
invariant(type instanceof GraphQLInterfaceType, | ||
validationError(source, typeNode, 'Expected Interface type')); | ||
return type; | ||
} | ||
|
||
|
@@ -524,7 +530,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 commentThe reason will be displayed to describe this comment to others. Learn more.
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
return buildASTSchema(parse(sourceObj), sourceObj); | ||
} | ||
|
||
// Count the number of spaces on the starting side of a 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.
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.