diff --git a/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClient.cs b/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClient.cs index 0d0d8277807..6e8c13ae1a3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClient.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClient.cs @@ -31,6 +31,7 @@ public sealed class InMemorySourceSchemaClient : ISourceSchemaClient private readonly RequestExecutorProxy _executor; private readonly JsonResultFormatter _formatter; + private readonly ErrorHandlingMode? _onError; private bool _disposed; /// @@ -42,15 +43,20 @@ public sealed class InMemorySourceSchemaClient : ISourceSchemaClient /// /// The JSON result formatter used to serialize execution results. /// + /// + /// The error handling mode requested by the source schema. + /// public InMemorySourceSchemaClient( RequestExecutorProxy executor, - JsonResultFormatter formatter) + JsonResultFormatter formatter, + ErrorHandlingMode? onError = null) { ArgumentNullException.ThrowIfNull(executor); ArgumentNullException.ThrowIfNull(formatter); _executor = executor; _formatter = formatter; + _onError = onError; } /// @@ -68,7 +74,7 @@ public async ValueTask ExecuteAsync( ObjectDisposedException.ThrowIf(_disposed, this); ChunkedArrayWriter? buffer = null; - var operationRequest = BuildOperationRequest(context, request, ref buffer); + var operationRequest = BuildOperationRequest(context, request, _onError, ref buffer); try { @@ -103,7 +109,7 @@ public async IAsyncEnumerable ExecuteBatchStreamAsync( { for (var i = 0; i < requests.Length; i++) { - operationRequests[i] = BuildOperationRequest(context, requests[i], ref buffer); + operationRequests[i] = BuildOperationRequest(context, requests[i], _onError, ref buffer); } } catch @@ -221,6 +227,7 @@ public ValueTask DisposeAsync() private static IOperationRequest BuildOperationRequest( OperationPlanContext context, SourceSchemaClientRequest request, + ErrorHandlingMode? onError, ref ChunkedArrayWriter? buffer) { IFeatureCollection? features = null; @@ -236,6 +243,7 @@ private static IOperationRequest BuildOperationRequest( { return OperationRequest.FromSourceText( request.OperationSourceText, + errorHandlingMode: onError, features: features); } @@ -247,6 +255,7 @@ private static IOperationRequest BuildOperationRequest( { return OperationRequest.FromSourceText( request.OperationSourceText, + errorHandlingMode: onError, variableValues: JsonDocument.Parse(sequence)); } @@ -254,6 +263,7 @@ private static IOperationRequest BuildOperationRequest( var cleanedJson = StripFileMarkers(buffer, sequence); return OperationRequest.FromSourceText( request.OperationSourceText, + errorHandlingMode: onError, variableValues: JsonDocument.Parse(cleanedJson.AsSequence()), features: features); } @@ -279,6 +289,7 @@ private static IOperationRequest BuildOperationRequest( return VariableBatchRequest.FromSourceText( request.OperationSourceText, variableValues: JsonDocument.Parse(variableSequence), + errorHandlingMode: onError, features: features); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientConfiguration.cs b/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientConfiguration.cs index 05f1d1bab6e..f5f894792d7 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientConfiguration.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientConfiguration.cs @@ -1,3 +1,5 @@ +using HotChocolate.Language; + namespace HotChocolate.Fusion.Execution.Clients; /// @@ -11,14 +13,19 @@ public sealed class InMemorySourceSchemaClientConfiguration : ISourceSchemaClien /// /// The name of the source schema. /// The supported operation types. + /// + /// The error handling mode requested by the source schema. + /// public InMemorySourceSchemaClientConfiguration( string name, - SupportedOperationType supportedOperations = SupportedOperationType.All) + SupportedOperationType supportedOperations = SupportedOperationType.All, + ErrorHandlingMode? onError = null) { ArgumentException.ThrowIfNullOrEmpty(name); Name = name; SupportedOperations = supportedOperations; + OnError = onError; } /// @@ -26,4 +33,9 @@ public InMemorySourceSchemaClientConfiguration( /// public SupportedOperationType SupportedOperations { get; } + + /// + /// Gets the error handling mode requested by the source schema. + /// + public ErrorHandlingMode? OnError { get; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientFactory.cs b/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientFactory.cs index 0bb094e015f..aafe0d0826f 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientFactory.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Connectors.InMemory/InMemorySourceSchemaClientFactory.cs @@ -39,6 +39,6 @@ protected override ISourceSchemaClient CreateClient( InMemorySourceSchemaClientConfiguration configuration) { var proxy = new RequestExecutorProxy(_executorProvider, _executorEvents, configuration.Name); - return new InMemorySourceSchemaClient(proxy, _formatter); + return new InMemorySourceSchemaClient(proxy, _formatter, configuration.OnError); } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Configuration/Parsers/HttpSourceSchemaClientConfigurationParser.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Configuration/Parsers/HttpSourceSchemaClientConfigurationParser.cs index 6834861d732..b7f8b8f101e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Configuration/Parsers/HttpSourceSchemaClientConfigurationParser.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Configuration/Parsers/HttpSourceSchemaClientConfigurationParser.cs @@ -3,12 +3,13 @@ using System.Net.Http.Headers; using System.Text.Json; using HotChocolate.Fusion.Execution.Clients; +using HotChocolate.Language; namespace HotChocolate.Fusion.Configuration.Parsers; /// /// Built-in parser that claims the http transport and produces a -/// . +/// . /// internal sealed class HttpSourceSchemaClientConfigurationParser : ISourceSchemaClientConfigurationParser { @@ -27,16 +28,17 @@ public bool TryParse( return false; } - private static SourceSchemaHttpClientConfiguration CreateHttpClientConfiguration( + private static HttpSourceSchemaClientConfiguration CreateHttpClientConfiguration( string schemaName, JsonElement http) { - var clientName = SourceSchemaHttpClientConfiguration.DefaultClientName; + var clientName = HttpSourceSchemaClientConfiguration.DefaultClientName; var capabilities = SourceSchemaClientCapabilities.All; var supportedOperations = SupportedOperationType.All; ImmutableArray? defaultAcceptHeaderValues = null; ImmutableArray? batchingAcceptHeaderValues = null; ImmutableArray? subscriptionAcceptHeaderValues = null; + ErrorHandlingMode? onError = null; if (http.TryGetProperty("clientName", out var clientNameProperty) && clientNameProperty.ValueKind is JsonValueKind.String @@ -90,6 +92,14 @@ private static SourceSchemaHttpClientConfiguration CreateHttpClientConfiguration } } + if (capabilitiesElement.TryGetProperty("onError", out var onErrorElement) + && onErrorElement.ValueKind is JsonValueKind.String + && onErrorElement.GetString() is { } onErrorValue + && Enum.TryParse(onErrorValue, ignoreCase: true, out var parsedOnError)) + { + onError = parsedOnError; + } + if (capabilitiesElement.TryGetProperty("subscriptions", out var subscriptionsElement)) { if (subscriptionsElement.TryGetProperty("supported", out var supported) @@ -112,12 +122,13 @@ private static SourceSchemaHttpClientConfiguration CreateHttpClientConfiguration } } - return new SourceSchemaHttpClientConfiguration( + return new HttpSourceSchemaClientConfiguration( name: schemaName, httpClientName: clientName, baseAddress: new Uri(http.GetProperty("url").GetString()!), supportedOperations: supportedOperations, capabilities: capabilities, + onError: onError, defaultAcceptHeaderValues: defaultAcceptHeaderValues, batchingAcceptHeaderValues: batchingAcceptHeaderValues, subscriptionAcceptHeaderValues: subscriptionAcceptHeaderValues); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.SourceSchemaClients.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.SourceSchemaClients.cs index c8461909e44..4d7b0070c1b 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.SourceSchemaClients.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.SourceSchemaClients.cs @@ -4,6 +4,7 @@ using HotChocolate.Fusion.Execution; using HotChocolate.Fusion.Execution.Clients; using HotChocolate.Fusion.Execution.Nodes; +using HotChocolate.Language; namespace Microsoft.Extensions.DependencyInjection; @@ -27,6 +28,9 @@ public static partial class CoreFusionGatewayBuilderExtensions /// /// The client capabilities. /// + /// + /// The error handling mode requested by the source schema. + /// /// /// The Accept header values sent in case of a single, non-Subscription GraphQL request. /// @@ -54,6 +58,7 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( Uri baseAddress, SupportedOperationType supportedOperations = SupportedOperationType.All, SourceSchemaClientCapabilities capabilities = SourceSchemaClientCapabilities.All, + ErrorHandlingMode? onError = null, ImmutableArray? defaultAcceptHeaderValues = null, ImmutableArray? batchingAcceptHeaderValues = null, ImmutableArray? subscriptionAcceptHeaderValues = null, @@ -67,6 +72,7 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( baseAddress, supportedOperations, capabilities, + onError, defaultAcceptHeaderValues, batchingAcceptHeaderValues, subscriptionAcceptHeaderValues, @@ -95,6 +101,9 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( /// /// The client capabilities. /// + /// + /// The error handling mode requested by the source schema. + /// /// /// The Accept header values sent in case of a single, non-Subscription GraphQL request. /// @@ -123,6 +132,7 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( Uri baseAddress, SupportedOperationType supportedOperations = SupportedOperationType.All, SourceSchemaClientCapabilities capabilities = SourceSchemaClientCapabilities.All, + ErrorHandlingMode? onError = null, ImmutableArray? defaultAcceptHeaderValues = null, ImmutableArray? batchingAcceptHeaderValues = null, ImmutableArray? subscriptionAcceptHeaderValues = null, @@ -137,12 +147,13 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( return AddHttpClientConfiguration( builder, - new SourceSchemaHttpClientConfiguration( + new HttpSourceSchemaClientConfiguration( name, httpClientName, baseAddress, supportedOperations, capabilities, + onError, defaultAcceptHeaderValues, batchingAcceptHeaderValues, subscriptionAcceptHeaderValues, @@ -165,7 +176,7 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( /// public static IFusionGatewayBuilder AddHttpClientConfiguration( this IFusionGatewayBuilder builder, - SourceSchemaHttpClientConfiguration configuration) + HttpSourceSchemaClientConfiguration configuration) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(configuration); @@ -187,7 +198,7 @@ public static IFusionGatewayBuilder AddHttpClientConfiguration( /// public static IFusionGatewayBuilder AddHttpClientConfiguration( this IFusionGatewayBuilder builder, - Func create) + Func create) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(create); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/SourceSchemaHttpClient.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs similarity index 95% rename from src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/SourceSchemaHttpClient.cs rename to src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs index 5bf48cca33e..b891227bde3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/SourceSchemaHttpClient.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs @@ -16,33 +16,35 @@ namespace HotChocolate.Fusion.Execution.Clients; /// /// HTTP-based implementation of that sends GraphQL operations /// to a downstream service over HTTP. Supports single requests, Apollo-style request batching, -/// and variable batching depending on the configured . +/// and variable batching depending on the configured . /// -public sealed class SourceSchemaHttpClient : ISourceSchemaClient +public sealed class HttpSourceSchemaClient : ISourceSchemaClient { private static readonly Uri s_unknownUri = new("http://unknown"); private static ReadOnlySpan VariableIndex => "variableIndex"u8; private static ReadOnlySpan RequestIndex => "requestIndex"u8; private readonly GraphQLHttpClient _client; - private readonly SourceSchemaHttpClientConfiguration _configuration; + private readonly HttpSourceSchemaClientConfiguration _configuration; + private readonly ErrorHandlingMode? _onError; private readonly bool _supportsVariableBatching; private bool _disposed; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// The underlying HTTP client used to send requests. /// The transport configuration for this source schema. - public SourceSchemaHttpClient( + public HttpSourceSchemaClient( GraphQLHttpClient client, - SourceSchemaHttpClientConfiguration configuration) + HttpSourceSchemaClientConfiguration configuration) { ArgumentNullException.ThrowIfNull(client); ArgumentNullException.ThrowIfNull(configuration); _client = client; _configuration = configuration; + _onError = configuration.OnError; var capabilities = configuration.Capabilities; @@ -103,7 +105,7 @@ public IAsyncEnumerable ExecuteBatchStreamAsync( if (ContainsSubscriptionRequest(requests)) { throw new InvalidOperationException( - FusionExecutionResources.SourceSchemaHttpClient_SubscriptionBatchNotSupported); + FusionExecutionResources.HttpSourceSchemaClient_SubscriptionBatchNotSupported); } var requiresFileUpload = requests[0].RequiresFileUpload; @@ -244,7 +246,7 @@ private async IAsyncEnumerable ExecuteBatchStreamAsync( result.Dispose(); throw new InvalidOperationException( string.Format( - FusionExecutionResources.SourceSchemaHttpClient_InvalidVariableIndex, + FusionExecutionResources.HttpSourceSchemaClient_InvalidVariableIndex, variableIndex, request.Node.Id)); } @@ -297,7 +299,7 @@ private GraphQLHttpRequest CreateHttpRequest( switch (originalRequest.Variables.Length) { case 0: - httpRequest = new GraphQLHttpRequest(CreateSingleRequest(context, originalRequest, ref buffer)) + httpRequest = new GraphQLHttpRequest(CreateSingleRequest(context, originalRequest, _onError, ref buffer)) { Uri = _configuration.BaseAddress, AcceptHeaderValue = defaultAcceptHeader @@ -305,7 +307,7 @@ private GraphQLHttpRequest CreateHttpRequest( break; case 1: - httpRequest = new GraphQLHttpRequest(CreateSingleRequest(context, originalRequest, ref buffer)) + httpRequest = new GraphQLHttpRequest(CreateSingleRequest(context, originalRequest, _onError, ref buffer)) { Uri = _configuration.BaseAddress, AcceptHeaderValue = defaultAcceptHeader, @@ -317,7 +319,7 @@ private GraphQLHttpRequest CreateHttpRequest( if (!originalRequest.RequiresFileUpload && _supportsVariableBatching) { httpRequest = - new GraphQLHttpRequest(CreateVariableBatchRequest(operationSourceText, originalRequest)) + new GraphQLHttpRequest(CreateVariableBatchRequest(operationSourceText, originalRequest, _onError)) { Uri = _configuration.BaseAddress, AcceptHeaderValue = _configuration.BatchingAcceptHeaderValue @@ -326,7 +328,7 @@ private GraphQLHttpRequest CreateHttpRequest( else { httpRequest = - new GraphQLHttpRequest(CreateOperationBatchRequest(context, originalRequest, ref buffer)) + new GraphQLHttpRequest(CreateOperationBatchRequest(context, originalRequest, _onError, ref buffer)) { Uri = _configuration.BaseAddress, AcceptHeaderValue = _configuration.BatchingAcceptHeaderValue, @@ -374,6 +376,7 @@ private GraphQLHttpRequest CreateHttpBatchRequest( CreateBatchUploadRequest( sourceRequest, VariableValues.Empty, + _onError, buffer, fileLookup, fileEntries)); @@ -385,6 +388,7 @@ private GraphQLHttpRequest CreateHttpBatchRequest( CreateBatchUploadRequest( sourceRequest, sourceRequest.Variables[0], + _onError, buffer, fileLookup, fileEntries, @@ -399,6 +403,7 @@ private GraphQLHttpRequest CreateHttpBatchRequest( CreateBatchUploadRequest( sourceRequest, sourceRequest.Variables[j], + _onError, buffer, fileLookup, fileEntries, @@ -441,14 +446,14 @@ private GraphQLHttpRequest CreateHttpBatchRequest( switch (sourceRequest.Variables.Length) { case 0 or 1: - batchRequests.Add(CreateSingleRequest(context, sourceRequest, ref buffer)); + batchRequests.Add(CreateSingleRequest(context, sourceRequest, _onError, ref buffer)); break; default: if (_supportsVariableBatching) { batchRequests.Add(CreateVariableBatchRequest( - sourceRequest.OperationSourceText, sourceRequest)); + sourceRequest.OperationSourceText, sourceRequest, _onError)); } else { @@ -458,7 +463,7 @@ private GraphQLHttpRequest CreateHttpBatchRequest( sourceRequest.OperationSourceText, id: null, operationName: null, - onError: null, + onError: _onError, variables: sourceRequest.Variables[j], extensions: JsonSegment.Empty)); } @@ -480,6 +485,7 @@ private GraphQLHttpRequest CreateHttpBatchRequest( private static OperationRequest CreateSingleRequest( OperationPlanContext context, SourceSchemaClientRequest originalRequest, + ErrorHandlingMode? onError, ref ChunkedArrayWriter? writer) { var variables = originalRequest.Variables.IsDefaultOrEmpty @@ -496,7 +502,7 @@ private static OperationRequest CreateSingleRequest( originalRequest.OperationSourceText, id: null, operationName: null, - onError: null, + onError: onError, variables: variables with { Values = cleanedJson }, extensions: JsonSegment.Empty, fileMap: fileMap); @@ -506,7 +512,7 @@ private static OperationRequest CreateSingleRequest( originalRequest.OperationSourceText, id: null, operationName: null, - onError: null, + onError: onError, variables: variables, extensions: JsonSegment.Empty); } @@ -514,6 +520,7 @@ private static OperationRequest CreateSingleRequest( private static OperationRequest CreateBatchUploadRequest( SourceSchemaClientRequest originalRequest, VariableValues variables, + ErrorHandlingMode? onError, ChunkedArrayWriter writer, IFileLookup fileLookup, ImmutableArray.Builder fileEntries, @@ -525,7 +532,7 @@ private static OperationRequest CreateBatchUploadRequest( originalRequest.OperationSourceText, id: null, operationName: null, - onError: null, + onError: onError, variables: variables with { Values = cleanedJson }, extensions: JsonSegment.Empty); } @@ -533,6 +540,7 @@ private static OperationRequest CreateBatchUploadRequest( private static OperationBatchRequest CreateOperationBatchRequest( OperationPlanContext context, SourceSchemaClientRequest originalRequest, + ErrorHandlingMode? onError, ref ChunkedArrayWriter? writer) { if (originalRequest.RequiresFileUpload @@ -547,6 +555,7 @@ private static OperationBatchRequest CreateOperationBatchRequest( requests[i] = CreateBatchUploadRequest( originalRequest, originalRequest.Variables[i], + onError, writer, fileLookup, fileEntries, @@ -567,7 +576,7 @@ private static OperationBatchRequest CreateOperationBatchRequest( originalRequest.OperationSourceText, id: null, operationName: null, - onError: null, + onError: onError, variables: originalRequest.Variables[i], extensions: JsonSegment.Empty); } @@ -578,13 +587,14 @@ private static OperationBatchRequest CreateOperationBatchRequest( private static VariableBatchRequest CreateVariableBatchRequest( string operationSourceText, - SourceSchemaClientRequest originalRequest) + SourceSchemaClientRequest originalRequest, + ErrorHandlingMode? onError) { return new VariableBatchRequest( operationSourceText, id: null, operationName: null, - onError: null, + onError: onError, variables: originalRequest.Variables, extensions: JsonSegment.Empty); } @@ -706,8 +716,8 @@ public ValueTask DisposeAsync() } /// - /// Attaches and - /// callbacks to + /// Attaches and + /// callbacks to /// the HTTP request. /// private void ConfigureCallbacks( @@ -755,7 +765,7 @@ private static bool ContainsSubscriptionRequest( /// private sealed class Response( OperationPlanContext context, - SourceSchemaHttpClientConfiguration configuration, + HttpSourceSchemaClientConfiguration configuration, bool supportsVariableBatching, ExecutionNode node, OperationType operation, diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/SourceSchemaHttpClientConfiguration.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientConfiguration.cs similarity index 91% rename from src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/SourceSchemaHttpClientConfiguration.cs rename to src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientConfiguration.cs index 3e36b7f7442..a1a798345e7 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/SourceSchemaHttpClientConfiguration.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientConfiguration.cs @@ -1,18 +1,19 @@ using System.Collections.Immutable; using System.Net.Http.Headers; using HotChocolate.Fusion.Execution.Nodes; +using HotChocolate.Language; namespace HotChocolate.Fusion.Execution.Clients; /// /// Represents the configuration for fetching data from a source schema over HTTP. /// -public class SourceSchemaHttpClientConfiguration : ISourceSchemaClientConfiguration +public class HttpSourceSchemaClientConfiguration : ISourceSchemaClientConfiguration { public const string DefaultClientName = "fusion"; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// The name of the source schema. @@ -26,6 +27,9 @@ public class SourceSchemaHttpClientConfiguration : ISourceSchemaClientConfigurat /// /// The client capabilities. /// + /// + /// The error handling mode requested by the source schema. + /// /// /// The Accept header values sent in case of a single, non-Subscription GraphQL request. /// @@ -44,11 +48,12 @@ public class SourceSchemaHttpClientConfiguration : ISourceSchemaClientConfigurat /// /// The action to call after a was materialized. /// - public SourceSchemaHttpClientConfiguration( + public HttpSourceSchemaClientConfiguration( string name, Uri baseAddress, SupportedOperationType supportedOperations = SupportedOperationType.All, SourceSchemaClientCapabilities capabilities = SourceSchemaClientCapabilities.All, + ErrorHandlingMode? onError = null, ImmutableArray? defaultAcceptHeaderValues = null, ImmutableArray? batchingAcceptHeaderValues = null, ImmutableArray? subscriptionAcceptHeaderValues = null, @@ -61,6 +66,7 @@ public SourceSchemaHttpClientConfiguration( baseAddress, supportedOperations, capabilities, + onError, defaultAcceptHeaderValues, batchingAcceptHeaderValues, subscriptionAcceptHeaderValues, @@ -71,7 +77,7 @@ public SourceSchemaHttpClientConfiguration( } /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// The name of the source schema. @@ -88,6 +94,9 @@ public SourceSchemaHttpClientConfiguration( /// /// The client capabilities. /// + /// + /// The error handling mode requested by the source schema. + /// /// /// The Accept header values sent in case of a single, non-Subscription GraphQL request. /// @@ -106,12 +115,13 @@ public SourceSchemaHttpClientConfiguration( /// /// The action to call after a was materialized. /// - public SourceSchemaHttpClientConfiguration( + public HttpSourceSchemaClientConfiguration( string name, string httpClientName, Uri baseAddress, SupportedOperationType supportedOperations = SupportedOperationType.All, SourceSchemaClientCapabilities capabilities = SourceSchemaClientCapabilities.All, + ErrorHandlingMode? onError = null, ImmutableArray? defaultAcceptHeaderValues = null, ImmutableArray? batchingAcceptHeaderValues = null, ImmutableArray? subscriptionAcceptHeaderValues = null, @@ -128,6 +138,7 @@ public SourceSchemaHttpClientConfiguration( BaseAddress = baseAddress; SupportedOperations = supportedOperations; Capabilities = capabilities; + OnError = onError; DefaultAcceptHeaderValue = defaultAcceptHeaderValues is null ? AcceptContentTypes.DefaultHeader @@ -171,6 +182,11 @@ public SourceSchemaHttpClientConfiguration( /// public SourceSchemaClientCapabilities Capabilities { get; } + /// + /// Gets the error handling mode requested by the source schema. + /// + public ErrorHandlingMode? OnError { get; } + /// /// Gets a pre-formatted Accept header string for single, non-Subscription GraphQL requests. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientFactory.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientFactory.cs index 44e3bc3ed9e..714790180ec 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientFactory.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClientFactory.cs @@ -3,7 +3,7 @@ namespace HotChocolate.Fusion.Execution.Clients; internal sealed class HttpSourceSchemaClientFactory - : SourceSchemaClientFactory + : SourceSchemaClientFactory { private readonly IHttpClientFactory _httpClientFactory; @@ -14,12 +14,12 @@ public HttpSourceSchemaClientFactory(IHttpClientFactory httpClientFactory) } protected override ISourceSchemaClient CreateClient( - SourceSchemaHttpClientConfiguration configuration) + HttpSourceSchemaClientConfiguration configuration) { var httpClient = _httpClientFactory.CreateClient(configuration.HttpClientName); httpClient.BaseAddress = configuration.BaseAddress; - return new SourceSchemaHttpClient( + return new HttpSourceSchemaClient( GraphQLHttpClient.Create(httpClient, disposeHttpClient: true), configuration); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/RequestCallbackState.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/RequestCallbackState.cs index 7ee507da20f..0bcdfe88692 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/RequestCallbackState.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/RequestCallbackState.cs @@ -4,8 +4,8 @@ namespace HotChocolate.Fusion.Execution.Clients; /// /// Carries the context needed by the transport-level request hooks -/// ( and -/// ). +/// ( and +/// ). /// Stored on /// so the static hook delegates can access it without capturing. /// @@ -14,7 +14,7 @@ public readonly struct RequestCallbackState public RequestCallbackState( OperationPlanContext context, ExecutionNode node, - SourceSchemaHttpClientConfiguration configuration) + HttpSourceSchemaClientConfiguration configuration) { Context = context; Node = node; @@ -25,5 +25,5 @@ public RequestCallbackState( public ExecutionNode Node { get; } - public SourceSchemaHttpClientConfiguration Configuration { get; } + public HttpSourceSchemaClientConfiguration Configuration { get; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/ThrowHelper.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/ThrowHelper.cs index 8f0caacece9..e14e23f204a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/ThrowHelper.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/ThrowHelper.cs @@ -30,12 +30,12 @@ public static InvalidOperationException SingleOperationRequired() public static InvalidOperationException RequestIndexOutOfRange(int requestIndex) => new(string.Format( - FusionExecutionResources.SourceSchemaHttpClient_InvalidRequestIndex, + FusionExecutionResources.HttpSourceSchemaClient_InvalidRequestIndex, requestIndex)); public static InvalidOperationException VariableIndexOutOfRange(int variableIndex) => new(string.Format( - FusionExecutionResources.SourceSchemaHttpClient_VariableIndexOutOfRange, + FusionExecutionResources.HttpSourceSchemaClient_VariableIndexOutOfRange, variableIndex)); public static ArgumentException InvalidClientConfiguration(Type expected, Type actual) diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.Designer.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.Designer.cs index e6fecdecdd1..94d2c29afa8 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.Designer.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.Designer.cs @@ -99,39 +99,39 @@ internal static string SourceSchemaRequestDispatcher_NodeNotRegisteredInGroup { } } - internal static string SourceSchemaHttpClient_SubscriptionBatchNotSupported { + internal static string HttpSourceSchemaClient_SubscriptionBatchNotSupported { get { - return ResourceManager.GetString("SourceSchemaHttpClient_SubscriptionBatchNotSupported", resourceCulture); + return ResourceManager.GetString("HttpSourceSchemaClient_SubscriptionBatchNotSupported", resourceCulture); } } - internal static string SourceSchemaHttpClient_InvalidRequestIndex { + internal static string HttpSourceSchemaClient_InvalidRequestIndex { get { - return ResourceManager.GetString("SourceSchemaHttpClient_InvalidRequestIndex", resourceCulture); + return ResourceManager.GetString("HttpSourceSchemaClient_InvalidRequestIndex", resourceCulture); } } - internal static string SourceSchemaHttpClient_NoResponseChannelForNode { + internal static string HttpSourceSchemaClient_NoResponseChannelForNode { get { - return ResourceManager.GetString("SourceSchemaHttpClient_NoResponseChannelForNode", resourceCulture); + return ResourceManager.GetString("HttpSourceSchemaClient_NoResponseChannelForNode", resourceCulture); } } - internal static string SourceSchemaHttpClient_InvalidVariableIndex { + internal static string HttpSourceSchemaClient_InvalidVariableIndex { get { - return ResourceManager.GetString("SourceSchemaHttpClient_InvalidVariableIndex", resourceCulture); + return ResourceManager.GetString("HttpSourceSchemaClient_InvalidVariableIndex", resourceCulture); } } - internal static string SourceSchemaHttpClient_VariableIndexOutOfRange { + internal static string HttpSourceSchemaClient_VariableIndexOutOfRange { get { - return ResourceManager.GetString("SourceSchemaHttpClient_VariableIndexOutOfRange", resourceCulture); + return ResourceManager.GetString("HttpSourceSchemaClient_VariableIndexOutOfRange", resourceCulture); } } - internal static string SourceSchemaHttpClient_NoResultForNode { + internal static string HttpSourceSchemaClient_NoResultForNode { get { - return ResourceManager.GetString("SourceSchemaHttpClient_NoResultForNode", resourceCulture); + return ResourceManager.GetString("HttpSourceSchemaClient_NoResultForNode", resourceCulture); } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.resx b/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.resx index b8857db71d4..4c51a8dd2b9 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.resx +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Properties/FusionExecutionResources.resx @@ -45,22 +45,22 @@ The execution node with id '{0}' is registered to be part of batching group '{1}', but no request was submitted for that node. - + Subscription requests are not supported by batch execution. - + The batch response contains an invalid requestIndex '{0}'. - + No response channel exists for node '{0}'. - + The batch response contains an invalid variableIndex '{0}' for node '{1}'. - + The batch response contains an out-of-range variableIndex '{0}'. - + The batch response does not contain any result for node '{0}'. diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.CreateSourceSchema.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.CreateSourceSchema.cs index 97c3ecb5213..89d86275da2 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.CreateSourceSchema.cs +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.CreateSourceSchema.cs @@ -4,6 +4,7 @@ using HotChocolate.Configuration; using HotChocolate.Execution.Configuration; using HotChocolate.Fusion.Execution.Clients; +using HotChocolate.Language; using HotChocolate.Types.Descriptors; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; @@ -23,6 +24,7 @@ protected TestServer CreateSourceSchema( bool isOffline = false, bool isTimingOut = false, SourceSchemaClientCapabilities capabilities = SourceSchemaClientCapabilities.All, + ErrorHandlingMode? onError = null, ImmutableArray? defaultAcceptHeaderValues = null, ImmutableArray? batchingAcceptHeaderValues = null, ImmutableArray? subscriptionAcceptHeaderValues = null, @@ -54,6 +56,7 @@ protected TestServer CreateSourceSchema( opt.ConfigureHttpClient = configureHttpClient; opt.MockHttpResponse = mockHttpResponse; opt.Capabilities = capabilities; + opt.OnError = onError; opt.DefaultAcceptHeaderValues = defaultAcceptHeaderValues; opt.BatchingAcceptHeaderValues = batchingAcceptHeaderValues; opt.SubscriptionAcceptHeaderValues = subscriptionAcceptHeaderValues; @@ -70,6 +73,7 @@ protected TestServer CreateSourceSchema( Action? configureHttpClient = null, HttpClient? httpClient = null, SourceSchemaClientCapabilities capabilities = SourceSchemaClientCapabilities.All, + ErrorHandlingMode? onError = null, ImmutableArray? defaultAcceptHeaderValues = null, ImmutableArray? batchingAcceptHeaderValues = null, ImmutableArray? subscriptionAcceptHeaderValues = null, @@ -96,6 +100,7 @@ protected TestServer CreateSourceSchema( opt.HttpClient = httpClient; opt.MockHttpResponse = mockHttpResponse; opt.Capabilities = capabilities; + opt.OnError = onError; opt.DefaultAcceptHeaderValues = defaultAcceptHeaderValues; opt.BatchingAcceptHeaderValues = batchingAcceptHeaderValues; opt.SubscriptionAcceptHeaderValues = subscriptionAcceptHeaderValues; diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.MatchSnapshot.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.MatchSnapshot.cs index 367cda5dccf..06dee9edcad 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.MatchSnapshot.cs +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.MatchSnapshot.cs @@ -347,6 +347,8 @@ private static void WriteSourceSchema( writer.Unindent(); } + TryWriteOnError(writer, item); + writer.Unindent(); } @@ -354,6 +356,8 @@ private static void WriteSourceSchema( } else { + TryWriteOnError(writer, jsonBody.RootElement); + writer.WriteLine("document: |"); writer.Indent(); @@ -523,6 +527,22 @@ private static void WriteOperationRequest( } } + private static void TryWriteOnError(CodeWriter writer, JsonElement requestElement) + { + if (requestElement.ValueKind is not JsonValueKind.Object + || !requestElement.TryGetProperty("onError", out var onErrorProperty) + || onErrorProperty.ValueKind is not JsonValueKind.String + || !Enum.TryParse( + onErrorProperty.GetString(), + ignoreCase: true, + out var onError)) + { + return; + } + + writer.WriteLine("onError: {0}", onError); + } + private static void WriteFormattedJson(CodeWriter writer, JsonElement json) { var memoryStream = new MemoryStream(); diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.cs index 2c7fc333973..d519e1ff63d 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.cs +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/FusionTestBase.cs @@ -15,6 +15,7 @@ using HotChocolate.Fusion.Execution.Nodes; using HotChocolate.Fusion.Logging; using HotChocolate.Fusion.Options; +using HotChocolate.Language; using HotChocolate.Transport.Http; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -69,6 +70,7 @@ protected async Task CreateCompositeSchemaAsync( name, new Uri("http://localhost:5000/graphql"), capabilities: sourceSchemaOptions.Capabilities, + onError: sourceSchemaOptions.OnError, defaultAcceptHeaderValues: sourceSchemaOptions.DefaultAcceptHeaderValues, batchingAcceptHeaderValues: sourceSchemaOptions.BatchingAcceptHeaderValues, subscriptionAcceptHeaderValues: sourceSchemaOptions.SubscriptionAcceptHeaderValues, @@ -336,6 +338,8 @@ private sealed class SourceSchemaOptions public ImmutableArray? BatchingAcceptHeaderValues { get; set; } public ImmutableArray? SubscriptionAcceptHeaderValues { get; set; } + + public ErrorHandlingMode? OnError { get; set; } } private sealed class OperationPlanHttpRequestInterceptor : DefaultHttpRequestInterceptor diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs index 4347a8df220..6ec46af7ccd 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs @@ -49,7 +49,7 @@ public async Task Error_On_Root_Field(ErrorHandlingMode onError) } [Fact] - public async Task SchemaDefault_Null_AppliesWithoutPerRequestOverride() + public async Task OnError_SchemaDefault_Null_AppliesWithoutPerRequestOverride() { // arrange using var server1 = CreateSourceSchema( @@ -83,6 +83,40 @@ public async Task SchemaDefault_Null_AppliesWithoutPerRequestOverride() await MatchSnapshotAsync(gateway, request, result); } + [Fact] + public async Task OnError_Null_OnSourceSchema_Forwards_To_Subgraph_Request() + { + // arrange + using var server1 = CreateSourceSchema( + "A", + b => b.AddQueryType(), + onError: ErrorHandlingMode.Null); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("A", server1) + ]); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + var request = new OperationRequest( + """ + { + productById(id: 1) { + name + } + } + """); + + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql")); + + // assert + await MatchSnapshotAsync(gateway, request, result); + } + [Theory] [InlineData(ErrorHandlingMode.Propagate)] [InlineData(ErrorHandlingMode.Null)] diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.OnError_Null_OnSourceSchema_Forwards_To_Subgraph_Request.yaml b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.OnError_Null_OnSourceSchema_Forwards_To_Subgraph_Request.yaml new file mode 100644 index 00000000000..2b7ece205f6 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.OnError_Null_OnSourceSchema_Forwards_To_Subgraph_Request.yaml @@ -0,0 +1,85 @@ +title: OnError_Null_OnSourceSchema_Forwards_To_Subgraph_Request +request: + document: | + { + productById(id: 1) { + name + } + } +response: + body: | + { + "data": { + "productById": null + }, + "errors": [ + { + "message": "Could not resolve Product", + "path": [ + "productById" + ] + } + ] + } +sourceSchemas: + - name: A + schema: | + schema { + query: Query + } + + type Product { + name: String! + id: Int! + } + + type Query { + productById(id: Int!): Product @lookup + } + interactions: + - request: + accept: application/graphql-response+json; charset=utf-8, application/json; charset=utf-8, application/jsonl; charset=utf-8, text/event-stream; charset=utf-8 + onError: Null + document: | + query Op_97fd38fc_1 { + productById(id: 1) { + name + } + } + response: + results: + - | + { + "errors": [ + { + "message": "Could not resolve Product", + "path": [ + "productById" + ] + } + ], + "data": { + "productById": null + } + } +operationPlan: + operation: + - document: | + { + productById(id: 1) { + name + } + } + hash: 97fd38fcea394bc6badd7ea22c6de660 + searchSpace: 1 + expandedNodes: 1 + nodes: + - id: 1 + type: Operation + schema: A + operation: | + query Op_97fd38fc_1 { + productById(id: 1) { + name + } + } diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.SchemaDefault_Null_AppliesWithoutPerRequestOverride.yaml b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.OnError_SchemaDefault_Null_AppliesWithoutPerRequestOverride.yaml similarity index 96% rename from src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.SchemaDefault_Null_AppliesWithoutPerRequestOverride.yaml rename to src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.OnError_SchemaDefault_Null_AppliesWithoutPerRequestOverride.yaml index 96c15b68ef7..674c7606273 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.SchemaDefault_Null_AppliesWithoutPerRequestOverride.yaml +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/SourceSchemaErrorTests.OnError_SchemaDefault_Null_AppliesWithoutPerRequestOverride.yaml @@ -1,4 +1,4 @@ -title: SchemaDefault_Null_AppliesWithoutPerRequestOverride +title: OnError_SchemaDefault_Null_AppliesWithoutPerRequestOverride request: document: | { diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityExecutionDiagnosticListenerTests.Source_Schema_Transport_Error.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityExecutionDiagnosticListenerTests.Source_Schema_Transport_Error.snap index 26a84ad5d80..43f91265ce4 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityExecutionDiagnosticListenerTests.Source_Schema_Transport_Error.snap +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityExecutionDiagnosticListenerTests.Source_Schema_Transport_Error.snap @@ -157,7 +157,7 @@ }, { "Key": "exception.stacktrace", - "Value": "System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).\n at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\n at HotChocolate.Fusion.Transport.Http.GraphQLHttpResponse.ReadAsResultAsync(CancellationToken cancellationToken) in GraphQLHttpResponse.cs\n at HotChocolate.Fusion.Execution.Clients.SourceSchemaHttpClient.Response.ReadAsResultStreamCoreAsync(CancellationToken cancellationToken)+MoveNext() in SourceSchemaHttpClient.cs\n at HotChocolate.Fusion.Execution.Clients.SourceSchemaHttpClient.Response.ReadAsResultStreamCoreAsync(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource.GetResult()\n at HotChocolate.Fusion.Execution.Clients.SourceSchemaHttpClient.Response.WithResultCallback(IAsyncEnumerable`1 results, OperationPlanContext context, ExecutionNode node, Action`3 onSourceSchemaResult, CancellationToken cancellationToken)+MoveNext() in SourceSchemaHttpClient.cs\n at HotChocolate.Fusion.Execution.Clients.SourceSchemaHttpClient.Response.WithResultCallback(IAsyncEnumerable`1 results, OperationPlanContext context, ExecutionNode node, Action`3 onSourceSchemaResult, CancellationToken cancellationToken)+MoveNext() in SourceSchemaHttpClient.cs\n at HotChocolate.Fusion.Execution.Clients.SourceSchemaHttpClient.Response.WithResultCallback(IAsyncEnumerable`1 results, OperationPlanContext context, ExecutionNode node, Action`3 onSourceSchemaResult, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource.GetResult()\n at HotChocolate.Fusion.Execution.Nodes.OperationExecutionNode.OnExecuteAsync(OperationPlanContext context, CancellationToken cancellationToken) in OperationExecutionNode.cs\n at HotChocolate.Fusion.Execution.Nodes.OperationExecutionNode.OnExecuteAsync(OperationPlanContext context, CancellationToken cancellationToken) in OperationExecutionNode.cs" + "Value": "System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).\n at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\n at HotChocolate.Fusion.Transport.Http.GraphQLHttpResponse.ReadAsResultAsync(CancellationToken cancellationToken) in GraphQLHttpResponse.cs\n at HotChocolate.Fusion.Execution.Clients.HttpSourceSchemaClient.Response.ReadAsResultStreamCoreAsync(CancellationToken cancellationToken)+MoveNext() in HttpSourceSchemaClient.cs\n at HotChocolate.Fusion.Execution.Clients.HttpSourceSchemaClient.Response.ReadAsResultStreamCoreAsync(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource.GetResult()\n at HotChocolate.Fusion.Execution.Clients.HttpSourceSchemaClient.Response.WithResultCallback(IAsyncEnumerable`1 results, OperationPlanContext context, ExecutionNode node, Action`3 onSourceSchemaResult, CancellationToken cancellationToken)+MoveNext() in HttpSourceSchemaClient.cs\n at HotChocolate.Fusion.Execution.Clients.HttpSourceSchemaClient.Response.WithResultCallback(IAsyncEnumerable`1 results, OperationPlanContext context, ExecutionNode node, Action`3 onSourceSchemaResult, CancellationToken cancellationToken)+MoveNext() in HttpSourceSchemaClient.cs\n at HotChocolate.Fusion.Execution.Clients.HttpSourceSchemaClient.Response.WithResultCallback(IAsyncEnumerable`1 results, OperationPlanContext context, ExecutionNode node, Action`3 onSourceSchemaResult, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource.GetResult()\n at HotChocolate.Fusion.Execution.Nodes.OperationExecutionNode.OnExecuteAsync(OperationPlanContext context, CancellationToken cancellationToken) in OperationExecutionNode.cs\n at HotChocolate.Fusion.Execution.Nodes.OperationExecutionNode.OnExecuteAsync(OperationPlanContext context, CancellationToken cancellationToken) in OperationExecutionNode.cs" }, { "Key": "exception.type", diff --git a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Configuration/HttpSourceSchemaClientConfigurationParserTests.cs b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Configuration/HttpSourceSchemaClientConfigurationParserTests.cs new file mode 100644 index 00000000000..effa0f3599e --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Configuration/HttpSourceSchemaClientConfigurationParserTests.cs @@ -0,0 +1,507 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using HotChocolate.Buffers; +using HotChocolate.Features; +using HotChocolate.Fusion.Configuration.Parsers; +using HotChocolate.Fusion.Execution; +using HotChocolate.Fusion.Execution.Clients; +using HotChocolate.Language; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Fusion.Configuration; + +public class HttpSourceSchemaClientConfigurationParserTests : FusionTestBase +{ + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Return_False_When_Http_Transport_Missing() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "a": { + "transports": { + "websockets": { "url": "ws://localhost:5000/graphql" } + } + } + } + } + """, + "a"); + var transport = GetTransportProperty(sourceSchema, "websockets"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.False(claimed); + Assert.Null(configuration); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Produce_Configuration_When_Http_Transport_Present() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "clientName": "products-client" + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Summarize(http).MatchInlineSnapshot( + """ + Name: products + HttpClientName: products-client + BaseAddress: http://localhost:5000/graphql + SupportedOperations: All + Capabilities: All + OnError: + """); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Disable_VariableBatching_Capability_When_Configured() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": { + "batching": { + "variableBatching": false + } + } + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Equal(SourceSchemaClientCapabilities.RequestBatching, http.Capabilities); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Disable_Subscription_Operations_When_Configured() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": { + "subscriptions": { + "supported": false + } + } + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Equal( + SupportedOperationType.Query | SupportedOperationType.Mutation, + http.SupportedOperations); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Set_OnError_To_Null_Mode_When_Configured_As_NULL() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": { + "onError": "NULL" + } + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Equal(ErrorHandlingMode.Null, http.OnError); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Set_OnError_To_Propagate_When_Configured_As_PROPAGATE() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": { + "onError": "PROPAGATE" + } + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Equal(ErrorHandlingMode.Propagate, http.OnError); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Leave_OnError_Null_When_Property_Missing() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": {} + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Null(http.OnError); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Leave_OnError_Null_When_Value_Is_Json_Null() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": { + "onError": null + } + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Null(http.OnError); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Leave_OnError_Null_When_Value_Is_Unknown_String() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql", + "capabilities": { + "onError": "BOGUS" + } + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Null(http.OnError); + } + + [Fact] + public void HttpSourceSchemaClientConfigurationParser_Should_Default_ClientName_When_Not_Provided() + { + // arrange + var sourceSchema = GetSourceSchemaProperty( + """ + { + "sourceSchemas": { + "products": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql" + } + } + } + } + } + """, + "products"); + var transport = GetTransportProperty(sourceSchema, "http"); + var parser = new HttpSourceSchemaClientConfigurationParser(); + + // act + var claimed = parser.TryParse(sourceSchema, transport, out var configuration); + + // assert + Assert.True(claimed); + var http = Assert.IsType(configuration); + Assert.Equal(HttpSourceSchemaClientConfiguration.DefaultClientName, http.HttpClientName); + } + + [Fact] + public async Task CreateClientConfigurations_Should_Throw_When_No_Parser_Claims_Schema() + { + // arrange + var config = CreateConfigurationWithSettings( + """ + { + "sourceSchemas": { + "a": { + "transports": { + "xyz": { "url": "xyz://localhost" } + } + } + } + } + """); + + var configProvider = new TestFusionConfigurationProvider(config); + + var services = + new ServiceCollection() + .AddGraphQLGateway() + .AddConfigurationProvider(_ => configProvider) + .Services + .BuildServiceProvider(); + + var manager = services.GetRequiredService(); + + // act + async Task Act() => await manager.GetExecutorAsync(); + + // assert + var exception = await Assert.ThrowsAsync(Act); + Assert.Equal("No parser claimed any transport for source schema 'a'.", exception.Message); + } + + [Fact] + public async Task CreateClientConfigurations_Should_Prefer_User_Parser_Over_Builtin() + { + // arrange + var config = CreateConfigurationWithSettings( + """ + { + "sourceSchemas": { + "a": { + "transports": { + "http": { + "url": "http://localhost:5000/graphql" + } + } + } + } + } + """); + + var configProvider = new TestFusionConfigurationProvider(config); + var userParser = new AlwaysClaimParser(); + + var builder = + new ServiceCollection() + .AddGraphQLGateway() + .AddConfigurationProvider(_ => configProvider); + + FusionSetupUtilities.Configure( + builder, + setup => setup.SourceSchemaClientConfigurationParsers.Add(userParser)); + + var services = builder.Services.BuildServiceProvider(); + + var manager = services.GetRequiredService(); + + // act + var executor = await manager.GetExecutorAsync(); + + // assert + var clientConfigs = executor.Schema.Features.GetRequired(); + Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); + Assert.IsType(queryConfig); + } + + private static JsonProperty GetSourceSchemaProperty(string settingsJson, string schemaName) + { + var document = JsonDocument.Parse(settingsJson); + var sourceSchemas = document.RootElement.GetProperty("sourceSchemas"); + + foreach (var candidate in sourceSchemas.EnumerateObject()) + { + if (candidate.Name == schemaName) + { + return candidate; + } + } + + throw new InvalidOperationException($"Source schema '{schemaName}' not found."); + } + + private static JsonProperty GetTransportProperty(JsonProperty sourceSchema, string transportName) + { + var transports = sourceSchema.Value.GetProperty("transports"); + + foreach (var candidate in transports.EnumerateObject()) + { + if (candidate.Name == transportName) + { + return candidate; + } + } + + throw new InvalidOperationException($"Transport '{transportName}' not found."); + } + + private static string Summarize(HttpSourceSchemaClientConfiguration configuration) + { + return $""" + Name: {configuration.Name} + HttpClientName: {configuration.HttpClientName} + BaseAddress: {configuration.BaseAddress} + SupportedOperations: {configuration.SupportedOperations} + Capabilities: {configuration.Capabilities} + OnError: {configuration.OnError?.ToString() ?? ""} + """; + } + + private static FusionConfiguration CreateConfigurationWithSettings(string settingsJson) + { + var compositeSchema = ComposeSchemaDocument("type Query { foo: String }"); + var settings = JsonDocument.Parse(settingsJson); + + return new FusionConfiguration( + compositeSchema, + new JsonDocumentOwner(settings)); + } + + private sealed class AlwaysClaimParser : ISourceSchemaClientConfigurationParser + { + public bool TryParse( + JsonProperty sourceSchema, + JsonProperty transport, + [NotNullWhen(true)] out ISourceSchemaClientConfiguration? configuration) + { + configuration = new StubClientConfiguration(sourceSchema.Name); + return true; + } + } + + private sealed class StubClientConfiguration(string name) : ISourceSchemaClientConfiguration + { + public string Name { get; } = name; + + public SupportedOperationType SupportedOperations => SupportedOperationType.All; + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Configuration/ParsersTests.cs b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Configuration/ParsersTests.cs deleted file mode 100644 index 212c974c0c9..00000000000 --- a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Configuration/ParsersTests.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using HotChocolate.Buffers; -using HotChocolate.Features; -using HotChocolate.Fusion.Configuration.Parsers; -using HotChocolate.Fusion.Execution; -using HotChocolate.Fusion.Execution.Clients; -using HotChocolate.Language; -using Microsoft.Extensions.DependencyInjection; - -namespace HotChocolate.Fusion.Configuration; - -public class ParsersTests : FusionTestBase -{ - [Fact] - public void HttpSourceSchemaClientConfigurationParser_Should_Return_False_When_Http_Transport_Missing() - { - // arrange - var sourceSchema = GetSourceSchemaProperty( - """ - { - "sourceSchemas": { - "a": { - "transports": { - "websockets": { "url": "ws://localhost:5000/graphql" } - } - } - } - } - """, - "a"); - var transport = GetTransportProperty(sourceSchema, "websockets"); - var parser = new HttpSourceSchemaClientConfigurationParser(); - - // act - var claimed = parser.TryParse(sourceSchema, transport, out var configuration); - - // assert - Assert.False(claimed); - Assert.Null(configuration); - } - - [Fact] - public void HttpSourceSchemaClientConfigurationParser_Should_Produce_Configuration_When_Http_Transport_Present() - { - // arrange - var sourceSchema = GetSourceSchemaProperty( - """ - { - "sourceSchemas": { - "products": { - "transports": { - "http": { - "url": "http://localhost:5000/graphql", - "clientName": "products-client" - } - } - } - } - } - """, - "products"); - var transport = GetTransportProperty(sourceSchema, "http"); - var parser = new HttpSourceSchemaClientConfigurationParser(); - - // act - var claimed = parser.TryParse(sourceSchema, transport, out var configuration); - - // assert - Assert.True(claimed); - var http = Assert.IsType(configuration); - Summarize(http).MatchInlineSnapshot( - """ - Name: products - HttpClientName: products-client - BaseAddress: http://localhost:5000/graphql - SupportedOperations: All - Capabilities: All - """); - } - - [Fact] - public async Task CreateClientConfigurations_Should_Throw_When_No_Parser_Claims_Schema() - { - // arrange - var config = CreateConfigurationWithSettings( - """ - { - "sourceSchemas": { - "a": { - "transports": { - "xyz": { "url": "xyz://localhost" } - } - } - } - } - """); - - var configProvider = new TestFusionConfigurationProvider(config); - - var services = - new ServiceCollection() - .AddGraphQLGateway() - .AddConfigurationProvider(_ => configProvider) - .Services - .BuildServiceProvider(); - - var manager = services.GetRequiredService(); - - // act - async Task Act() => await manager.GetExecutorAsync(); - - // assert - var exception = await Assert.ThrowsAsync(Act); - Assert.Equal("No parser claimed any transport for source schema 'a'.", exception.Message); - } - - [Fact] - public async Task CreateClientConfigurations_Should_Prefer_User_Parser_Over_Builtin() - { - // arrange - var config = CreateConfigurationWithSettings( - """ - { - "sourceSchemas": { - "a": { - "transports": { - "http": { - "url": "http://localhost:5000/graphql" - } - } - } - } - } - """); - - var configProvider = new TestFusionConfigurationProvider(config); - var userParser = new AlwaysClaimParser(); - - var builder = - new ServiceCollection() - .AddGraphQLGateway() - .AddConfigurationProvider(_ => configProvider); - - FusionSetupUtilities.Configure( - builder, - setup => setup.SourceSchemaClientConfigurationParsers.Add(userParser)); - - var services = builder.Services.BuildServiceProvider(); - - var manager = services.GetRequiredService(); - - // act - var executor = await manager.GetExecutorAsync(); - - // assert - var clientConfigs = executor.Schema.Features.GetRequired(); - Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); - Assert.IsType(queryConfig); - } - - private static JsonProperty GetSourceSchemaProperty(string settingsJson, string schemaName) - { - var document = JsonDocument.Parse(settingsJson); - var sourceSchemas = document.RootElement.GetProperty("sourceSchemas"); - - foreach (var candidate in sourceSchemas.EnumerateObject()) - { - if (candidate.Name == schemaName) - { - return candidate; - } - } - - throw new InvalidOperationException($"Source schema '{schemaName}' not found."); - } - - private static JsonProperty GetTransportProperty(JsonProperty sourceSchema, string transportName) - { - var transports = sourceSchema.Value.GetProperty("transports"); - - foreach (var candidate in transports.EnumerateObject()) - { - if (candidate.Name == transportName) - { - return candidate; - } - } - - throw new InvalidOperationException($"Transport '{transportName}' not found."); - } - - private static string Summarize(SourceSchemaHttpClientConfiguration configuration) - { - return $""" - Name: {configuration.Name} - HttpClientName: {configuration.HttpClientName} - BaseAddress: {configuration.BaseAddress} - SupportedOperations: {configuration.SupportedOperations} - Capabilities: {configuration.Capabilities} - """; - } - - private static FusionConfiguration CreateConfigurationWithSettings(string settingsJson) - { - var compositeSchema = ComposeSchemaDocument("type Query { foo: String }"); - var settings = JsonDocument.Parse(settingsJson); - - return new FusionConfiguration( - compositeSchema, - new JsonDocumentOwner(settings)); - } - - private sealed class AlwaysClaimParser : ISourceSchemaClientConfigurationParser - { - public bool TryParse( - JsonProperty sourceSchema, - JsonProperty transport, - [NotNullWhen(true)] out ISourceSchemaClientConfiguration? configuration) - { - configuration = new StubClientConfiguration(sourceSchema.Name); - return true; - } - } - - private sealed class StubClientConfiguration(string name) : ISourceSchemaClientConfiguration - { - public string Name { get; } = name; - - public SupportedOperationType SupportedOperations => SupportedOperationType.All; - } -} diff --git a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs index 4f06ad8ad32..514c2c596a8 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs @@ -379,9 +379,9 @@ public async Task CreateHttpClientConfiguration_Should_UseDefaults_When_NoCapabi Assert.True(clientConfigs.TryGet("a", OperationType.Mutation, out _)); Assert.True(clientConfigs.TryGet("a", OperationType.Subscription, out _)); - var httpConfig = Assert.IsType(queryConfig); + var httpConfig = Assert.IsType(queryConfig); Assert.Equal("a", httpConfig.Name); - Assert.Equal(SourceSchemaHttpClientConfiguration.DefaultClientName, httpConfig.HttpClientName); + Assert.Equal(HttpSourceSchemaClientConfiguration.DefaultClientName, httpConfig.HttpClientName); Assert.Equal(new Uri("http://localhost:5000/graphql"), httpConfig.BaseAddress); Assert.Equal(SourceSchemaClientCapabilities.All, httpConfig.Capabilities); Assert.Equal(SupportedOperationType.All, httpConfig.SupportedOperations); @@ -428,7 +428,7 @@ public async Task CreateHttpClientConfiguration_Should_UseCustomClientName_When_ var clientConfigs = executor.Schema.Features.GetRequired(); Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); - var httpConfig = Assert.IsType(queryConfig); + var httpConfig = Assert.IsType(queryConfig); Assert.Equal("a", httpConfig.Name); Assert.Equal("my-custom-client", httpConfig.HttpClientName); Assert.Equal(new Uri("http://localhost:5000/graphql"), httpConfig.BaseAddress); @@ -481,9 +481,9 @@ public async Task CreateHttpClientConfiguration_Should_DisableVariableBatching_W var clientConfigs = executor.Schema.Features.GetRequired(); Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); - var httpConfig = Assert.IsType(queryConfig); + var httpConfig = Assert.IsType(queryConfig); Assert.Equal("a", httpConfig.Name); - Assert.Equal(SourceSchemaHttpClientConfiguration.DefaultClientName, httpConfig.HttpClientName); + Assert.Equal(HttpSourceSchemaClientConfiguration.DefaultClientName, httpConfig.HttpClientName); Assert.Equal(new Uri("http://localhost:5000/graphql"), httpConfig.BaseAddress); Assert.Equal(SourceSchemaClientCapabilities.RequestBatching, httpConfig.Capabilities); Assert.Equal(SupportedOperationType.All, httpConfig.SupportedOperations); @@ -534,9 +534,9 @@ public async Task CreateHttpClientConfiguration_Should_DisableRequestBatching_Wh var clientConfigs = executor.Schema.Features.GetRequired(); Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); - var httpConfig = Assert.IsType(queryConfig); + var httpConfig = Assert.IsType(queryConfig); Assert.Equal("a", httpConfig.Name); - Assert.Equal(SourceSchemaHttpClientConfiguration.DefaultClientName, httpConfig.HttpClientName); + Assert.Equal(HttpSourceSchemaClientConfiguration.DefaultClientName, httpConfig.HttpClientName); Assert.Equal(new Uri("http://localhost:5000/graphql"), httpConfig.BaseAddress); Assert.Equal(SourceSchemaClientCapabilities.VariableBatching, httpConfig.Capabilities); Assert.Equal(SupportedOperationType.All, httpConfig.SupportedOperations); @@ -587,9 +587,9 @@ public async Task CreateHttpClientConfiguration_Should_DisableSubscriptions_When var clientConfigs = executor.Schema.Features.GetRequired(); Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); - var httpConfig = Assert.IsType(queryConfig); + var httpConfig = Assert.IsType(queryConfig); Assert.Equal("a", httpConfig.Name); - Assert.Equal(SourceSchemaHttpClientConfiguration.DefaultClientName, httpConfig.HttpClientName); + Assert.Equal(HttpSourceSchemaClientConfiguration.DefaultClientName, httpConfig.HttpClientName); Assert.Equal(new Uri("http://localhost:5000/graphql"), httpConfig.BaseAddress); Assert.Equal(SourceSchemaClientCapabilities.All, httpConfig.Capabilities); Assert.Equal(SupportedOperationType.Query | SupportedOperationType.Mutation, httpConfig.SupportedOperations); @@ -648,9 +648,9 @@ public async Task CreateHttpClientConfiguration_Should_UseCustomFormats_When_Spe var clientConfigs = executor.Schema.Features.GetRequired(); Assert.True(clientConfigs.TryGet("a", OperationType.Query, out var queryConfig)); - var httpConfig = Assert.IsType(queryConfig); + var httpConfig = Assert.IsType(queryConfig); Assert.Equal("a", httpConfig.Name); - Assert.Equal(SourceSchemaHttpClientConfiguration.DefaultClientName, httpConfig.HttpClientName); + Assert.Equal(HttpSourceSchemaClientConfiguration.DefaultClientName, httpConfig.HttpClientName); Assert.Equal(new Uri("http://localhost:5000/graphql"), httpConfig.BaseAddress); Assert.Equal(SourceSchemaClientCapabilities.All, httpConfig.Capabilities); Assert.Equal(SupportedOperationType.All, httpConfig.SupportedOperations);