From 953adaa60e6b2d6f722acfae387d6c72ff53aa81 Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Mon, 5 Aug 2019 20:05:22 -0700 Subject: [PATCH] [validation] Add "onError" option to allow for custom error handling behavior when performing validation --- src/validation/ValidationContext.js | 25 ++++++++++++++------- src/validation/__tests__/validation-test.js | 25 +++++++++++++++++++++ src/validation/validate.js | 16 ++++++++++--- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/validation/ValidationContext.js b/src/validation/ValidationContext.js index 26400c33f1..ccead9669a 100644 --- a/src/validation/ValidationContext.js +++ b/src/validation/ValidationContext.js @@ -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'; @@ -41,6 +41,7 @@ type VariableUsage = {| */ export class ASTValidationContext { _ast: DocumentNode; + _onError: ?(err: Error) => void; _errors: Array; _fragments: ?ObjMap; _fragmentSpreads: Map>; @@ -49,16 +50,23 @@ export class ASTValidationContext { $ReadOnlyArray, >; - 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(); + this._onError = undefined; + if (onError) { + this._onError = onError.bind(this); + } } reportError(error: GraphQLError): void { this._errors.push(error); + if (this._onError) { + this._onError(error); + } } getErrors(): $ReadOnlyArray { @@ -165,8 +173,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(); diff --git a/src/validation/__tests__/validation-test.js b/src/validation/__tests__/validation-test.js index 067255e2fa..75cc74c9c7 100644 --- a/src/validation/__tests__/validation-test.js +++ b/src/validation/__tests__/validation-test.js @@ -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); + }); }); diff --git a/src/validation/validate.js b/src/validation/validate.js index 4da3e6d9c7..450cfbb5f5 100644 --- a/src/validation/validate.js +++ b/src/validation/validate.js @@ -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'; /** @@ -41,12 +41,22 @@ export function validate( documentAST: DocumentNode, rules?: $ReadOnlyArray = specifiedRules, typeInfo?: TypeInfo = new TypeInfo(schema), + onError?: (err: Error, ctx: ValidationContext) => void, ): $ReadOnlyArray { 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)));