Skip to content

Conversation

ardatan
Copy link
Member

@ardatan ardatan commented Aug 14, 2025

Rewrite of #299 within the new structure

  • Normalize error paths with _entities into the real paths using flatten paths
  • Bring back some of old test components so I could write the unit tests based on local schemas without actual HTTP requests
  • Add serviceName and code to the subgraph errors while passing it to the final gw response
  • Remove traverse_and_callback_mut as it is no longer needed because we collect indexes in paths and use it with flatten path to get the mutable reference in the final response without traversing again

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @ardatan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly overhauls the error propagation mechanism within the GraphQL gateway, focusing on providing more precise and actionable error information to clients. The changes involve restructuring how errors are represented internally, introducing a sophisticated normalization layer to correctly map subgraph errors to the gateway's response paths, and enhancing data traversal to support this mapping. These improvements ensure that even in complex federated query scenarios, errors are clearly attributed and located, greatly aiding debugging and client-side error handling. The refactoring also includes internal naming clarifications and adoption of standard asynchronous primitives.

Highlights

  • Enhanced Error Structure: The core error handling mechanism has been significantly upgraded. GraphQLError now supports structured extensions using a HashMap, enabling richer metadata like serviceName and specific error codes to be attached to errors. This provides clearer context for debugging and client-side error handling.
  • Intelligent Error Path Normalization: A new error_normalization module has been introduced to intelligently map errors originating from subgraphs (especially in federated queries where entities are flattened) back to their logical paths in the gateway's final response. This ensures that clients receive accurate error locations, even for complex nested data structures.
  • Improved Data Traversal for Error Mapping: The internal data traversal logic (traverse_and_callback) has been refined to track precise paths, including array indices, using VecDeque. This detailed path information is critical for the new error normalization process, allowing the gateway to pinpoint the exact location of an error within a deeply nested response.
  • Robust Testing for Error Propagation: The pull request includes comprehensive new test infrastructure, integrating async-graphql for easy subgraph simulation and dedicated fixtures. A new error_propagation test case thoroughly validates the end-to-end error handling, ensuring that errors are correctly captured, normalized, and propagated to the client.
  • Internal Refactorings and Simplifications: Several key internal components, such as ExecutionContext (now QueryPlanExecutionContext) and HttpExecutionRequest (now SubgraphExecutionRequest), have been renamed for better clarity and consistency. The custom concurrency management has also been replaced with FuturesUnordered for streamlined asynchronous operations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly refactors error propagation within the GraphQL executor, introducing more structured handling, especially for federated queries. The changes include renaming several components for clarity, improving parallel execution logic, and adding comprehensive tests for error propagation scenarios. I've identified a critical bug where some subgraph errors were being dropped and an inconsistency in how error extensions are applied. Addressing these points will make the error handling much more robust. Overall, this is a great improvement.

Copy link

github-actions bot commented Aug 14, 2025

Federation Audit Results

189 tests  ±0   189 ✅ ±0   3s ⏱️ ±0s
 42 suites ±0     0 💤 ±0 
 42 files   ±0     0 ❌ ±0 

Results for commit 7d567a0. ± Comparison against base commit 02bf99e.

♻️ This comment has been updated with latest results.

This comment was marked as resolved.

Copy link

github-actions bot commented Aug 14, 2025

k6-benchmark results

     ✓ response code was 200
     ✓ no graphql errors
     ✓ valid response structure

     █ setup

     checks.........................: 100.00% ✓ 42270      ✗ 0    
     data_received..................: 1.2 GB  41 MB/s
     data_sent......................: 17 MB   550 kB/s
     http_req_blocked...............: avg=41.52µs  min=791ns   med=2.09µs   max=19.44ms  p(90)=2.97µs   p(95)=3.43µs  
     http_req_connecting............: avg=36.99µs  min=0s      med=0s       max=19.42ms  p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=104.9ms  min=2.2ms   med=102.9ms  max=298.38ms p(90)=130.36ms p(95)=141.69ms
       { expected_response:true }...: avg=104.9ms  min=2.2ms   med=102.9ms  max=298.38ms p(90)=130.36ms p(95)=141.69ms
     http_req_failed................: 0.00%   ✓ 0          ✗ 14110
     http_req_receiving.............: avg=454.15µs min=31.02µs med=53.45µs  max=90.27ms  p(90)=1.16ms   p(95)=2.8ms   
     http_req_sending...............: avg=101.89µs min=6.55µs  med=12.73µs  max=17.06ms  p(90)=19.06µs  p(95)=30.18µs 
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=104.35ms min=2.14ms  med=102.44ms max=256.09ms p(90)=129.63ms p(95)=141.23ms
     http_reqs......................: 14110   468.601621/s
     iteration_duration.............: avg=106.57ms min=16.21ms med=103.33ms max=632.96ms p(90)=130.96ms p(95)=142.43ms
     iterations.....................: 14090   467.937409/s
     vus............................: 50      min=50       max=50 
     vus_max........................: 50      min=50       max=50 

@ardatan ardatan marked this pull request as draft August 14, 2025 15:40
@ardatan ardatan marked this pull request as ready for review August 14, 2025 16:08
@ardatan ardatan force-pushed the error-propagation-v2 branch from d62ddbf to 13c3a6e Compare August 17, 2025 14:49
@dotansimha dotansimha changed the title Error Propagation feat(gw): error propagation Aug 17, 2025
if let Some(target_paths) =
job.filtered_representations_hashes.get_mut(hash)
{
for indexes_in_path in target_paths {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In traverse_and_callback_mut we give it a path and once it reaches the final segment, it executes the callback.
The traverse of final_response starts at the root, then it moves to the first path segment until it hits the final segment.

In your code, we always start from root, for every element in the target_paths vector.
At least from what I see, but maybe I'm missing something.

What was wrong with traverse_and_callback_mut?

Copy link
Member Author

@ardatan ardatan Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually didn't touch the logic. But instead I hold the array indices (the actual values of @ in the flatten path) so I can use them for fixing the error paths in the final response. Since we already know the path, traverse_and_callback_mut is no longer needed because we already use this information to access the entity in the fina lresponse.
Nothing is wrong with it but mutable version of traverse_and_callback is no longer needed. Immutable version is still used to collect the entities and also indices in the paths.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the loop within a loop. We start from the final_response for every for indexes_in_path in target_paths

fetch_node_id: i64,
representation_hashes: Vec<u64>,
representation_hash_to_index: HashMap<u64, usize>,
filtered_representations_hashes: HashMap<u64, Vec<VecDeque<usize>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment explaining the HashMap value? Or create a dedicated struct if it will make it more obvious?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are the indexes of the representations. Indexes are the actual values of @ in the flatten path.

job.representation_hash_to_index.get(&hash)
{
if let Some(entity) = entities.get(*entity_index) {
// SAFETY: `new_val` is a clone of an entity that lives for `'a`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is missing, it would be nice to include it, so we know why we use transmute.

if let Some(path_in_error) = &error.path {
if let Some(GraphQLErrorPathSegment::String(first_path)) = path_in_error.first() {
if first_path == "_entities" {
if let Some(GraphQLErrorPathSegment::Index(entity_index)) = path_in_error.get(1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we somehow reduce the depth level here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try 👍

@dotansimha dotansimha force-pushed the main branch 9 times, most recently from 3066ce0 to 53e8bd5 Compare September 1, 2025 12:13
@dotansimha dotansimha force-pushed the main branch 3 times, most recently from e9864f4 to a80cb5b Compare September 1, 2025 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants