Skip to content

Commit

Permalink
Reworked Stitching Error Handling (#2529)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Nov 2, 2020
1 parent 601190b commit 300d01b
Show file tree
Hide file tree
Showing 59 changed files with 514 additions and 1,862 deletions.
1 change: 1 addition & 0 deletions src/HotChocolate/Data/src/Data/Filters/FilterInputType.cs
Expand Up @@ -66,6 +66,7 @@ protected virtual void Configure(IFilterInputTypeDescriptor descriptor)
{
fields.Add(new AndField(context.DescriptorContext, def.Scope));
}

if (definition is FilterInputTypeDefinition { UseOr: true } defOr)
{
fields.Add(new OrField(context.DescriptorContext, defOr.Scope));
Expand Down
28 changes: 14 additions & 14 deletions src/HotChocolate/Stitching/HotChocolate.Stitching.sln
Expand Up @@ -47,7 +47,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Abstractions", "src\Stitching.Abstractions\HotChocolate.Stitching.Abstractions.csproj", "{E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stitching.Redis", "src\Stitching.Redis\Stitching.Redis.csproj", "{5492C256-DD42-45DC-8515-F1281F30E99E}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Redis", "src\Stitching.Redis\HotChocolate.Stitching.Redis.csproj", "{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -251,18 +251,18 @@ Global
{E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}.Release|x64.Build.0 = Release|Any CPU
{E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}.Release|x86.ActiveCfg = Release|Any CPU
{E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}.Release|x86.Build.0 = Release|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x64.ActiveCfg = Debug|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x64.Build.0 = Debug|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x86.ActiveCfg = Debug|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x86.Build.0 = Debug|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Release|Any CPU.Build.0 = Release|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x64.ActiveCfg = Release|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x64.Build.0 = Release|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x86.ActiveCfg = Release|Any CPU
{5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x86.Build.0 = Release|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Debug|x64.ActiveCfg = Debug|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Debug|x64.Build.0 = Debug|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Debug|x86.Build.0 = Debug|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Release|Any CPU.Build.0 = Release|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Release|x64.ActiveCfg = Release|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Release|x64.Build.0 = Release|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Release|x86.ActiveCfg = Release|Any CPU
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -284,7 +284,7 @@ Global
{C1CF3B06-4A02-46A6-98B2-448B5DD4E7D3} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8}
{709EE9F8-BA48-4C5A-8BDA-96B96689A1FC} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8}
{E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3}
{5492C256-DD42-45DC-8515-F1281F30E99E} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3}
{46B6437B-5AC2-41E6-89C6-CC0FE42190A1} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FB1557E1-2F94-4540-93E5-B47698838B72}
Expand Down
@@ -1,11 +1,10 @@
using System.Collections.Generic;
using HotChocolate.Execution.Options;

namespace HotChocolate.Stitching.Redis
{
internal sealed class SchemaDefinitionDto
{
public string Name { get; set; }
public string? Name { get; set; }

public string? Document { get; set; }

Expand Down
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -67,7 +68,7 @@ public async Task InvokeAsync(IMiddlewareContext context)
context.Result = value is null or NullValueNode ? null : new SerializedData(value);
if (result.Errors is not null)
{
ReportErrors(delegateDirective.Schema, context, result.Errors);
ReportErrors(delegateDirective.Schema, context, path, result.Errors);
}
}

Expand Down Expand Up @@ -224,24 +225,27 @@ public async Task InvokeAsync(IMiddlewareContext context)
private static void ReportErrors(
NameString schemaName,
IResolverContext context,
IImmutableStack<SelectionPathComponent> fetchPath,
IEnumerable<IError> errors)
{
foreach (IError error in errors)
{
IErrorBuilder builder = ErrorBuilder.FromError(error)
IErrorBuilder builder = ErrorBuilder
.FromError(error)
.SetExtension(_remoteErrorField, error.RemoveException())
.SetExtension(_schemaNameErrorField, schemaName.Value);

if (error.Path != null)
if (error.Path is not null)
{
Path path = RewriteErrorPath(error, context.Path);
builder.SetPath(path)
builder
.SetPath(RewriteErrorPath(error.Path, context.Path, fetchPath))
.ClearLocations()
.AddLocation(context.FieldSelection);
}
else if (IsHttpError(error))
{
builder.SetPath(context.Path)
builder
.SetPath(context.Path)
.ClearLocations()
.AddLocation(context.FieldSelection);
}
Expand All @@ -250,40 +254,66 @@ public async Task InvokeAsync(IMiddlewareContext context)
}
}

private static Path RewriteErrorPath(IError error, Path path)
private static Path RewriteErrorPath(
Path errorPath,
Path fieldPath,
IImmutableStack<SelectionPathComponent> fetchPath)
{
// TODO : FIX THIS
Path current = path;

/*
if (error.Path.Depth > 0 &&
error.Path is NamePathSegment p1 &&
path is NamePathSegment p2 &&
p1.Name.Equals(p2.Name))
{
while()
}
var depth = errorPath.Depth + 1;
Path[] buffer = ArrayPool<Path>.Shared.Rent(depth);
Span<Path> paths = buffer.AsSpan().Slice(0, depth);

if (error.Path.Depth > 0
&& error.Path[0] is string s
&& current.Name.Equals(s))
try
{
for (int i = 1; i < error.Path.Count; i++)
Path? current = errorPath;

do
{
if (error.Path[i] is string name)
paths[--depth] = current;
current = current.Parent;
} while (current is not null && current is not RootPathSegment);

depth = 0;
while (!fetchPath.IsEmpty)
{
fetchPath = fetchPath.Pop(out SelectionPathComponent fp);
if (paths[depth] is NamePathSegment np && np.Name.Equals(fp.Name.Value))
{
current = current.Append(name);
depth++;
}
else
{
return fieldPath;
}
}

paths = depth == 0 ? paths.Slice(1) : paths.Slice(depth);

if (paths.Length == 0)
{
return fieldPath;
}

if (error.Path[i] is int index)
current = fieldPath;

for (int i = 0; i < paths.Length; i++)
{
if (paths[i] is IndexerPathSegment index)
{
current = current.Append(index.Index);
}
else if (paths[i] is NamePathSegment name)
{
current = current.Append(index);
current = current.Append(name.Name);
}
}
}
*/

return current;
return current;
}
finally
{
ArrayPool<Path>.Shared.Return(buffer);
}
}

private static bool IsHttpError(IError error) =>
Expand Down Expand Up @@ -370,13 +400,13 @@ private static Path RewriteErrorPath(IError error, Path path)
return field;
}

