Skip to content

Commit 4edebb5

Browse files
committed
[validation] Add "onError" option to allow for custom error handling behavior when performing validation
1 parent 6adb527 commit 4edebb5

File tree

3 files changed

+54
-11
lines changed

3 files changed

+54
-11
lines changed

src/validation/ValidationContext.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ import { Kind } from '../language/kinds';
88
import { type ASTVisitor, visit, visitWithTypeInfo } from '../language/visitor';
99
import {
1010
type DocumentNode,
11+
type FragmentDefinitionNode,
12+
type FragmentSpreadNode,
1113
type OperationDefinitionNode,
12-
type VariableNode,
1314
type SelectionSetNode,
14-
type FragmentSpreadNode,
15-
type FragmentDefinitionNode,
15+
type VariableNode,
1616
} from '../language/ast';
1717

1818
import { type GraphQLSchema } from '../type/schema';
1919
import { type GraphQLDirective } from '../type/directives';
2020
import {
21-
type GraphQLInputType,
22-
type GraphQLOutputType,
21+
type GraphQLArgument,
2322
type GraphQLCompositeType,
2423
type GraphQLField,
25-
type GraphQLArgument,
24+
type GraphQLInputType,
25+
type GraphQLOutputType,
2626
} from '../type/definition';
2727

2828
import { TypeInfo } from '../utilities/TypeInfo';
@@ -41,6 +41,7 @@ type VariableUsage = {|
4141
*/
4242
export class ASTValidationContext {
4343
_ast: DocumentNode;
44+
_onError: ?(err: Error) => void = undefined;
4445
_errors: Array<GraphQLError>;
4546
_fragments: ?ObjMap<FragmentDefinitionNode>;
4647
_fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
@@ -49,16 +50,22 @@ export class ASTValidationContext {
4950
$ReadOnlyArray<FragmentDefinitionNode>,
5051
>;
5152

52-
constructor(ast: DocumentNode): void {
53+
constructor(ast: DocumentNode, onError?: (err: Error) => void): void {
5354
this._ast = ast;
5455
this._errors = [];
5556
this._fragments = undefined;
5657
this._fragmentSpreads = new Map();
5758
this._recursivelyReferencedFragments = new Map();
59+
if (onError) {
60+
this._onError = onError.bind(this);
61+
}
5862
}
5963

6064
reportError(error: GraphQLError): void {
6165
this._errors.push(error);
66+
if (this._onError) {
67+
this._onError(error);
68+
}
6269
}
6370

6471
getErrors(): $ReadOnlyArray<GraphQLError> {
@@ -165,8 +172,9 @@ export class ValidationContext extends ASTValidationContext {
165172
schema: GraphQLSchema,
166173
ast: DocumentNode,
167174
typeInfo: TypeInfo,
175+
onError?: (err: Error) => void,
168176
): void {
169-
super(ast);
177+
super(ast, onError);
170178
this._schema = schema;
171179
this._typeInfo = typeInfo;
172180
this._variableUsages = new Map();

src/validation/__tests__/validation-test.js

+25
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,29 @@ describe('Validate: Supports full validation', () => {
7474
'Cannot query field "isHousetrained" on type "Dog". Did you mean "isHousetrained"?',
7575
]);
7676
});
77+
78+
it('properly calls onError callback when passed', () => {
79+
const doc = parse(`
80+
query {
81+
cat {
82+
name
83+
someNonExistentField
84+
}
85+
dog {
86+
name
87+
anotherNonExistentField
88+
}
89+
}
90+
`);
91+
92+
const expectedNumberOfErrors = 2;
93+
let errorCount = 0;
94+
validate(testSchema, doc, specifiedRules, undefined, (err, ctx) => {
95+
expect(err).to.not.be.a('null');
96+
expect(ctx).to.not.be.a('null');
97+
expect(ctx.getErrors()).to.be.length(++errorCount);
98+
});
99+
100+
expect(errorCount).to.be.equal(expectedNumberOfErrors);
101+
});
77102
});

src/validation/validate.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import { TypeInfo } from '../utilities/TypeInfo';
1414

1515
import { specifiedRules, specifiedSDLRules } from './specifiedRules';
1616
import {
17-
type SDLValidationRule,
18-
type ValidationRule,
1917
SDLValidationContext,
18+
type SDLValidationRule,
2019
ValidationContext,
20+
type ValidationRule,
2121
} from './ValidationContext';
2222

2323
/**
@@ -41,12 +41,22 @@ export function validate(
4141
documentAST: DocumentNode,
4242
rules?: $ReadOnlyArray<ValidationRule> = specifiedRules,
4343
typeInfo?: TypeInfo = new TypeInfo(schema),
44+
onError?: (err: Error, ctx: ValidationContext) => void,
4445
): $ReadOnlyArray<GraphQLError> {
4546
devAssert(documentAST, 'Must provide document');
4647
// If the schema used for validation is invalid, throw an error.
4748
assertValidSchema(schema);
4849

49-
const context = new ValidationContext(schema, documentAST, typeInfo);
50+
const context = new ValidationContext(
51+
schema,
52+
documentAST,
53+
typeInfo,
54+
function onErrorWithContext(err) {
55+
if (onError) {
56+
onError(err, this);
57+
}
58+
},
59+
);
5060
// This uses a specialized visitor which runs multiple visitors in parallel,
5161
// while maintaining the visitor skip and break API.
5262
const visitor = visitInParallel(rules.map(rule => rule(context)));

0 commit comments

Comments
 (0)