Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fusion] Add FusionGatewayBuilder.UseRequest #7283

Merged
merged 11 commits into from
Jul 25, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,88 @@
return builder;
}

/// <summary>
/// Adds a type that will be used to create a middleware for the execution pipeline.
/// </summary>
/// <param name="builder">
/// The gateway builder.
/// </param>
/// <returns>
/// Returns the gateway builder for configuration chaining.
/// </returns>
public static FusionGatewayBuilder UseRequest<TMiddleware>(
this FusionGatewayBuilder builder)
where TMiddleware : class
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));

Check warning on line 427 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L426-L427

Added lines #L426 - L427 were not covered by tests
}

builder.CoreBuilder.UseRequest<TMiddleware>();
return builder;
}

/// <summary>
/// Adds a delegate that will be used to create a middleware for the execution pipeline.
/// </summary>
/// <param name="builder">
/// The gateway builder.
/// </param>
/// <param name="middleware">
/// A delegate that is used to create a middleware for the execution pipeline.
/// </param>
/// <returns>
/// Returns the gateway builder for configuration chaining.
/// </returns>
public static FusionGatewayBuilder UseRequest(
this FusionGatewayBuilder builder,
RequestCoreMiddleware middleware)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));

Check warning on line 452 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L449-L452

Added lines #L449 - L452 were not covered by tests
}

if (middleware is null)
{
throw new ArgumentNullException(nameof(middleware));

Check warning on line 457 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L455-L457

Added lines #L455 - L457 were not covered by tests
}

builder.CoreBuilder.UseRequest(middleware);
return builder;
}

Check warning on line 462 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L460-L462

Added lines #L460 - L462 were not covered by tests

/// <summary>
/// Adds a delegate that will be used to create a middleware for the execution pipeline.
/// </summary>
/// <param name="builder">
/// The gateway builder.
/// </param>
/// <param name="middleware">
/// A delegate that is used to create a middleware for the execution pipeline.
/// </param>
/// <returns>
/// Returns the gateway builder for configuration chaining.
/// </returns>
public static FusionGatewayBuilder UseRequest(
this FusionGatewayBuilder builder,
RequestMiddleware middleware)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));

Check warning on line 482 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L479-L482

Added lines #L479 - L482 were not covered by tests
}

if (middleware is null)
{
throw new ArgumentNullException(nameof(middleware));

Check warning on line 487 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L485-L487

Added lines #L485 - L487 were not covered by tests
}

builder.CoreBuilder.UseRequest(middleware);
return builder;
}

Check warning on line 492 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L490-L492

Added lines #L490 - L492 were not covered by tests

private static IRequestExecutorBuilder UseFusionDefaultPipeline(
this IRequestExecutorBuilder builder)
{
Expand Down Expand Up @@ -481,7 +563,7 @@
throw new ArgumentNullException(nameof(builder));
}

return builder.UseRequest<DistributedOperationExecutionMiddleware>();
return builder.UseRequest(DistributedOperationExecutionMiddleware.Create());
}

internal static void AddDefaultPipeline(this IList<RequestCoreMiddleware> pipeline)
Expand Down
158 changes: 158 additions & 0 deletions src/HotChocolate/Fusion/test/Core.Tests/RequestPipelineTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using CookieCrumble;
using HotChocolate.Execution;
using HotChocolate.Fetching;
using HotChocolate.Fusion.Composition;
using HotChocolate.Fusion.Composition.Features;
using HotChocolate.Fusion.Shared;
using HotChocolate.Skimmed.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
using static HotChocolate.Language.Utf8GraphQLParser;

namespace HotChocolate.Fusion;

public class RequestPipelineTests(ITestOutputHelper output)
{
private readonly Func<ICompositionLog> _logFactory = () => new TestCompositionLog(output);

[Fact]
public async Task Custom_Middleware_Runs_Before_DefaultPipeline()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

var fusionGraph =
await new FusionGraphComposer(logFactory: _logFactory)
.ComposeAsync(new[] { demoProject.Accounts.ToConfiguration() });

var config = new DemoIntegrationTests.HotReloadConfiguration(
new GatewayConfiguration(
SchemaFormatter.FormatAsDocument(fusionGraph)));

var executor = await new ServiceCollection()
.AddSingleton(demoProject.HttpClientFactory)
.AddFusionGatewayServer()
.RegisterGatewayConfiguration(_ => config)
.UseRequest<TestMiddleware>()
.UseDefaultPipeline()
.BuildRequestExecutorAsync();

// act
var result = await executor.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument("{ __typename }")
.AddGlobalState("short-circuit", true)
.Build());

// assert
result.MatchInlineSnapshot("""
{
"data": {
"result": true
},
"extensions": {
"state": "custom middleware short-circuited"
}
}
""");
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public async Task Custom_Middleware_Falls_Through_To_DefaultPipeline()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

var fusionGraph =
await new FusionGraphComposer(logFactory: _logFactory)
.ComposeAsync(new[] { demoProject.Accounts.ToConfiguration() });

var config = new DemoIntegrationTests.HotReloadConfiguration(
new GatewayConfiguration(
SchemaFormatter.FormatAsDocument(fusionGraph)));

var executor = await new ServiceCollection()
.AddSingleton(demoProject.HttpClientFactory)
.AddFusionGatewayServer()
.RegisterGatewayConfiguration(_ => config)
.UseRequest<TestMiddleware>()
.UseDefaultPipeline()
.BuildRequestExecutorAsync();

// act
var result = await executor.ExecuteAsync("{ __typename }");

// assert
result.MatchInlineSnapshot("""
{
"data": {
"__typename": "Query"
}
}
""");
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public async Task Custom_Middleware_Without_DefaultPipeline_No_Other_Middleware_Registered()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();

var fusionGraph =
await new FusionGraphComposer(logFactory: _logFactory)
.ComposeAsync(new[] { demoProject.Accounts.ToConfiguration() });

var config = new DemoIntegrationTests.HotReloadConfiguration(
new GatewayConfiguration(
SchemaFormatter.FormatAsDocument(fusionGraph)));

var executor = await new ServiceCollection()
.AddSingleton(demoProject.HttpClientFactory)
.AddFusionGatewayServer()
.RegisterGatewayConfiguration(_ => config)
.UseRequest<TestMiddleware>()
.BuildRequestExecutorAsync();

// act
var result = await executor.ExecuteAsync("{ __typename }");

// assert
result.MatchInlineSnapshot("""
{
"data": {
"result": true
},
"extensions": {
"state": "default pipeline didn't run"
}
}
""");
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved
}

private class TestMiddleware(RequestDelegate next)
{
public async ValueTask InvokeAsync(
IRequestContext context,
IBatchDispatcher batchDispatcher)
{
if (context.ContextData.ContainsKey("short-circuit"))
{
context.Result = OperationResultBuilder.New()
.SetData(new Dictionary<string, object?> { ["result"] = true })
.AddExtension("state", "custom middleware short-circuited")
.Build();
return;
}

await next(context);

if (context.Result is not IOperationResult)
{
context.Result = OperationResultBuilder.New()
.SetData(new Dictionary<string, object?> { ["result"] = true })
.AddExtension("state", "default pipeline didn't run")
.Build();
}
}
}
}
Loading