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! 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>! resolver) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! -static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.AddResolver(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Resolvers.FieldResolver! fieldResolver) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! 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! 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>! resolver) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! diff --git a/src/HotChocolate/Core/src/Execution/PublicAPI.Unshipped.txt b/src/HotChocolate/Core/src/Execution/PublicAPI.Unshipped.txt index 21dcfc87ddc..0621b1d0dce 100644 --- a/src/HotChocolate/Core/src/Execution/PublicAPI.Unshipped.txt +++ b/src/HotChocolate/Core/src/Execution/PublicAPI.Unshipped.txt @@ -112,3 +112,4 @@ virtual HotChocolate.Execution.Instrumentation.DiagnosticEventListener.StopProce *REMOVED*static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.AddResolver(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.NameString typeName, HotChocolate.NameString fieldName, TResult constantValue) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! *REMOVED*static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.ModifyOptions(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, System.Action! configure) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! *REMOVED*static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.SetOptions(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Configuration.IReadOnlySchemaOptions! options) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! +*REMOVED*static Microsoft.Extensions.DependencyInjection.SchemaRequestExecutorBuilderExtensions.AddResolver(this HotChocolate.Execution.Configuration.IRequestExecutorBuilder! builder, HotChocolate.Resolvers.FieldResolver! fieldResolver) -> HotChocolate.Execution.Configuration.IRequestExecutorBuilder! diff --git a/src/HotChocolate/Core/test/Execution.Tests/Errors/ErrorHandlerTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Errors/ErrorHandlerTests.cs index 128b1a8b7e7..ba07973474f 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Errors/ErrorHandlerTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Errors/ErrorHandlerTests.cs @@ -18,7 +18,7 @@ await ExpectError( "{ foo }", b => b .AddDocumentFromString("type Query { foo: String }") - .UseField(next => context => throw new Exception("Foo")) + .UseField(_ => _ => throw new Exception("Foo")) .Services .AddErrorFilter(error => error.WithCode("Foo123"))); } @@ -31,8 +31,8 @@ await ExpectError( "{ foo bar }", b => b .AddDocumentFromString("type Query { foo: String bar: String }") - .AddResolver("Query", "foo", ctx => throw new Exception("Foo")) - .AddResolver("Query", "bar", ctx => throw new NullReferenceException("Foo")) + .AddResolver("Query", "foo", _ => throw new Exception("Foo")) + .AddResolver("Query", "bar", _ => throw new NullReferenceException("Foo")) .AddErrorFilter(error => { if (error.Exception is NullReferenceException) @@ -63,14 +63,14 @@ public async Task AddClassErrorFilter_SchemaBuiltViaServiceExtensions_ErrorFilte { // arrange var serviceCollection = new ServiceCollection(); - var schema = await serviceCollection + IRequestExecutor schema = await serviceCollection .AddGraphQLServer() .AddErrorFilter() .AddQueryType() .BuildRequestExecutorAsync(); // act - var resp = await schema.ExecuteAsync("{ foo }"); + IExecutionResult resp = await schema.ExecuteAsync("{ foo }"); // assert resp.MatchSnapshot(); @@ -81,14 +81,14 @@ public async Task AddClassErrorFilterUsingFactory_SchemaBuiltViaServiceExtension { // arrange var serviceCollection = new ServiceCollection(); - var schema = await serviceCollection + IRequestExecutor schema = await serviceCollection .AddGraphQLServer() .AddErrorFilter(f => new DummyErrorFilter()) .AddQueryType() .BuildRequestExecutorAsync(); // act - var resp = await schema.ExecuteAsync("{ foo }"); + IExecutionResult resp = await schema.ExecuteAsync("{ foo }"); // assert resp.MatchSnapshot(); @@ -107,12 +107,43 @@ await ExpectError( .AddErrorFilter(_ => new DummyErrorFilter())); } + [Fact] + public async Task UseAggregateError_In_ErrorFilter() + { + Snapshot.FullName(); + + await ExpectError( + "{ foo }", + b => b + .AddDocumentFromString("type Query { foo: String }") + .AddResolver("Query", "foo", _ => throw new Exception("Foo")) + .Services + .AddErrorFilter(_ => new AggregateErrorFilter())); + } + + [Fact] + public async Task ReportAggregateError_In_Resolver() + { + Snapshot.FullName(); + + await ExpectError( + "{ foo }", + b => b + .AddDocumentFromString("type Query { foo: String }") + .AddResolver("Query", "foo", ctx => + { + ctx.ReportError(new AggregateError(new Error("abc"), new Error("def"))); + return "Hello"; + }), + expectedErrorCount: 2); + } + private async Task ExpectError( string query, Action configure, int expectedErrorCount = 1) { - int errors = 0; + var errors = 0; await TestHelper.ExpectError( query, @@ -129,8 +160,7 @@ await TestHelper.ExpectError( Assert.Equal(expectedErrorCount, errors); } - public class DummyErrorFilter - : IErrorFilter + public class DummyErrorFilter : IErrorFilter { public IError OnError(IError error) { @@ -138,6 +168,16 @@ public IError OnError(IError error) } } + public class AggregateErrorFilter : IErrorFilter + { + public IError OnError(IError error) + { + return new AggregateError( + error.WithCode("A"), + error.WithCode("B")); + } + } + public class Query { public string GetFoo() => throw new Exception("FooError"); diff --git a/src/HotChocolate/Core/test/Execution.Tests/Errors/__snapshots__/ErrorHandlerTests.ReportAggregateError_In_Resolver.snap b/src/HotChocolate/Core/test/Execution.Tests/Errors/__snapshots__/ErrorHandlerTests.ReportAggregateError_In_Resolver.snap new file mode 100644 index 00000000000..9778cbd6a82 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Errors/__snapshots__/ErrorHandlerTests.ReportAggregateError_In_Resolver.snap @@ -0,0 +1,13 @@ +{ + "errors": [ + { + "message": "abc" + }, + { + "message": "def" + } + ], + "data": { + "foo": "Hello" + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Errors/__snapshots__/ErrorHandlerTests.UseAggregateError_In_ErrorFilter.snap b/src/HotChocolate/Core/test/Execution.Tests/Errors/__snapshots__/ErrorHandlerTests.UseAggregateError_In_ErrorFilter.snap new file mode 100644 index 00000000000..9907edee218 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Errors/__snapshots__/ErrorHandlerTests.UseAggregateError_In_ErrorFilter.snap @@ -0,0 +1,37 @@ +{ + "errors": [ + { + "message": "Unexpected Execution Error", + "locations": [ + { + "line": 1, + "column": 3 + } + ], + "path": [ + "foo" + ], + "extensions": { + "code": "A" + } + }, + { + "message": "Unexpected Execution Error", + "locations": [ + { + "line": 1, + "column": 3 + } + ], + "path": [ + "foo" + ], + "extensions": { + "code": "B" + } + } + ], + "data": { + "foo": null + } +}