@@ -36,7 +36,9 @@ use crate::{
3636 response:: project_by_operation,
3737 } ,
3838 response:: {
39- graphql_error:: GraphQLError , merge:: deep_merge, subgraph_response:: SubgraphResponse ,
39+ graphql_error:: { GraphQLError , GraphQLErrorPath } ,
40+ merge:: deep_merge,
41+ subgraph_response:: SubgraphResponse ,
4042 value:: Value ,
4143 } ,
4244 utils:: {
@@ -420,7 +422,7 @@ impl<'exec> Executor<'exec> {
420422 ctx : & mut ExecutionContext < ' exec > ,
421423 response_bytes : Bytes ,
422424 fetch_node_id : i64 ,
423- ) -> Option < ( Value < ' exec > , Option < & ' exec Vec < FetchRewrite > > ) > {
425+ ) -> Option < ( SubgraphResponse < ' exec > , Option < & ' exec Vec < FetchRewrite > > ) > {
424426 let idx = ctx. response_storage . add_response ( response_bytes) ;
425427 // SAFETY: The `bytes` are transmuted to the lifetime `'a` of the `ExecutionContext`.
426428 // This is safe because the `response_storage` is part of the `ExecutionContext` (`ctx`)
@@ -452,9 +454,7 @@ impl<'exec> Executor<'exec> {
452454 }
453455 } ;
454456
455- ctx. handle_errors ( response. errors ) ;
456-
457- Some ( ( response. data , output_rewrites) )
457+ Some ( ( response, output_rewrites) )
458458 }
459459
460460 fn process_job_result (
@@ -472,16 +472,18 @@ impl<'exec> Executor<'exec> {
472472 & mut ctx. response_headers_aggregator ,
473473 ) ?;
474474
475- if let Some ( ( mut data , output_rewrites) ) =
475+ if let Some ( ( mut response , output_rewrites) ) =
476476 self . process_subgraph_response ( ctx, job. response . body , job. fetch_node_id )
477477 {
478+ ctx. handle_errors ( response. errors , None ) ;
478479 if let Some ( output_rewrites) = output_rewrites {
479480 for output_rewrite in output_rewrites {
480- output_rewrite. rewrite ( & self . schema_metadata . possible_types , & mut data) ;
481+ output_rewrite
482+ . rewrite ( & self . schema_metadata . possible_types , & mut response. data ) ;
481483 }
482484 }
483485
484- deep_merge ( & mut ctx. final_response , data) ;
486+ deep_merge ( & mut ctx. final_response , response . data ) ;
485487 }
486488 }
487489 ExecutionJob :: FlattenFetch ( job) => {
@@ -493,10 +495,10 @@ impl<'exec> Executor<'exec> {
493495 & mut ctx. response_headers_aggregator ,
494496 ) ?;
495497
496- if let Some ( ( mut data , output_rewrites) ) =
498+ if let Some ( ( mut response , output_rewrites) ) =
497499 self . process_subgraph_response ( ctx, job. response . body , job. fetch_node_id )
498500 {
499- if let Some ( mut entities) = data. take_entities ( ) {
501+ if let Some ( mut entities) = response . data . take_entities ( ) {
500502 if let Some ( output_rewrites) = output_rewrites {
501503 for output_rewrite in output_rewrites {
502504 for entity in & mut entities {
@@ -508,15 +510,33 @@ impl<'exec> Executor<'exec> {
508510
509511 let mut index = 0 ;
510512 let normalized_path = job. flatten_node_path . as_slice ( ) ;
513+ // If there is an error in the response, then collect the paths for normalizing the error
514+ let initial_error_path = response
515+ . errors
516+ . as_ref ( )
517+ . map ( |_| GraphQLErrorPath :: with_capacity ( normalized_path. len ( ) + 2 ) ) ;
518+ let mut entity_index_error_map = response
519+ . errors
520+ . as_ref ( )
521+ . map ( |_| HashMap :: with_capacity ( entities. len ( ) ) ) ;
511522 traverse_and_callback_mut (
512523 & mut ctx. final_response ,
513524 normalized_path,
514525 self . schema_metadata ,
515- & mut |target| {
526+ initial_error_path,
527+ & mut |target, error_path| {
516528 let hash = job. representation_hashes [ index] ;
517529 if let Some ( entity_index) =
518530 job. representation_hash_to_index . get ( & hash)
519531 {
532+ if let ( Some ( error_path) , Some ( entity_index_error_map) ) =
533+ ( error_path, entity_index_error_map. as_mut ( ) )
534+ {
535+ let error_paths = entity_index_error_map
536+ . entry ( entity_index)
537+ . or_insert_with ( Vec :: new) ;
538+ error_paths. push ( error_path) ;
539+ }
520540 if let Some ( entity) = entities. get ( * entity_index) {
521541 // SAFETY: `new_val` is a clone of an entity that lives for `'a`.
522542 // The transmute is to satisfy the compiler, but the lifetime
@@ -529,6 +549,7 @@ impl<'exec> Executor<'exec> {
529549 index += 1 ;
530550 } ,
531551 ) ;
552+ ctx. handle_errors ( response. errors , entity_index_error_map) ;
532553 }
533554 }
534555 }
@@ -714,6 +735,8 @@ fn select_fetch_variables<'a>(
714735
715736#[ cfg( test) ]
716737mod tests {
738+ use crate :: { context:: ExecutionContext , response:: graphql_error:: GraphQLErrorPath } ;
739+
717740 use super :: select_fetch_variables;
718741 use sonic_rs:: Value ;
719742 use std:: collections:: { BTreeSet , HashMap } ;
@@ -768,4 +791,65 @@ mod tests {
768791
769792 assert ! ( selected. is_none( ) ) ;
770793 }
794+ #[ test]
795+ /**
796+ * We have the same entity in two different paths ["a", 0] and ["b", 1],
797+ * and the subgraph response has an error for this entity.
798+ * So we should duplicate the error for both paths.
799+ */
800+ fn normalize_entity_errors_correctly ( ) {
801+ use crate :: response:: graphql_error:: { GraphQLError , GraphQLErrorPathSegment } ;
802+ use std:: collections:: HashMap ;
803+ let mut ctx = ExecutionContext :: default ( ) ;
804+ let mut entity_index_error_map: HashMap < & usize , Vec < GraphQLErrorPath > > = HashMap :: new ( ) ;
805+ entity_index_error_map. insert (
806+ & 0 ,
807+ vec ! [
808+ GraphQLErrorPath {
809+ segments: vec![
810+ GraphQLErrorPathSegment :: String ( "a" . to_string( ) ) ,
811+ GraphQLErrorPathSegment :: Index ( 0 ) ,
812+ ] ,
813+ } ,
814+ GraphQLErrorPath {
815+ segments: vec![
816+ GraphQLErrorPathSegment :: String ( "b" . to_string( ) ) ,
817+ GraphQLErrorPathSegment :: Index ( 1 ) ,
818+ ] ,
819+ } ,
820+ ] ,
821+ ) ;
822+ let response_errors = vec ! [ GraphQLError {
823+ message: "Error 1" . to_string( ) ,
824+ locations: None ,
825+ path: Some ( GraphQLErrorPath {
826+ segments: vec![
827+ GraphQLErrorPathSegment :: String ( "_entities" . to_string( ) ) ,
828+ GraphQLErrorPathSegment :: Index ( 0 ) ,
829+ GraphQLErrorPathSegment :: String ( "field1" . to_string( ) ) ,
830+ ] ,
831+ } ) ,
832+ extensions: None ,
833+ } ] ;
834+ ctx. handle_errors ( Some ( response_errors) , Some ( entity_index_error_map) ) ;
835+ assert_eq ! ( ctx. errors. len( ) , 2 ) ;
836+ assert_eq ! ( ctx. errors[ 0 ] . message, "Error 1" ) ;
837+ assert_eq ! (
838+ ctx. errors[ 0 ] . path. as_ref( ) . unwrap( ) . segments,
839+ vec![
840+ GraphQLErrorPathSegment :: String ( "a" . to_string( ) ) ,
841+ GraphQLErrorPathSegment :: Index ( 0 ) ,
842+ GraphQLErrorPathSegment :: String ( "field1" . to_string( ) )
843+ ]
844+ ) ;
845+ assert_eq ! ( ctx. errors[ 1 ] . message, "Error 1" ) ;
846+ assert_eq ! (
847+ ctx. errors[ 1 ] . path. as_ref( ) . unwrap( ) . segments,
848+ vec![
849+ GraphQLErrorPathSegment :: String ( "b" . to_string( ) ) ,
850+ GraphQLErrorPathSegment :: Index ( 1 ) ,
851+ GraphQLErrorPathSegment :: String ( "field1" . to_string( ) )
852+ ]
853+ ) ;
854+ }
771855}
0 commit comments