Skip to content

Commit cea8a25

Browse files
committed
[validation] Add "bail" option to allow for bailing early when validating a document
1 parent 5e986eb commit cea8a25

File tree

3 files changed

+55
-5
lines changed

3 files changed

+55
-5
lines changed

src/validation/ValidationContext.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ type VariableUsage = {|
3434
+defaultValue: ?mixed,
3535
|};
3636

37+
export class ValidationBailEarlyError extends Error {}
38+
3739
/**
3840
* An instance of this class is passed as the "this" context to all validators,
3941
* allowing access to commonly useful contextual information from within a
4042
* validation rule.
4143
*/
4244
export class ASTValidationContext {
4345
_ast: DocumentNode;
46+
_bail: boolean;
4447
_errors: Array<GraphQLError>;
4548
_fragments: ?ObjMap<FragmentDefinitionNode>;
4649
_fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
@@ -49,8 +52,9 @@ export class ASTValidationContext {
4952
$ReadOnlyArray<FragmentDefinitionNode>,
5053
>;
5154

52-
constructor(ast: DocumentNode): void {
55+
constructor(ast: DocumentNode, bail: boolean = false): void {
5356
this._ast = ast;
57+
this._bail = bail;
5458
this._errors = [];
5559
this._fragments = undefined;
5660
this._fragmentSpreads = new Map();
@@ -59,6 +63,9 @@ export class ASTValidationContext {
5963

6064
reportError(error: GraphQLError): void {
6165
this._errors.push(error);
66+
if (this._bail) {
67+
throw new ValidationBailEarlyError();
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+
bail: boolean = false,
168176
): void {
169-
super(ast);
177+
super(ast, bail);
170178
this._schema = schema;
171179
this._typeInfo = typeInfo;
172180
this._variableUsages = new Map();

src/validation/__tests__/validation-test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,36 @@ describe('Validate: Supports full validation', () => {
7474
'Cannot query field "isHousetrained" on type "Dog". Did you mean "isHousetrained"?',
7575
]);
7676
});
77+
78+
it('exits validation early when bail option is set', () => {
79+
const doc = parse(`
80+
query {
81+
cat {
82+
name
83+
someNonExistentField
84+
}
85+
dog {
86+
name
87+
anotherNonExistentField
88+
}
89+
}
90+
`);
91+
92+
// Ensure that the number of errors without the bail option is 2
93+
const errors = validate(testSchema, doc, specifiedRules);
94+
expect(errors).to.be.length(2);
95+
96+
const bailedErrors = validate(
97+
testSchema,
98+
doc,
99+
specifiedRules,
100+
undefined,
101+
true,
102+
);
103+
expect(bailedErrors).to.be.length(1);
104+
const errorMessages = errors.map(err => err.message);
105+
expect(errorMessages[0]).to.equal(
106+
'Cannot query field "someNonExistentField" on type "Cat".',
107+
);
108+
});
77109
});

src/validation/validate.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
type ValidationRule,
1919
SDLValidationContext,
2020
ValidationContext,
21+
ValidationBailEarlyError,
2122
} from './ValidationContext';
2223

2324
/**
@@ -41,17 +42,26 @@ export function validate(
4142
documentAST: DocumentNode,
4243
rules?: $ReadOnlyArray<ValidationRule> = specifiedRules,
4344
typeInfo?: TypeInfo = new TypeInfo(schema),
45+
bail?: boolean = false,
4446
): $ReadOnlyArray<GraphQLError> {
4547
devAssert(documentAST, 'Must provide document');
4648
// If the schema used for validation is invalid, throw an error.
4749
assertValidSchema(schema);
4850

49-
const context = new ValidationContext(schema, documentAST, typeInfo);
51+
const context = new ValidationContext(schema, documentAST, typeInfo, bail);
5052
// This uses a specialized visitor which runs multiple visitors in parallel,
5153
// while maintaining the visitor skip and break API.
5254
const visitor = visitInParallel(rules.map(rule => rule(context)));
53-
// Visit the whole document with each instance of all provided rules.
54-
visit(documentAST, visitWithTypeInfo(typeInfo, visitor));
55+
try {
56+
// Visit the whole document with each instance of all provided rules.
57+
visit(documentAST, visitWithTypeInfo(typeInfo, visitor));
58+
} catch (e) {
59+
// If the caught error is not a `ValidationBailEarlyError`, rethrow as the
60+
// error is fatal
61+
if (!(e instanceof ValidationBailEarlyError)) {
62+
throw e;
63+
}
64+
}
5565
return context.getErrors();
5666
}
5767

0 commit comments

Comments
 (0)