Skip to content

Commit

Permalink
bail -> onError
Browse files Browse the repository at this point in the history
  • Loading branch information
skevy committed Aug 6, 2019
1 parent cea8a25 commit be3051b
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 46 deletions.
30 changes: 15 additions & 15 deletions src/validation/ValidationContext.js
Expand Up @@ -8,21 +8,21 @@ import { Kind } from '../language/kinds';
import { type ASTVisitor, visit, visitWithTypeInfo } from '../language/visitor';
import {
type DocumentNode,
type FragmentDefinitionNode,
type FragmentSpreadNode,
type OperationDefinitionNode,
type VariableNode,
type SelectionSetNode,
type FragmentSpreadNode,
type FragmentDefinitionNode,
type VariableNode,
} from '../language/ast';

import { type GraphQLSchema } from '../type/schema';
import { type GraphQLDirective } from '../type/directives';
import {
type GraphQLInputType,
type GraphQLOutputType,
type GraphQLArgument,
type GraphQLCompositeType,
type GraphQLField,
type GraphQLArgument,
type GraphQLInputType,
type GraphQLOutputType,
} from '../type/definition';

import { TypeInfo } from '../utilities/TypeInfo';
Expand All @@ -34,16 +34,14 @@ 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;
_onError: ?(err: Error) => void = undefined;
_errors: Array<GraphQLError>;
_fragments: ?ObjMap<FragmentDefinitionNode>;
_fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
Expand All @@ -52,19 +50,21 @@ export class ASTValidationContext {
$ReadOnlyArray<FragmentDefinitionNode>,
>;

constructor(ast: DocumentNode, bail: boolean = false): void {
constructor(ast: DocumentNode, onError?: (err: Error) => void): void {
this._ast = ast;
this._bail = bail;
this._errors = [];
this._fragments = undefined;
this._fragmentSpreads = new Map();
this._recursivelyReferencedFragments = new Map();
if (onError) {
this._onError = onError.bind(this);
}
}

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

Expand Down Expand Up @@ -172,9 +172,9 @@ export class ValidationContext extends ASTValidationContext {
schema: GraphQLSchema,
ast: DocumentNode,
typeInfo: TypeInfo,
bail: boolean = false,
onError?: (err: Error) => void,
): void {
super(ast, bail);
super(ast, onError);
this._schema = schema;
this._typeInfo = typeInfo;
this._variableUsages = new Map();
Expand Down
25 changes: 9 additions & 16 deletions src/validation/__tests__/validation-test.js
Expand Up @@ -75,7 +75,7 @@ describe('Validate: Supports full validation', () => {
]);
});

it('exits validation early when bail option is set', () => {
it('properly calls onError callback when passed', () => {
const doc = parse(`
query {
cat {
Expand All @@ -89,21 +89,14 @@ describe('Validate: Supports full validation', () => {
}
`);

// 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 expectedNumberOfErrors = 2;
let errorCount = 0;
validate(testSchema, doc, specifiedRules, undefined, (err, ctx) => {
expect(err).to.not.be.a('null');
expect(ctx).to.not.be.a('null');
expect(ctx.getErrors()).to.be.length(++errorCount);
});

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".',
);
expect(errorCount).to.be.equal(expectedNumberOfErrors);
});
});
27 changes: 12 additions & 15 deletions src/validation/validate.js
Expand Up @@ -14,11 +14,10 @@ import { TypeInfo } from '../utilities/TypeInfo';

import { specifiedRules, specifiedSDLRules } from './specifiedRules';
import {
type SDLValidationRule,
type ValidationRule,
SDLValidationContext,
type SDLValidationRule,
ValidationContext,
ValidationBailEarlyError,
type ValidationRule,
} from './ValidationContext';

/**
Expand All @@ -42,26 +41,24 @@ export function validate(
documentAST: DocumentNode,
rules?: $ReadOnlyArray<ValidationRule> = specifiedRules,
typeInfo?: TypeInfo = new TypeInfo(schema),
bail?: boolean = false,
onError?: (err: Error, ctx: ValidationContext) => void,
): $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, bail);
const context = new ValidationContext(schema, documentAST, typeInfo, function(
err,
) {
if (onError) {
onError(err, this);
}
});
// 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)));
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;
}
}
// Visit the whole document with each instance of all provided rules.
visit(documentAST, visitWithTypeInfo(typeInfo, visitor));
return context.getErrors();
}

Expand Down

0 comments on commit be3051b

Please sign in to comment.