Skip to content

Commit

Permalink
Include path for Fusion subgraph errors (#6916)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Staib <michael@chillicream.com>
  • Loading branch information
tobias-tengler and michaelstaib authored Mar 4, 2024
1 parent ed0449f commit 9479306
Show file tree
Hide file tree
Showing 16 changed files with 1,352 additions and 27 deletions.
22 changes: 19 additions & 3 deletions src/HotChocolate/Core/src/Execution/Processing/PathHelper.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System;
using System.Buffers;
using System.Text.Json;

namespace HotChocolate.Execution.Processing;

internal static class PathHelper
{
private const int _initialPathLength = 64;
private const int _initialPathLength = 64;

public static Path CreatePathFromContext(ObjectResult parent)
=> CreatePath(parent);
=> parent.Parent is null ? Path.Root : CreatePath(parent);

public static Path CreatePathFromContext(ISelection selection, ResultData parent, int index)
=> parent switch
Expand All @@ -18,6 +19,21 @@ public static Path CreatePathFromContext(ISelection selection, ResultData parent
_ => throw new NotSupportedException($"{parent.GetType().FullName} is not a supported parent type."),
};

public static Path CombinePath(Path path, JsonElement errorSubPath, int skipSubElements)
{
for (var i = skipSubElements; i < errorSubPath.GetArrayLength(); i++)
{
path = errorSubPath[i] switch
{
{ ValueKind: JsonValueKind.String, } nameElement => path.Append(nameElement.GetString()!),
{ ValueKind: JsonValueKind.Number, } indexElement => path.Append(indexElement.GetInt32()),
_ => throw new InvalidOperationException("The error path contains an unsupported element.")
};
}

return path;
}

private static Path CreatePath(ResultData parent, object segmentValue)
{
var segments = ArrayPool<object>.Shared.Rent(_initialPathLength);
Expand Down
4 changes: 4 additions & 0 deletions src/HotChocolate/Fusion/src/Core/Execution/ExecutionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ public ExecutionState(

/// <summary>
/// Gets the selection set data that was collected during execution.
/// The selection set data represents the data that we have collected
/// from the subgraphs for the <see cref="SelectionSet"/>.
/// </summary>
public SelectionData[] SelectionSetData { get; }

/// <summary>
/// Gets the completed selection set result.
/// The selection set result represents the data for the
/// <see cref="SelectionSet"/> that we deliver to the user.
/// </summary>
public ObjectResult SelectionSetResult { get; }

Expand Down
12 changes: 10 additions & 2 deletions src/HotChocolate/Fusion/src/Core/Execution/ExecutorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,22 +483,28 @@ public static void TryInitializeExecutionState(QueryPlan queryPlan, ExecutionSta
public static void ExtractErrors(
ResultBuilder resultBuilder,
JsonElement errors,
ObjectResult selectionSetResult,
int pathDepth,
bool addDebugInfo)
{
if (errors.ValueKind is not JsonValueKind.Array)
{
return;
}

var path = PathHelper.CreatePathFromContext(selectionSetResult);
foreach (var error in errors.EnumerateArray())
{
ExtractError(resultBuilder, error, addDebugInfo);
ExtractError(resultBuilder, error, selectionSetResult, path, pathDepth, addDebugInfo);
}
}

private static void ExtractError(
ResultBuilder resultBuilder,
JsonElement error,
ObjectResult selectionSetResult,
Path parentPath,
int pathDepth,
bool addDebugInfo)
{
if (error.ValueKind is not JsonValueKind.Object)
Expand Down Expand Up @@ -530,7 +536,9 @@ private static void ExtractError(
if (error.TryGetProperty("path", out var remotePath) &&
remotePath.ValueKind is JsonValueKind.Array)
{
// TODO : rewrite remote path if possible!
var path = PathHelper.CombinePath(parentPath, remotePath, pathDepth);
errorBuilder.SetPath(path);

if (addDebugInfo)
{
errorBuilder.SetExtension("remotePath", remotePath);
Expand Down
13 changes: 7 additions & 6 deletions src/HotChocolate/Fusion/src/Core/Execution/Nodes/Resolve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ namespace HotChocolate.Fusion.Execution.Nodes;
/// </param>
internal sealed class Resolve(int id, Config config) : ResolverNodeBase(id, config)
{

/// <summary>
/// Gets the kind of this node.
/// </summary>
Expand Down Expand Up @@ -69,7 +68,7 @@ protected override async Task OnExecuteAsync(
ProcessResponses(context, executionState, requests, responses, SubgraphName);
}
}
catch(Exception ex)
catch (Exception ex)
{
var error = context.OperationContext.ErrorHandler.CreateUnexpectedError(ex);
context.Result.AddError(error.Build());
Expand Down Expand Up @@ -129,20 +128,22 @@ private void ProcessResponses(
ref var request = ref MemoryMarshal.GetArrayDataReference(requests);
ref var response = ref MemoryMarshal.GetArrayDataReference(responses);
ref var end = ref Unsafe.Add(ref state, executionStates.Count);
var pathLength = Path.Length;

while (Unsafe.IsAddressLessThan(ref state, ref end))
{
var data = UnwrapResult(response);
var selectionSet = state.SelectionSet;
var selectionResults = state.SelectionSetData;
var selectionSetData = state.SelectionSetData;
var selectionSetResult = state.SelectionSetResult;
var exportKeys = state.Requires;
var variableValues = state.VariableValues;

ExtractErrors(context.Result, response.Errors, context.ShowDebugInfo);
ExtractErrors(context.Result, response.Errors, selectionSetResult, pathLength, context.ShowDebugInfo);

// we extract the selection data from the request and add it to the
// workItem results.
ExtractSelectionResults(SelectionSet, subgraphName, data, selectionResults);
ExtractSelectionResults(SelectionSet, subgraphName, data, selectionSetData);

// next we need to extract any variables that we need for followup requests.
ExtractVariables(data, context.QueryPlan, selectionSet, exportKeys, variableValues);
Expand All @@ -152,4 +153,4 @@ private void ProcessResponses(
response = ref Unsafe.Add(ref response, 1)!;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using HotChocolate.Execution.Processing;
using HotChocolate.Fusion.Clients;
using HotChocolate.Language;
using static HotChocolate.Fusion.Execution.ExecutorUtils;
Expand Down Expand Up @@ -87,7 +88,7 @@ protected override async Task OnExecuteAsync(
ProcessResult(context, response, batchExecutionState);
}
}
catch(Exception ex)
catch (Exception ex)
{
var error = context.OperationContext.ErrorHandler.CreateUnexpectedError(ex);
context.Result.AddError(error.Build());
Expand Down Expand Up @@ -134,17 +135,28 @@ private void ProcessResult(
GraphQLResponse response,
BatchExecutionState[] batchExecutionState)
{
ExtractErrors(context.Result, response.Errors, context.ShowDebugInfo);
var result = UnwrapResult(response, Requires);

ref var batchState = ref MemoryMarshal.GetArrayDataReference(batchExecutionState);
ref var end = ref Unsafe.Add(ref batchState, batchExecutionState.Length);
var pathLength = Path.Length;
var first = true;

while (Unsafe.IsAddressLessThan(ref batchState, ref end))
{
if (first)
{
ExtractErrors(
context.Result,
response.Errors,
batchState.SelectionSetResult,
pathLength + 1,
context.ShowDebugInfo);
first = false;
}

if (result.TryGetValue(batchState.Key, out var data))
{
ExtractSelectionResults(SelectionSet, SubgraphName, data, batchState.SelectionResults);
ExtractSelectionResults(SelectionSet, SubgraphName, data, batchState.SelectionSetData);
ExtractVariables(data, context.QueryPlan, SelectionSet, batchState.Requires, batchState.VariableValues);
}

Expand Down Expand Up @@ -237,6 +249,7 @@ private Dictionary<string, JsonElement> UnwrapResult(
if (exportKeys.Count == 1)
{
var key = exportKeys[0];

foreach (var element in data.EnumerateArray())
{
if (element.TryGetProperty(key, out var keyValue))
Expand Down Expand Up @@ -391,8 +404,17 @@ private readonly struct BatchExecutionState(string batchKey, ExecutionState exec
public IReadOnlyList<string> Requires { get; } = executionState.Requires;

/// <summary>
/// Gets the selection set data.
/// Gets the completed selection set result.
/// The selection set result represents the data for the
/// <see cref="ExecutionState.SelectionSet"/> that we deliver to the user.
/// </summary>
public ObjectResult SelectionSetResult { get; } = executionState.SelectionSetResult;

/// <summary>
/// Gets the selection set data that was collected during execution.
/// The selection set data represents the data that we have collected
/// from the subgraphs for the <see cref="ExecutionState.SelectionSet"/>.
/// </summary>
public SelectionData[] SelectionResults { get; } = executionState.SelectionSetData;
public SelectionData[] SelectionSetData { get; } = executionState.SelectionSetData;
}
}
}
Loading

0 comments on commit 9479306

Please sign in to comment.