Skip to content

Commit

Permalink
[validation] Add "bail" option to allow for bailing early when valida…
Browse files Browse the repository at this point in the history
…ting a document
  • Loading branch information
skevy committed Aug 6, 2019
1 parent 5e986eb commit cea8a25
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 5 deletions.
12 changes: 10 additions & 2 deletions src/validation/ValidationContext.js
Expand Up @@ -34,13 +34,16 @@ type VariableUsage = {|
+defaultValue: ?mixed,
|};

export class ValidationBailEarlyError extends Error {}

/**
* An instance of this class is passed as the "this" context to all validators,
* allowing access to commonly useful contextual information from within a
* validation rule.
*/
export class ASTValidationContext {
_ast: DocumentNode;
_bail: boolean;
_errors: Array<GraphQLError>;
_fragments: ?ObjMap<FragmentDefinitionNode>;
_fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
Expand All @@ -49,8 +52,9 @@ export class ASTValidationContext {
$ReadOnlyArray<FragmentDefinitionNode>,
>;

constructor(ast: DocumentNode): void {
constructor(ast: DocumentNode, bail: boolean = false): void {
this._ast = ast;
this._bail = bail;
this._errors = [];
this._fragments = undefined;
this._fragmentSpreads = new Map();
Expand All @@ -59,6 +63,9 @@ export class ASTValidationContext {

reportError(error: GraphQLError): void {
this._errors.push(error);
if (this._bail) {
throw new ValidationBailEarlyError();
}
}

getErrors(): $ReadOnlyArray<GraphQLError> {
Expand Down Expand Up @@ -165,8 +172,9 @@ export class ValidationContext extends ASTValidationContext {
schema: GraphQLSchema,
ast: DocumentNode,
typeInfo: TypeInfo,
bail: boolean = false,
): void {
super(ast);
super(ast, bail);
this._schema = schema;
this._typeInfo = typeInfo;
this._variableUsages = new Map();
Expand Down
32 changes: 32 additions & 0 deletions src/validation/__tests__/validation-test.js
Expand Up @@ -74,4 +74,36 @@ describe('Validate: Supports full validation', () => {
'Cannot query field "isHousetrained" on type "Dog". Did you mean "isHousetrained"?',
]);
});

it('exits validation early when bail option is set', () => {
const doc = parse(`
query {
cat {
name
someNonExistentField
}
dog {
name
anotherNonExistentField
}
}
`);

// Ensure that the number of errors without the bail option is 2
const errors = validate(testSchema, doc, specifiedRules);
expect(errors).to.be.length(2);

const bailedErrors = validate(
testSchema,
doc,
specifiedRules,
undefined,
true,
);
expect(bailedErrors).to.be.length(1);
const errorMessages = errors.map(err => err.message);
expect(errorMessages[0]).to.equal(
'Cannot query field "someNonExistentField" on type "Cat".',
);
});
});
16 changes: 13 additions & 3 deletions src/validation/validate.js
Expand Up @@ -18,6 +18,7 @@ import {
type ValidationRule,
SDLValidationContext,
ValidationContext,
ValidationBailEarlyError,
} from './ValidationContext';

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

const context = new ValidationContext(schema, documentAST, typeInfo);
const context = new ValidationContext(schema, documentAST, typeInfo, bail);
// This uses a specialized visitor which runs multiple visitors in parallel,
// while maintaining the visitor skip and break API.
const visitor = visitInParallel(rules.map(rule => rule(context)));
// Visit the whole document with each instance of all provided rules.
visit(documentAST, visitWithTypeInfo(typeInfo, visitor));
try {
// Visit the whole document with each instance of all provided rules.
visit(documentAST, visitWithTypeInfo(typeInfo, visitor));
} catch (e) {
// If the caught error is not a `ValidationBailEarlyError`, rethrow as the
// error is fatal
if (!(e instanceof ValidationBailEarlyError)) {
throw e;
}
}
return context.getErrors();
}

Expand Down

0 comments on commit cea8a25

Please sign in to comment.