Skip to content

Commit

Permalink
[validation] Add "onError" option to allow for custom error handling …
Browse files Browse the repository at this point in the history
…behavior when performing validation
  • Loading branch information
skevy committed Aug 6, 2019
1 parent 6adb527 commit 6632da6
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 11 deletions.
24 changes: 16 additions & 8 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 @@ -41,6 +41,7 @@ type VariableUsage = {|
*/
export class ASTValidationContext {
_ast: DocumentNode;
_onError: ?(err: Error) => void = undefined;
_errors: Array<GraphQLError>;
_fragments: ?ObjMap<FragmentDefinitionNode>;
_fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
Expand All @@ -49,16 +50,22 @@ export class ASTValidationContext {
$ReadOnlyArray<FragmentDefinitionNode>,
>;

constructor(ast: DocumentNode): void {
constructor(ast: DocumentNode, onError?: (err: Error) => void): void {
this._ast = ast;
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._onError) {
this._onError(error);
}
}

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

it('properly calls onError callback when passed', () => {
const doc = parse(`
query {
cat {
name
someNonExistentField
}
dog {
name
anotherNonExistentField
}
}
`);

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);
});

expect(errorCount).to.be.equal(expectedNumberOfErrors);
});
});
16 changes: 13 additions & 3 deletions src/validation/validate.js
Expand Up @@ -14,10 +14,10 @@ import { TypeInfo } from '../utilities/TypeInfo';

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

/**
Expand All @@ -41,12 +41,22 @@ export function validate(
documentAST: DocumentNode,
rules?: $ReadOnlyArray<ValidationRule> = specifiedRules,
typeInfo?: TypeInfo = new TypeInfo(schema),
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);
const context = new ValidationContext(
schema,
documentAST,
typeInfo,
function onErrorWithContext(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)));
Expand Down

0 comments on commit 6632da6

Please sign in to comment.