diff --git a/src/HotChocolate/Core/src/Abstractions/AggregateError.cs b/src/HotChocolate/Core/src/Abstractions/AggregateError.cs
new file mode 100644
index 00000000000..2b3ee9bcb5b
--- /dev/null
+++ b/src/HotChocolate/Core/src/Abstractions/AggregateError.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Linq;
+using HotChocolate.Properties;
+
+namespace HotChocolate
+{
+ ///
+ /// An aggregate error allows to pass a collection of error in a single error object.
+ ///
+ public class AggregateError : Error
+ {
+ public AggregateError(IEnumerable errors)
+ : base(AbstractionResources.AggregateError_Message)
+ {
+ Errors = errors.ToArray();
+ }
+
+ public AggregateError(params IError[] errors)
+ : base(AbstractionResources.AggregateError_Message)
+ {
+ Errors = errors.ToArray();
+ }
+
+ ///
+ /// Gets the actual errors.
+ ///
+ public IReadOnlyList Errors { get; }
+ }
+}
diff --git a/src/HotChocolate/Core/src/Abstractions/Error.cs b/src/HotChocolate/Core/src/Abstractions/Error.cs
index 71e0f942343..e73a7e8eea0 100644
--- a/src/HotChocolate/Core/src/Abstractions/Error.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Error.cs
@@ -8,10 +8,16 @@
namespace HotChocolate
{
- public sealed class Error : IError
+ ///
+ /// Represents a GraphQL execution error.
+ ///
+ public class Error : IError
{
private const string _codePropertyName = "code";
+ ///
+ /// Initializes a new instance of .
+ ///
public Error(
string message,
string? code = null,
@@ -77,8 +83,8 @@ public IError WithCode(string? code)
return RemoveCode();
}
- var extensions = Extensions is null
- ? new OrderedDictionary() { [_codePropertyName] = code }
+ OrderedDictionary extensions = Extensions is null
+ ? new OrderedDictionary { [_codePropertyName] = code }
: new OrderedDictionary(Extensions) { [_codePropertyName] = code };
return new Error(Message, code, Path, Locations, extensions, Exception);
}
@@ -157,7 +163,7 @@ public IError SetExtension(string key, object? value)
nameof(key));
}
- var extensions = Extensions is { }
+ OrderedDictionary extensions = Extensions is { }
? new OrderedDictionary(Extensions)
: new OrderedDictionary();
extensions[key] = value;
diff --git a/src/HotChocolate/Core/src/Abstractions/ErrorHandlerExtensions.cs b/src/HotChocolate/Core/src/Abstractions/ErrorHandlerExtensions.cs
index f9326ad00ea..06873cc6610 100644
--- a/src/HotChocolate/Core/src/Abstractions/ErrorHandlerExtensions.cs
+++ b/src/HotChocolate/Core/src/Abstractions/ErrorHandlerExtensions.cs
@@ -20,16 +20,38 @@ public static IReadOnlyList Handle(
throw new ArgumentNullException(nameof(errors));
}
- return HandleEnumerator(errorHandler, errors).ToList();
- }
+ var result = new List();
- private static IEnumerable HandleEnumerator(
- IErrorHandler errorHandler,
- IEnumerable errors)
- {
foreach (IError error in errors)
{
- yield return errorHandler.Handle(error);
+ if (error is AggregateError aggregateError)
+ {
+ foreach (var innerError in aggregateError.Errors)
+ {
+ AddProcessed(errorHandler.Handle(innerError));
+ }
+ }
+ else
+ {
+ AddProcessed(errorHandler.Handle(error));
+ }
+ }
+
+ return result;
+
+ void AddProcessed(IError error)
+ {
+ if (error is AggregateError aggregateError)
+ {
+ foreach (var innerError in aggregateError.Errors)
+ {
+ result.Add(innerError);
+ }
+ }
+ else
+ {
+ result.Add(error);
+ }
}
}
}
diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs b/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs
index e6350d0a473..f6b457e7469 100644
--- a/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs
@@ -32,11 +32,7 @@ public IQueryResultBuilder AddError(IError error)
throw new ArgumentNullException(nameof(error));
}
- if (_errors is null)
- {
- _errors = new List();
- }
-
+ _errors ??= new List();
_errors.Add(error);
return this;
}
@@ -48,11 +44,7 @@ public IQueryResultBuilder AddErrors(IEnumerable errors)
throw new ArgumentNullException(nameof(errors));
}
- if (_errors is null)
- {
- _errors = new List();
- }
-
+ _errors ??= new List();
_errors.AddRange(errors);
return this;
}
@@ -65,22 +57,14 @@ public IQueryResultBuilder ClearErrors()
public IQueryResultBuilder AddExtension(string key, object? data)
{
- if (_extensionData is null)
- {
- _extensionData = new ExtensionData();
- }
-
+ _extensionData ??= new ExtensionData();
_extensionData.Add(key, data);
return this;
}
public IQueryResultBuilder SetExtension(string key, object? data)
{
- if (_extensionData is null)
- {
- _extensionData = new ExtensionData();
- }
-
+ _extensionData ??= new ExtensionData();
_extensionData[key] = data;
return this;
}
@@ -109,22 +93,14 @@ public IQueryResultBuilder ClearExtensions()
public IQueryResultBuilder AddContextData(string key, object? data)
{
- if (_contextData is null)
- {
- _contextData = new ExtensionData();
- }
-
+ _contextData ??= new ExtensionData();
_contextData.Add(key, data);
return this;
}
public IQueryResultBuilder SetContextData(string key, object? data)
{
- if (_contextData is null)
- {
- _contextData = new ExtensionData();
- }
-
+ _contextData ??= new ExtensionData();
_contextData[key] = data;
return this;
}
@@ -157,23 +133,22 @@ public IQueryResult Create()
{
return new QueryResult(
_data,
- _errors is { } && _errors.Count > 0 ? _errors : null,
- _extensionData is { } && _extensionData.Count > 0 ? _extensionData : null,
- _contextData is { } && _contextData.Count > 0 ? _contextData : null,
+ _errors is { Count: > 0 } ? _errors : null,
+ _extensionData is { Count: > 0 } ? _extensionData : null,
+ _contextData is { Count: > 0 } ? _contextData : null,
_label,
_path,
_hasNext,
_disposable);
}
- public static QueryResultBuilder New() => new QueryResultBuilder();
+ public static QueryResultBuilder New() => new();
public static QueryResultBuilder FromResult(IQueryResult result)
{
- var builder = new QueryResultBuilder();
- builder._data = result.Data;
+ var builder = new QueryResultBuilder { _data = result.Data };
- if (result.Errors is { })
+ if (result.Errors is not null)
{
builder._errors = new List(result.Errors);
}
@@ -182,7 +157,7 @@ public static QueryResultBuilder FromResult(IQueryResult result)
{
builder._extensionData = new ExtensionData(d);
}
- else if (result.Extensions is { })
+ else if (result.Extensions is not null)
{
builder._extensionData = new ExtensionData(result.Extensions);
}
@@ -192,12 +167,15 @@ public static QueryResultBuilder FromResult(IQueryResult result)
public static IQueryResult CreateError(
IError error,
- IReadOnlyDictionary? contextData = null) =>
- new QueryResult(null, new List { error }, contextData: contextData);
+ IReadOnlyDictionary? contextData = null)
+ => error is AggregateError aggregateError
+ ? CreateError(aggregateError.Errors, contextData)
+ : new QueryResult(null, new List { error }, contextData: contextData);
+
public static IQueryResult CreateError(
IReadOnlyList errors,
- IReadOnlyDictionary? contextData = null) =>
- new QueryResult(null, errors, contextData: contextData);
+ IReadOnlyDictionary? contextData = null)
+ => new QueryResult(null, errors, contextData: contextData);
}
}
diff --git a/src/HotChocolate/Core/src/Abstractions/IError.cs b/src/HotChocolate/Core/src/Abstractions/IError.cs
index da6fcc2c92b..91c1624bad8 100644
--- a/src/HotChocolate/Core/src/Abstractions/IError.cs
+++ b/src/HotChocolate/Core/src/Abstractions/IError.cs
@@ -6,7 +6,7 @@
namespace HotChocolate
{
///
- /// Represents a schema or query error.
+ /// Represents a GraphQL execution error.
///
public interface IError
{
diff --git a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs
index 2492b75d8d1..d1174000c9f 100644
--- a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs
+++ b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs
@@ -182,5 +182,11 @@ internal static string FieldCoordinate_Parse_InvalidFormat {
return ResourceManager.GetString("FieldCoordinate_Parse_InvalidFormat", resourceCulture);
}
}
+
+ internal static string AggregateError_Message {
+ get {
+ return ResourceManager.GetString("AggregateError_Message", resourceCulture);
+ }
+ }
}
}
diff --git a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx
index 51b99b141d2..7bc5c19cd98 100644
--- a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx
+++ b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx
@@ -186,4 +186,7 @@
The string has an invalid format.
+
+ For error details look at the `Errors` property.
+
diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs
index 0fb0a900d22..7d217f84ab9 100644
--- a/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs
+++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs
@@ -10,24 +10,6 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static partial class SchemaRequestExecutorBuilderExtensions
{
- public static IRequestExecutorBuilder AddResolver(
- this IRequestExecutorBuilder builder,
- FieldResolver fieldResolver)
- {
- if (builder is null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- if (fieldResolver is null)
- {
- throw new ArgumentNullException(nameof(fieldResolver));
- }
-
- // return builder.ConfigureSchema(b => b.AddResolver(fieldResolver));
- throw new NotImplementedException();
- }
-
///
/// Adds a resolver delegate for a specific field.
///
diff --git a/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Global.cs b/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Global.cs
index b9fc20f3cf5..44650a4836d 100644
--- a/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Global.cs
+++ b/src/HotChocolate/Core/src/Execution/Processing/MiddlewareContext.Global.cs
@@ -140,10 +140,40 @@ public void ReportError(IError error)
throw new ArgumentNullException(nameof(error));
}
- error = _operationContext.ErrorHandler.Handle(error);
- _operationContext.Result.AddError(error, _selection.SyntaxNode);
- _operationContext.DiagnosticEvents.ResolverError(this, error);
- HasErrors = true;
+ if (error is AggregateError aggregateError)
+ {
+ foreach (var innerError in aggregateError.Errors)
+ {
+ ReportSingle(innerError);
+ }
+ }
+ else
+ {
+ ReportSingle(error);
+ }
+
+ void ReportSingle(IError singleError)
+ {
+ AddProcessedError(_operationContext.ErrorHandler.Handle(singleError));
+ HasErrors = true;
+ }
+
+ void AddProcessedError(IError processed)
+ {
+ if (processed is AggregateError ar)
+ {
+ foreach (var ie in ar.Errors)
+ {
+ _operationContext.Result.AddError(ie, _selection.SyntaxNode);
+ _operationContext.DiagnosticEvents.ResolverError(this, ie);
+ }
+ }
+ else
+ {
+ _operationContext.Result.AddError(processed, _selection.SyntaxNode);
+ _operationContext.DiagnosticEvents.ResolverError(this, processed);
+ }
+ }
}
public async ValueTask ResolveAsync()
diff --git a/src/HotChocolate/Core/src/Execution/Processing/OperationContext.IExecutionTaskContext.cs b/src/HotChocolate/Core/src/Execution/Processing/OperationContext.IExecutionTaskContext.cs
index cc52a2705d1..eb848b66502 100644
--- a/src/HotChocolate/Core/src/Execution/Processing/OperationContext.IExecutionTaskContext.cs
+++ b/src/HotChocolate/Core/src/Execution/Processing/OperationContext.IExecutionTaskContext.cs
@@ -32,9 +32,40 @@ private void ReportError(IExecutionTask task, IError error)
}
AssertInitialized();
- error = ErrorHandler.Handle(error);
- Result.AddError(error);
- DiagnosticEvents.TaskError(task, error);
+
+ if (error is AggregateError aggregateError)
+ {
+ foreach (var innerError in aggregateError.Errors)
+ {
+ ReportSingle(innerError);
+ }
+ }
+ else
+ {
+ ReportSingle(error);
+ }
+
+ void ReportSingle(IError singleError)
+ {
+ AddProcessedError(ErrorHandler.Handle(singleError));
+ }
+
+ void AddProcessedError(IError processed)
+ {
+ if (processed is AggregateError ar)
+ {
+ foreach (var ie in ar.Errors)
+ {
+ Result.AddError(ie);
+ DiagnosticEvents.TaskError(task, ie);
+ }
+ }
+ else
+ {
+ Result.AddError(processed);
+ DiagnosticEvents.TaskError(task, processed);
+ }
+ }
}
void IExecutionTaskContext.Completed(IExecutionTask task)
diff --git a/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.cs b/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.cs
index 019e6c47f98..587916bca86 100644
--- a/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.cs
+++ b/src/HotChocolate/Core/src/Execution/Processing/SubscriptionExecutor.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using HotChocolate.Execution.Instrumentation;
using HotChocolate.Execution.Processing.Plan;
@@ -98,7 +99,17 @@ public async Task ExecuteAsync(
await subscription.DisposeAsync().ConfigureAwait(false);
}
- return new SubscriptionResult(null, new[] { error });
+ return new SubscriptionResult(null, Unwrap(error));
+ }
+
+ IReadOnlyList Unwrap(IError error)
+ {
+ if (error is AggregateError aggregateError)
+ {
+ return aggregateError.Errors;
+ }
+
+ return new[] { error };
}
}
}
diff --git a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Tools.cs b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Tools.cs
index c17add5b96c..0064ec5cfb2 100644
--- a/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Tools.cs
+++ b/src/HotChocolate/Core/src/Execution/Processing/ValueCompletion.Tools.cs
@@ -10,9 +10,39 @@ public static void ReportError(
ISelection selection,
IError error)
{
- error = operationContext.ErrorHandler.Handle(error);
- operationContext.Result.AddError(error, selection.SyntaxNode);
- operationContext.DiagnosticEvents.ResolverError(resolverContext, error);
+ if (error is AggregateError aggregateError)
+ {
+ foreach (var innerError in aggregateError.Errors)
+ {
+ ReportSingle(innerError);
+ }
+ }
+ else
+ {
+ ReportSingle(error);
+ }
+
+ void ReportSingle(IError singleError)
+ {
+ AddProcessedError(operationContext.ErrorHandler.Handle(singleError));
+ }
+
+ void AddProcessedError(IError processed)
+ {
+ if (processed is AggregateError ar)
+ {
+ foreach (var ie in ar.Errors)
+ {
+ operationContext.Result.AddError(ie, selection.SyntaxNode);
+ operationContext.DiagnosticEvents.ResolverError(resolverContext, ie);
+ }
+ }
+ else
+ {
+ operationContext.Result.AddError(processed, selection.SyntaxNode);
+ operationContext.DiagnosticEvents.ResolverError(resolverContext, processed);
+ }
+ }
}
public static void ReportError(
diff --git a/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.ExecuteAsync.cs b/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.ExecuteAsync.cs
index d6daf4d15c3..e1d58927b8e 100644
--- a/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.ExecuteAsync.cs
+++ b/src/HotChocolate/Core/src/Execution/Processing/WorkScheduler.ExecuteAsync.cs
@@ -96,7 +96,18 @@ private void HandleError(Exception exception)
.Build();
error = _errorHandler.Handle(error);
- _result.AddError(error);
+
+ if (error is AggregateError aggregateError)
+ {
+ foreach (var innerError in aggregateError.Errors)
+ {
+ _result.AddError(innerError);
+ }
+ }
+ else
+ {
+ _result.AddError(error);
+ }
}
}
}
diff --git a/src/HotChocolate/Core/src/Execution/PublicAPI.Shipped.txt b/src/HotChocolate/Core/src/Execution/PublicAPI.Shipped.txt
index 75d6fdc7432..5057a7eeee8 100644
--- a/src/HotChocolate/Core/src/Execution/PublicAPI.Shipped.txt
+++ b/src/HotChocolate/Core/src/Execution/PublicAPI.Shipped.txt
@@ -585,7 +585,6 @@ static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExte
static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.AddResolver(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString fieldName, System.Func>! resolver) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder!
static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.AddResolver(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString fieldName, System.Func