@@ -114,7 +114,46 @@ export interface ExecutionContext {
114114 fieldResolver : GraphQLFieldResolver < any , any > ;
115115 typeResolver : GraphQLTypeResolver < any , any > ;
116116 subscribeFieldResolver : GraphQLFieldResolver < any , any > ;
117- errors : Array < GraphQLError > ;
117+ collectedErrors : CollectedErrors ;
118+ }
119+
120+ /**
121+ * @internal
122+ */
123+ class CollectedErrors {
124+ private _errorPositions : Set < Path | undefined > ;
125+ private _errors : Array < GraphQLError > ;
126+ constructor ( ) {
127+ this . _errorPositions = new Set < Path | undefined > ( ) ;
128+ this . _errors = [ ] ;
129+ }
130+
131+ get errors ( ) : ReadonlyArray < GraphQLError > {
132+ return this . _errors ;
133+ }
134+
135+ add ( error : GraphQLError , path : Path | undefined ) {
136+ // Do not modify errors list if the execution position for this error or
137+ // any of its ancestors has already been nulled via error propagation.
138+ // This check should be unnecessary for implementations able to implement
139+ // actual cancellation.
140+ if ( this . _hasNulledPosition ( path ) ) {
141+ return ;
142+ }
143+ this . _errorPositions . add ( path ) ;
144+ this . _errors . push ( error ) ;
145+ }
146+
147+ private _hasNulledPosition ( startPath : Path | undefined ) : boolean {
148+ let path = startPath ;
149+ while ( path !== undefined ) {
150+ if ( this . _errorPositions . has ( path ) ) {
151+ return true ;
152+ }
153+ path = path . prev ;
154+ }
155+ return this . _errorPositions . has ( undefined ) ;
156+ }
118157}
119158
120159/**
@@ -206,17 +245,17 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
206245 const result = executeOperation ( exeContext , operation , rootValue ) ;
207246 if ( isPromise ( result ) ) {
208247 return result . then (
209- ( data ) => buildResponse ( data , exeContext . errors ) ,
248+ ( data ) => buildResponse ( data , exeContext . collectedErrors . errors ) ,
210249 ( error ) => {
211- exeContext . errors . push ( error ) ;
212- return buildResponse ( null , exeContext . errors ) ;
250+ exeContext . collectedErrors . add ( error , undefined ) ;
251+ return buildResponse ( null , exeContext . collectedErrors . errors ) ;
213252 } ,
214253 ) ;
215254 }
216- return buildResponse ( result , exeContext . errors ) ;
255+ return buildResponse ( result , exeContext . collectedErrors . errors ) ;
217256 } catch ( error ) {
218- exeContext . errors . push ( error ) ;
219- return buildResponse ( null , exeContext . errors ) ;
257+ exeContext . collectedErrors . add ( error , undefined ) ;
258+ return buildResponse ( null , exeContext . collectedErrors . errors ) ;
220259 }
221260}
222261
@@ -352,7 +391,7 @@ export function buildExecutionContext(
352391 fieldResolver : fieldResolver ?? defaultFieldResolver ,
353392 typeResolver : typeResolver ?? defaultTypeResolver ,
354393 subscribeFieldResolver : subscribeFieldResolver ?? defaultFieldResolver ,
355- errors : [ ] ,
394+ collectedErrors : new CollectedErrors ( ) ,
356395 } ;
357396}
358397
@@ -558,13 +597,13 @@ function executeField(
558597 // to take a second callback for the error case.
559598 return completed . then ( undefined , ( rawError ) => {
560599 const error = locatedError ( rawError , fieldNodes , pathToArray ( path ) ) ;
561- return handleFieldError ( error , returnType , exeContext ) ;
600+ return handleFieldError ( error , returnType , path , exeContext ) ;
562601 } ) ;
563602 }
564603 return completed ;
565604 } catch ( rawError ) {
566605 const error = locatedError ( rawError , fieldNodes , pathToArray ( path ) ) ;
567- return handleFieldError ( error , returnType , exeContext ) ;
606+ return handleFieldError ( error , returnType , path , exeContext ) ;
568607 }
569608}
570609
@@ -597,6 +636,7 @@ export function buildResolveInfo(
597636function handleFieldError (
598637 error : GraphQLError ,
599638 returnType : GraphQLOutputType ,
639+ path : Path ,
600640 exeContext : ExecutionContext ,
601641) : null {
602642 // If the field type is non-nullable, then it is resolved without any
@@ -607,7 +647,7 @@ function handleFieldError(
607647
608648 // Otherwise, error protection is applied, logging the error and resolving
609649 // a null value for this field if one is encountered.
610- exeContext . errors . push ( error ) ;
650+ exeContext . collectedErrors . add ( error , path ) ;
611651 return null ;
612652}
613653
@@ -779,13 +819,13 @@ function completeListValue(
779819 fieldNodes ,
780820 pathToArray ( itemPath ) ,
781821 ) ;
782- return handleFieldError ( error , itemType , exeContext ) ;
822+ return handleFieldError ( error , itemType , itemPath , exeContext ) ;
783823 } ) ;
784824 }
785825 return completedItem ;
786826 } catch ( rawError ) {
787827 const error = locatedError ( rawError , fieldNodes , pathToArray ( itemPath ) ) ;
788- return handleFieldError ( error , itemType , exeContext ) ;
828+ return handleFieldError ( error , itemType , itemPath , exeContext ) ;
789829 }
790830 } ) ;
791831
0 commit comments