private static void ResolveScopedVariableArguments(
IResolverContext context,
NameString schemaName,
SelectionPathComponent component,
IOutputField field,
ICollection<VariableValue> variables,
ExtractFieldQuerySyntaxRewriter rewriter)
private static void ResolveScopedVariableArguments(
IResolverContext context,
NameString schemaName,
SelectionPathComponent component,
IOutputField field,
ICollection<VariableValue> variables,
ExtractFieldQuerySyntaxRewriter rewriter)
{
foreach (ArgumentNode argument in component.Arguments)
{
Expand Down
Expand Up @@ -48,7 +48,7 @@ internal class HttpRequestClient
{
using var writer = new ArrayWriter();

HttpRequestMessage requestMessage =
using HttpRequestMessage requestMessage =
await CreateRequestAsync(writer, request, targetSchema, cancellationToken)
.ConfigureAwait(false);

Expand Down Expand Up @@ -89,18 +89,14 @@ await CreateRequestAsync(writer, request, targetSchema, cancellationToken)
cancellationToken)
.ConfigureAwait(false);
}
catch(Exception ex)
catch (Exception ex)
{
IError error = _errorHandler.CreateUnexpectedError(ex)
.SetCode(ErrorCodes.Stitching.UnknownRequestException)
.Build();

return QueryResultBuilder.CreateError(error);
}
finally
{
requestMessage.Dispose();
}
}

internal static async ValueTask<HttpRequestMessage> CreateRequestMessageAsync(
Expand Down Expand Up @@ -141,8 +137,14 @@ await CreateRequestAsync(writer, request, targetSchema, cancellationToken)

try
{
return await ParseResponseMessageAsync(responseMessage, cancellationToken)
.ConfigureAwait(false);
IReadOnlyDictionary<string, object?> response =
await BufferHelper.ReadAsync(
stream,
ParseResponse,
cancellationToken)
.ConfigureAwait(false);

return HttpResponseDeserializer.Deserialize(response);
}
catch
{
Expand Down Expand Up @@ -231,7 +233,7 @@ await _requestInterceptor
Utf8JsonWriter jsonWriter,
IReadOnlyDictionary<string, object?>? variables)
{
if (variables is not null && variables.Count > 0)
if (variables is not null && variables.Count > 0)
{
jsonWriter.WritePropertyName("variables");

Expand Down
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HotChocolate.Execution;
Expand Down Expand Up @@ -40,14 +41,88 @@ internal static class HttpResponseDeserializer
IReadOnlyDictionary<string, object?> serializedResult)
{
if (serializedResult.TryGetValue(_errors, out object? o)
&& o is ListValueNode l)
&& o is IReadOnlyList<object> errors)
{
foreach (var error in l.Items.OfType<ObjectValueNode>())
foreach (var obj in errors)
{
Dictionary<string, object?> dict = _converter.Convert(error);
result.AddError(ErrorBuilder.FromDictionary(dict).Build());
IError error = ErrorBuilder
.FromDictionary(DeserializeErrorObject(obj))
.Build();

result.AddError(error);
}
}
}

private static object? DeserializeErrorValue(object? value)
{
switch (value)
{
case IReadOnlyDictionary<string, object?> obj:
return DeserializeErrorObject(obj);

case IReadOnlyList<object?> list:
return DeserializeErrorList(list);

case StringValueNode sv:
return sv.Value;

case EnumValueNode ev:
return ev.Value;

case IntValueNode iv:
return iv.ToInt32();

case FloatValueNode fv:
return fv.ToDouble();

case BooleanValueNode bv:
return bv.Value;

case NullValueNode:
case null:
return null;

default:
throw new NotSupportedException();
}
}

private static Dictionary<string, object?> DeserializeErrorObject(
object obj)
{
if (obj is IReadOnlyDictionary<string, object?> dict)
{
return DeserializeErrorObject(dict);
}

throw new NotSupportedException("An error object must be a dictionary.");
}

private static Dictionary<string, object?> DeserializeErrorObject(
IReadOnlyDictionary<string, object?> obj)
{
var deserialized = new Dictionary<string, object?>();

foreach (var item in obj)
{
deserialized.Add(item.Key, DeserializeErrorValue(item.Value));
}

return deserialized;
}

private static List<object?> DeserializeErrorList(
IReadOnlyList<object?> list)
{
var deserialized = new List<object?>();

foreach (var item in list)
{
deserialized.Add(DeserializeErrorValue(item));
}

return deserialized;
}
}
}

0 comments on commit 300d01b

Please sign in to comment.