diff --git a/src/HotChocolate/Core/src/Authorization/Extensions/AuthorizeRequestExecutorBuilder.cs b/src/HotChocolate/Core/src/Authorization/Extensions/AuthorizeRequestExecutorBuilder.cs index 82f1eb13a56..87ece204497 100644 --- a/src/HotChocolate/Core/src/Authorization/Extensions/AuthorizeRequestExecutorBuilder.cs +++ b/src/HotChocolate/Core/src/Authorization/Extensions/AuthorizeRequestExecutorBuilder.cs @@ -37,16 +37,16 @@ public static IRequestExecutorBuilder AddAuthorizationCore( s.GetRequiredService())); var prepareAuthorization = PrepareAuthorizationMiddleware.Create(); - builder.InsertUseRequest( - before: WellKnownRequestMiddleware.DocumentValidationMiddleware, - middleware: prepareAuthorization.Middleware, - key: prepareAuthorization.Key); + builder.UseRequest( + prepareAuthorization.Middleware, + key: prepareAuthorization.Key, + before: WellKnownRequestMiddleware.DocumentValidationMiddleware); var authorizeRequest = AuthorizeRequestMiddleware.Create(); - builder.AppendUseRequest( - after: WellKnownRequestMiddleware.DocumentValidationMiddleware, - middleware: authorizeRequest.Middleware, - key: authorizeRequest.Key); + builder.UseRequest( + authorizeRequest.Middleware, + key: authorizeRequest.Key, + after: WellKnownRequestMiddleware.DocumentValidationMiddleware); return builder; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs index f6fb08921b1..c97614fd2d5 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs @@ -264,14 +264,17 @@ public void WriteInterceptMethod( using (_writer.IncreaseIndent()) { _writer.WriteIndentedLine("this {0} builder,", RequestExecutorBuilder); - _writer.WriteIndentedLine("string? key = null)"); + _writer.WriteIndentedLine("string? key = null,"); + _writer.WriteIndentedLine("string? before = null,"); + _writer.WriteIndentedLine("string? after = null,"); + _writer.WriteIndentedLine("bool allowMultiple = false)"); _writer.WriteIndentedLine("where TMiddleware : class"); } using (_writer.IncreaseIndent()) { _writer.WriteIndentedLine( - "=> builder.UseRequest(CreateMiddleware{2}(), key);", + "=> builder.UseRequest(CreateMiddleware{2}(), key, before, after, allowMultiple);", _moduleName, "MiddlewareFactories", middlewareIndex); diff --git a/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs b/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs index 36651ec7e1a..6647072a5e6 100644 --- a/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs +++ b/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs @@ -19,256 +19,47 @@ public static partial class RequestExecutorBuilderExtensions /// /// A unique identifier for the middleware. /// - /// - /// An that can be used to configure a schema and its execution. - /// - public static IRequestExecutorBuilder UseRequest( - this IRequestExecutorBuilder builder, - Func middleware, - string? key = null) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(middleware); - - return Configure( - builder, - options => options.Pipeline.Add( - new RequestMiddlewareConfiguration( - (_, n) => middleware(n), - key))); - } - - /// - /// Adds a delegate that will be used to create middleware for the execution pipeline. - /// - /// - /// The that can be used to configure a schema and its execution. - /// - /// - /// A delegate that is used to create middleware for the execution pipeline. - /// - /// - /// A unique identifier for the middleware. - /// - /// - /// An that can be used to configure a schema and its execution. - /// - public static IRequestExecutorBuilder UseRequest( - this IRequestExecutorBuilder builder, - RequestMiddleware middleware, - string? key = null) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(middleware); - - return Configure( - builder, - options => options.Pipeline.Add(new RequestMiddlewareConfiguration(middleware, key))); - } - - /// - /// Adds a delegate that will be used to create middleware for the execution pipeline. - /// - /// - /// The that can be used to configure a schema and its execution. - /// - /// - /// The middleware configuration to use. - /// - /// - /// An that can be used to configure a schema and its execution. - /// - public static IRequestExecutorBuilder UseRequest( - this IRequestExecutorBuilder builder, - RequestMiddlewareConfiguration configuration) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(configuration); - - return Configure(builder, options => options.Pipeline.Add(configuration)); - } - - /// - /// Adds a type that will be used to create middleware for the execution pipeline. - /// - /// - /// The that can be used to configure a schema and its execution. - /// - /// - /// A unique identifier for the middleware. - /// - /// - /// An that can be used to configure a schema and its execution. - /// - public static IRequestExecutorBuilder UseRequest( - this IRequestExecutorBuilder builder, - string? key = null) - where TMiddleware : class - { - ArgumentNullException.ThrowIfNull(builder); - - return Configure( - builder, - options => options.Pipeline.Add( - new RequestMiddlewareConfiguration( - RequestClassMiddlewareFactory.Create(), - key))); - } - - /// - /// Appends middleware to the execution pipeline the middleware with the specified key. - /// - /// - /// The that can be used to configure a schema and its execution. + /// + /// If specified, the middleware is inserted before the middleware with the given key. /// /// - /// The key of the middleware after which the new middleware will be appended. - /// - /// - /// The middleware to append. - /// - /// - /// A unique identifier for the middleware. + /// If specified, the middleware is inserted after the middleware with the given key. /// /// - /// If set to true, multiple instances of the same middleware can be appended. + /// If false and a middleware with the same already exists in the + /// pipeline, the insertion is skipped. Only applies when or + /// is specified. /// /// /// An that can be used to configure a schema and its execution. /// - public static IRequestExecutorBuilder AppendUseRequest( + public static IRequestExecutorBuilder UseRequest( this IRequestExecutorBuilder builder, - string after, - RequestMiddleware middleware, + Func middleware, string? key = null, + string? before = null, + string? after = null, bool allowMultiple = false) { ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(after); ArgumentNullException.ThrowIfNull(middleware); - if (!allowMultiple && key is null) + if (before is not null && after is not null) { throw new ArgumentException( - "The key must be set if allowMultiple is false.", - nameof(key)); + "Only one of 'before' or 'after' can be specified at the same time."); } - return Configure( - builder, - options => - { - var configuration = new RequestMiddlewareConfiguration(middleware, key); - - options.PipelineModifiers.Add(pipeline => - { - if (!allowMultiple && GetIndex(pipeline, key!) != -1) - { - return; - } - - var index = GetIndex(pipeline, after); - - if (index == -1) - { - throw new InvalidOperationException( - $"The middleware with the key `{after}` was not found."); - } - - pipeline.Insert(index + 1, configuration); - }); - }); - } - - /// - /// Appends middleware to the execution pipeline the middleware with the specified key. - /// - /// - /// The that can be used to configure a schema and its execution. - /// - /// - /// The key of the middleware after which the new middleware will be appended. - /// - /// - /// The middleware configuration to append. - /// - /// - /// If set to true, multiple instances of the same middleware can be appended. - /// - /// - /// An that can be used to configure a schema and its execution. - /// - public static IRequestExecutorBuilder AppendUseRequest( - this IRequestExecutorBuilder builder, - string after, - RequestMiddlewareConfiguration configuration, - bool allowMultiple = false) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(after); - ArgumentNullException.ThrowIfNull(configuration); - - if (!allowMultiple && configuration.Key is null) + if (before is null && after is null) { - throw new ArgumentException( - "The key must be set if allowMultiple is false.", - nameof(configuration)); + return Configure( + builder, + options => options.Pipeline.Add( + new RequestMiddlewareConfiguration( + (_, n) => middleware(n), + key))); } - return Configure( - builder, - options => - { - options.PipelineModifiers.Add(pipeline => - { - if (!allowMultiple && GetIndex(pipeline, configuration.Key!) != -1) - { - return; - } - - var index = GetIndex(pipeline, after); - - if (index == -1) - { - throw new InvalidOperationException($"The middleware with the key `{after}` was not found."); - } - - pipeline.Insert(index + 1, configuration); - }); - }); - } - - /// - /// Appends middleware to the execution pipeline the middleware with the specified key. - /// - /// - /// The type of the middleware to append. - /// - /// - /// The that can be used to configure a schema and its execution. - /// - /// - /// The key of the middleware after which the new middleware will be appended. - /// - /// - /// A unique identifier for the middleware. - /// - /// - /// If set to true, multiple instances of the same middleware can be appended. - /// - /// - /// An that can be used to configure a schema and its execution. - /// - public static IRequestExecutorBuilder AppendUseRequest( - this IRequestExecutorBuilder builder, - string after, - string? key = null, - bool allowMultiple = true) - where TMiddleware : class - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(after); - if (!allowMultiple && key is null) { throw new ArgumentException( @@ -280,9 +71,7 @@ public static IRequestExecutorBuilder AppendUseRequest( builder, options => { - var configuration = new RequestMiddlewareConfiguration( - RequestClassMiddlewareFactory.Create(), - key); + var configuration = new RequestMiddlewareConfiguration((_, n) => middleware(n), key); options.PipelineModifiers.Add(pipeline => { @@ -291,50 +80,70 @@ public static IRequestExecutorBuilder AppendUseRequest( return; } - var index = GetIndex(pipeline, after); + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { - throw new InvalidOperationException($"The middleware with the key `{after}` was not found."); + throw new InvalidOperationException( + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index + 1, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } /// - /// Inserts middleware to the execution pipeline the middleware with the specified key. + /// Adds a delegate that will be used to create middleware for the execution pipeline. /// /// /// The that can be used to configure a schema and its execution. /// - /// - /// The key of the middleware before which the new middleware will be inserted. - /// /// - /// The middleware to insert. + /// A delegate that is used to create middleware for the execution pipeline. /// /// /// A unique identifier for the middleware. /// + /// + /// If specified, the middleware is inserted before the middleware with the given key. + /// + /// + /// If specified, the middleware is inserted after the middleware with the given key. + /// /// - /// If set to true, multiple instances of the same middleware can be inserted. + /// If false and a middleware with the same already exists in the + /// pipeline, the insertion is skipped. Only applies when or + /// is specified. /// /// /// An that can be used to configure a schema and its execution. /// - public static IRequestExecutorBuilder InsertUseRequest( + public static IRequestExecutorBuilder UseRequest( this IRequestExecutorBuilder builder, - string before, RequestMiddleware middleware, string? key = null, + string? before = null, + string? after = null, bool allowMultiple = false) { ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(before); ArgumentNullException.ThrowIfNull(middleware); + if (before is not null && after is not null) + { + throw new ArgumentException( + "Only one of 'before' or 'after' can be specified at the same time."); + } + + if (before is null && after is null) + { + return Configure( + builder, + options => options.Pipeline.Add(new RequestMiddlewareConfiguration(middleware, key))); + } + if (!allowMultiple && key is null) { throw new ArgumentException( @@ -355,47 +164,64 @@ public static IRequestExecutorBuilder InsertUseRequest( return; } - var index = GetIndex(pipeline, before); + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { throw new InvalidOperationException( - $"The middleware with the key `{before}` was not found."); + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } /// - /// Inserts middleware to the execution pipeline the middleware with the specified key. + /// Adds middleware configuration to the execution pipeline. /// /// /// The that can be used to configure a schema and its execution. /// + /// + /// The middleware configuration to use. + /// /// - /// The key of the middleware before which the new middleware will be inserted. + /// If specified, the middleware is inserted before the middleware with the given key. /// - /// - /// The middleware configuration to insert. + /// + /// If specified, the middleware is inserted after the middleware with the given key. /// /// - /// If set to true, multiple instances of the same middleware can be inserted. + /// If false and a middleware with the same key already exists in the pipeline, + /// the insertion is skipped. Only applies when or + /// is specified. /// /// /// An that can be used to configure a schema and its execution. /// - public static IRequestExecutorBuilder InsertUseRequest( + public static IRequestExecutorBuilder UseRequest( this IRequestExecutorBuilder builder, - string before, RequestMiddlewareConfiguration configuration, + string? before = null, + string? after = null, bool allowMultiple = false) { ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(before); ArgumentNullException.ThrowIfNull(configuration); + if (before is not null && after is not null) + { + throw new ArgumentException( + "Only one of 'before' or 'after' can be specified at the same time."); + } + + if (before is null && after is null) + { + return Configure(builder, options => options.Pipeline.Add(configuration)); + } + if (!allowMultiple && configuration.Key is null) { throw new ArgumentException( @@ -414,48 +240,71 @@ public static IRequestExecutorBuilder InsertUseRequest( return; } - var index = GetIndex(pipeline, before); + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { - throw new InvalidOperationException($"The middleware with the key `{before}` was not found."); + throw new InvalidOperationException( + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } /// - /// Inserts middleware to the execution pipeline the middleware with the specified key. + /// Adds a type that will be used to create middleware for the execution pipeline. /// /// - /// The type of the middleware to insert. + /// The type of the middleware to add. /// /// /// The that can be used to configure a schema and its execution. /// - /// - /// The key of the middleware before which the new middleware will be inserted. - /// /// /// A unique identifier for the middleware. /// + /// + /// If specified, the middleware is inserted before the middleware with the given key. + /// + /// + /// If specified, the middleware is inserted after the middleware with the given key. + /// /// - /// If set to true, multiple instances of the same middleware can be inserted. + /// If false and a middleware with the same already exists in the + /// pipeline, the insertion is skipped. Only applies when or + /// is specified. /// /// /// An that can be used to configure a schema and its execution. /// - public static IRequestExecutorBuilder InsertUseRequest( + public static IRequestExecutorBuilder UseRequest( this IRequestExecutorBuilder builder, - string before, string? key = null, - bool allowMultiple = true) + string? before = null, + string? after = null, + bool allowMultiple = false) where TMiddleware : class { ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(before); + + if (before is not null && after is not null) + { + throw new ArgumentException( + "Only one of 'before' or 'after' can be specified at the same time."); + } + + if (before is null && after is null) + { + return Configure( + builder, + options => options.Pipeline.Add( + new RequestMiddlewareConfiguration( + RequestClassMiddlewareFactory.Create(), + key))); + } if (!allowMultiple && key is null) { @@ -479,14 +328,16 @@ public static IRequestExecutorBuilder InsertUseRequest( return; } - var index = GetIndex(pipeline, before); + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { - throw new InvalidOperationException($"The middleware with the key `{before}` was not found."); + throw new InvalidOperationException( + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs index 5e2c13667da..8c04aeb9a51 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs @@ -21,15 +21,15 @@ public async Task Concurrent_Same_Operation_Should_Be_Coalesced_To_One_Compilati .AddQueryType(d => d.Field("foo").Resolve("bar")) .UseDefaultPipeline() .AddDiagnosticEventListener(_ => new CompileCountListener(() => Interlocked.Increment(ref compileCount))) - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationCacheMiddleware, + .UseRequest( (_, next) => CreateGateMiddleware(next, gate), key: "Gate", + before: WellKnownRequestMiddleware.OperationCacheMiddleware, allowMultiple: true) - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationResolverMiddleware, + .UseRequest( (_, next) => CreateSingleFlightLeaderDelayMiddleware(next, TimeSpan.FromMilliseconds(100)), key: "LeaderDelay", + before: WellKnownRequestMiddleware.OperationResolverMiddleware, allowMultiple: true) .Services .BuildServiceProvider() @@ -68,10 +68,10 @@ public async Task Concurrent_Distinct_Operations_Should_Not_Be_Coalesced() }) .UseDefaultPipeline() .AddDiagnosticEventListener(_ => new CompileCountListener(() => Interlocked.Increment(ref compileCount))) - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationCacheMiddleware, + .UseRequest( (_, next) => CreateGateMiddleware(next, gate), key: "Gate", + before: WellKnownRequestMiddleware.OperationCacheMiddleware, allowMultiple: true) .Services .BuildServiceProvider() @@ -113,20 +113,20 @@ public async Task Leader_Compilation_Failure_Should_Be_Observed_By_Followers() .AddQueryType(d => d.Field("foo").Resolve("bar")) .UseDefaultPipeline() .AddDiagnosticEventListener(_ => new CompileCountListener(() => Interlocked.Increment(ref compileCount))) - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationCacheMiddleware, + .UseRequest( (_, next) => CreateGateMiddleware(next, gate), key: "Gate", + before: WellKnownRequestMiddleware.OperationCacheMiddleware, allowMultiple: true) - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationResolverMiddleware, + .UseRequest( (_, next) => CreateSingleFlightLeaderDelayMiddleware(next, TimeSpan.FromMilliseconds(100)), key: "LeaderDelay", - allowMultiple: true) - .InsertUseRequest( before: WellKnownRequestMiddleware.OperationResolverMiddleware, + allowMultiple: true) + .UseRequest( (_, next) => CreateThrowingMiddleware(next), key: "Throwing", + before: WellKnownRequestMiddleware.OperationResolverMiddleware, allowMultiple: true) .Services .BuildServiceProvider() @@ -162,10 +162,10 @@ public async Task Follower_Cancellation_Should_Not_Cancel_Leader_Compilation() .AddQueryType(d => d.Field("foo").Resolve("bar")) .UseDefaultPipeline() .AddDiagnosticEventListener(_ => new CompileCountListener(() => Interlocked.Increment(ref compileCount))) - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationResolverMiddleware, + .UseRequest( (_, next) => CreateBlockingMiddleware(next, compileGate), key: "Blocking", + before: WellKnownRequestMiddleware.OperationResolverMiddleware, allowMultiple: true) .Services .BuildServiceProvider() diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/RequestMiddlewareTests.GenerateSource_RequestMiddleware_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/RequestMiddlewareTests.GenerateSource_RequestMiddleware_MatchesSnapshot.md index 2aa4d9371a6..6c9e35ac4aa 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/RequestMiddlewareTests.GenerateSource_RequestMiddleware_MatchesSnapshot.md +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/RequestMiddlewareTests.GenerateSource_RequestMiddleware_MatchesSnapshot.md @@ -35,9 +35,12 @@ namespace HotChocolate.Execution.Generated [InterceptsLocation("", 15, 14)] public static global::HotChocolate.Execution.Configuration.IRequestExecutorBuilder UseRequestGen0( this HotChocolate.Execution.Configuration.IRequestExecutorBuilder builder, - string? key = null) + string? key = null, + string? before = null, + string? after = null, + bool allowMultiple = false) where TMiddleware : class - => builder.UseRequest(CreateMiddleware0(), key); + => builder.UseRequest(CreateMiddleware0(), key, before, after, allowMultiple); } } diff --git a/src/HotChocolate/CostAnalysis/src/CostAnalysis/DependencyInjection/CostAnalyzerRequestExecutorBuilderExtensions.cs b/src/HotChocolate/CostAnalysis/src/CostAnalysis/DependencyInjection/CostAnalyzerRequestExecutorBuilderExtensions.cs index 25e8981525d..1c4d657abfa 100644 --- a/src/HotChocolate/CostAnalysis/src/CostAnalysis/DependencyInjection/CostAnalyzerRequestExecutorBuilderExtensions.cs +++ b/src/HotChocolate/CostAnalysis/src/CostAnalysis/DependencyInjection/CostAnalyzerRequestExecutorBuilderExtensions.cs @@ -64,10 +64,9 @@ public static IRequestExecutorBuilder AddCostAnalyzer(this IRequestExecutorBuild .AddDirectiveType() .TryAddTypeInterceptor() .TryAddTypeInterceptor() - .AppendUseRequest( - after: WellKnownRequestMiddleware.DocumentValidationMiddleware, - configuration: CostAnalyzerMiddleware.Create(), - allowMultiple: false); + .UseRequest( + CostAnalyzerMiddleware.Create(), + after: WellKnownRequestMiddleware.DocumentValidationMiddleware); } /// diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.Core.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.Core.cs index f194ec39246..dde7a54e05a 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.Core.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.Core.cs @@ -8,114 +8,96 @@ public static partial class CoreFusionGatewayBuilderExtensions public static IFusionGatewayBuilder UseRequest( this IFusionGatewayBuilder builder, Func middleware, - string? key = null) + string? key = null, + string? before = null, + string? after = null, + bool allowMultiple = false) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(middleware); - return FusionSetupUtilities.Configure( - builder, - options => options.PipelineModifiers.Add( - pipeline => pipeline.Add( - new RequestMiddlewareConfiguration((_, n) => middleware(n), key)))); - } - - public static IFusionGatewayBuilder UseRequest( - this IFusionGatewayBuilder builder, - RequestMiddleware middleware, - string? key = null) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(middleware); - - return FusionSetupUtilities.Configure( - builder, - options => options.PipelineModifiers.Add( - pipeline => pipeline.Add( - new RequestMiddlewareConfiguration(middleware, key)))); - } - - public static IFusionGatewayBuilder UseRequest( - this IFusionGatewayBuilder builder, - RequestMiddlewareConfiguration configuration) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(configuration); + if (before is not null && after is not null) + { + throw new ArgumentException( + "Only one of 'before' or 'after' can be specified at the same time."); + } - return FusionSetupUtilities.Configure( - builder, - options => options.PipelineModifiers.Add( - pipeline => pipeline.Add(configuration))); - } + if (before is null && after is null) + { + return FusionSetupUtilities.Configure( + builder, + options => options.PipelineModifiers.Add( + pipeline => pipeline.Add( + new RequestMiddlewareConfiguration((_, n) => middleware(n), key)))); + } - public static IFusionGatewayBuilder AppendUseRequest( - this IFusionGatewayBuilder builder, - string after, - RequestMiddleware middleware, - string? key = null) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(after); - ArgumentNullException.ThrowIfNull(middleware); + if (!allowMultiple && key is null) + { + throw new ArgumentException( + "The key must be set if allowMultiple is false.", + nameof(key)); + } return FusionSetupUtilities.Configure( builder, options => { - var configuration = new RequestMiddlewareConfiguration(middleware, key); + var configuration = new RequestMiddlewareConfiguration((_, n) => middleware(n), key); options.PipelineModifiers.Add(pipeline => { - var index = GetIndex(pipeline, after); - - if (index == -1) + if (!allowMultiple && GetIndex(pipeline, key!) != -1) { - throw new InvalidOperationException( - $"The middleware with the key `{after}` was not found."); + return; } - pipeline.Insert(index + 1, configuration); - }); - }); - } - - public static IFusionGatewayBuilder AppendUseRequest( - this IFusionGatewayBuilder builder, - string after, - RequestMiddlewareConfiguration configuration) - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(after); - ArgumentNullException.ThrowIfNull(configuration); - - return FusionSetupUtilities.Configure( - builder, - options => - { - options.PipelineModifiers.Add(pipeline => - { - var index = GetIndex(pipeline, after); + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { - throw new InvalidOperationException($"The middleware with the key `{after}` was not found."); + throw new InvalidOperationException( + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index + 1, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } - public static IFusionGatewayBuilder InsertUseRequest( + public static IFusionGatewayBuilder UseRequest( this IFusionGatewayBuilder builder, - string before, RequestMiddleware middleware, - string? key = null) + string? key = null, + string? before = null, + string? after = null, + bool allowMultiple = false) { ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(before); ArgumentNullException.ThrowIfNull(middleware); + if (before is not null && after is not null) + { + throw new ArgumentException( + "Only one of 'before' or 'after' can be specified at the same time."); + } + + if (before is null && after is null) + { + return FusionSetupUtilities.Configure( + builder, + options => options.PipelineModifiers.Add( + pipeline => pipeline.Add( + new RequestMiddlewareConfiguration(middleware, key)))); + } + + if (!allowMultiple && key is null) + { + throw new ArgumentException( + "The key must be set if allowMultiple is false.", + nameof(key)); + } + return FusionSetupUtilities.Configure( builder, options => @@ -124,42 +106,77 @@ public static IFusionGatewayBuilder InsertUseRequest( options.PipelineModifiers.Add(pipeline => { - var index = GetIndex(pipeline, before); + if (!allowMultiple && GetIndex(pipeline, key!) != -1) + { + return; + } + + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { throw new InvalidOperationException( - $"The middleware with the key `{before}` was not found."); + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } - public static IFusionGatewayBuilder InsertUseRequest( + public static IFusionGatewayBuilder UseRequest( this IFusionGatewayBuilder builder, - string before, - RequestMiddlewareConfiguration configuration) + RequestMiddlewareConfiguration configuration, + string? before = null, + string? after = null, + bool allowMultiple = false) { ArgumentNullException.ThrowIfNull(builder); - ArgumentException.ThrowIfNullOrEmpty(before); ArgumentNullException.ThrowIfNull(configuration); + if (before is not null && after is not null) + { + throw new ArgumentException( + "Only one of 'before' or 'after' can be specified at the same time."); + } + + if (before is null && after is null) + { + return FusionSetupUtilities.Configure( + builder, + options => options.PipelineModifiers.Add( + pipeline => pipeline.Add(configuration))); + } + + if (!allowMultiple && configuration.Key is null) + { + throw new ArgumentException( + "The key must be set if allowMultiple is false.", + nameof(configuration)); + } + return FusionSetupUtilities.Configure( builder, options => { options.PipelineModifiers.Add(pipeline => { - var index = GetIndex(pipeline, before); + if (!allowMultiple && GetIndex(pipeline, configuration.Key!) != -1) + { + return; + } + + var anchor = (before ?? after)!; + var index = GetIndex(pipeline, anchor); if (index == -1) { - throw new InvalidOperationException($"The middleware with the key `{before}` was not found."); + throw new InvalidOperationException( + $"The middleware with the key `{anchor}` was not found."); } - pipeline.Insert(index, configuration); + pipeline.Insert(before is not null ? index : index + 1, configuration); }); }); } diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.cs index d216254b37e..6975321b7ff 100644 --- a/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.cs +++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Execution/DependencyInjection/CoreFusionGatewayBuilderExtensions.Pipeline.cs @@ -102,27 +102,10 @@ public static IFusionGatewayBuilder UseReadPersistedOperation( { ArgumentNullException.ThrowIfNull(builder); - if (after is not null && before is not null) - { - throw new ArgumentException( - "You cannot specify both 'after' and 'before' parameters."); - } - - if (after is not null) - { - return builder.AppendUseRequest( - after, - PersistedOperationMiddleware.ReadPersistedOperation); - } - - if (before is not null) - { - return builder.InsertUseRequest( - before, - PersistedOperationMiddleware.ReadPersistedOperation); - } - - return builder.UseRequest(PersistedOperationMiddleware.ReadPersistedOperation); + return builder.UseRequest( + PersistedOperationMiddleware.ReadPersistedOperation, + before: before, + after: after); } public static IFusionGatewayBuilder UseAutomaticPersistedOperationNotFound( @@ -132,27 +115,10 @@ public static IFusionGatewayBuilder UseAutomaticPersistedOperationNotFound( { ArgumentNullException.ThrowIfNull(builder); - if (after is not null && before is not null) - { - throw new ArgumentException( - "You cannot specify both 'after' and 'before' parameters."); - } - - if (after is not null) - { - return builder.AppendUseRequest( - after, - PersistedOperationMiddleware.AutomaticPersistedOperationNotFound); - } - - if (before is not null) - { - return builder.InsertUseRequest( - before, - PersistedOperationMiddleware.AutomaticPersistedOperationNotFound); - } - - return builder.UseRequest(PersistedOperationMiddleware.AutomaticPersistedOperationNotFound); + return builder.UseRequest( + PersistedOperationMiddleware.AutomaticPersistedOperationNotFound, + before: before, + after: after); } public static IFusionGatewayBuilder UseWritePersistedOperation( @@ -162,27 +128,10 @@ public static IFusionGatewayBuilder UseWritePersistedOperation( { ArgumentNullException.ThrowIfNull(builder); - if (after is not null && before is not null) - { - throw new ArgumentException( - "You cannot specify both 'after' and 'before' parameters."); - } - - if (after is not null) - { - return builder.AppendUseRequest( - after, - PersistedOperationMiddleware.WritePersistedOperation); - } - - if (before is not null) - { - return builder.InsertUseRequest( - before, - PersistedOperationMiddleware.WritePersistedOperation); - } - - return builder.UseRequest(PersistedOperationMiddleware.WritePersistedOperation); + return builder.UseRequest( + PersistedOperationMiddleware.WritePersistedOperation, + before: before, + after: after); } public static IFusionGatewayBuilder UsePersistedOperationNotFound( @@ -192,27 +141,10 @@ public static IFusionGatewayBuilder UsePersistedOperationNotFound( { ArgumentNullException.ThrowIfNull(builder); - if (after is not null && before is not null) - { - throw new ArgumentException( - "You cannot specify both 'after' and 'before' parameters."); - } - - if (after is not null) - { - return builder.AppendUseRequest( - after, - PersistedOperationMiddleware.PersistedOperationNotFound); - } - - if (before is not null) - { - return builder.InsertUseRequest( - before, - PersistedOperationMiddleware.PersistedOperationNotFound); - } - - return builder.UseRequest(PersistedOperationMiddleware.PersistedOperationNotFound); + return builder.UseRequest( + PersistedOperationMiddleware.PersistedOperationNotFound, + before: before, + after: after); } public static IFusionGatewayBuilder UseOnlyPersistedOperationAllowed( @@ -222,27 +154,10 @@ public static IFusionGatewayBuilder UseOnlyPersistedOperationAllowed( { ArgumentNullException.ThrowIfNull(builder); - if (after is not null && before is not null) - { - throw new ArgumentException( - "You cannot specify both 'after' and 'before' parameters."); - } - - if (after is not null) - { - return builder.AppendUseRequest( - after, - PersistedOperationMiddleware.OnlyPersistedOperationsAllowed); - } - - if (before is not null) - { - return builder.InsertUseRequest( - before, - PersistedOperationMiddleware.OnlyPersistedOperationsAllowed); - } - - return builder.UseRequest(PersistedOperationMiddleware.OnlyPersistedOperationsAllowed); + return builder.UseRequest( + PersistedOperationMiddleware.OnlyPersistedOperationsAllowed, + before: before, + after: after); } public static IFusionGatewayBuilder UseDefaultPipeline( diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/NullTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/NullTests.cs index b0b39799d87..9c8b4660ca9 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/NullTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/NullTests.cs @@ -13,8 +13,7 @@ public async Task Raise_NonNullViolation_Error_For_NonNull_Field_Being_Null() using var server1 = CreateSourceSchema( "A", b => b.AddQueryType() - .InsertUseRequest( - WellKnownRequestMiddleware.OperationExecutionMiddleware, + .UseRequest( (_, _) => context => { // TODO: Re-add this @@ -25,7 +24,8 @@ public async Task Raise_NonNullViolation_Error_For_NonNull_Field_Being_Null() // }); return ValueTask.CompletedTask; }, - key: "SetNull")); + key: "SetNull", + before: WellKnownRequestMiddleware.OperationExecutionMiddleware)); // act using var gateway = await CreateCompositeSchemaAsync( diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs index 258fc9aedb8..b02c9ad5c08 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/SourceSchemaErrorTests.cs @@ -133,9 +133,8 @@ public async Task No_Data_And_Error_Without_Path_For_Root_Field(ErrorHandlingMod using var server1 = CreateSourceSchema( "A", b => b.AddQueryType() - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationExecutionMiddleware, - middleware: (_, _) => + .UseRequest( + (_, _) => { return context => { @@ -147,7 +146,8 @@ public async Task No_Data_And_Error_Without_Path_For_Root_Field(ErrorHandlingMod return ValueTask.CompletedTask; }; }, - key: "error")); + key: "error", + before: WellKnownRequestMiddleware.OperationExecutionMiddleware)); using var gateway = await CreateCompositeSchemaAsync( [ @@ -185,9 +185,8 @@ public async Task No_Data_And_Error_Without_Path_For_Root_Field_NonNull(ErrorHan using var server1 = CreateSourceSchema( "A", b => b.AddQueryType() - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationExecutionMiddleware, - middleware: (_, _) => + .UseRequest( + (_, _) => { return context => { @@ -199,7 +198,8 @@ public async Task No_Data_And_Error_Without_Path_For_Root_Field_NonNull(ErrorHan return ValueTask.CompletedTask; }; }, - key: "error")); + key: "error", + before: WellKnownRequestMiddleware.OperationExecutionMiddleware)); using var gateway = await CreateCompositeSchemaAsync( [ @@ -665,9 +665,8 @@ public async Task No_Data_And_Error_Without_Path_For_Lookup_Field_NonNull(ErrorH using var server2 = CreateSourceSchema( "B", b => b.AddQueryType() - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationExecutionMiddleware, - middleware: (_, _) => + .UseRequest( + (_, _) => { return context => { @@ -679,7 +678,8 @@ public async Task No_Data_And_Error_Without_Path_For_Lookup_Field_NonNull(ErrorH return ValueTask.CompletedTask; }; }, - key: "error")); + key: "error", + before: WellKnownRequestMiddleware.OperationExecutionMiddleware)); using var gateway = await CreateCompositeSchemaAsync( [ diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs index d085eb34e35..b4a34a89033 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/FusionRequestExecutorManagerTests.cs @@ -82,8 +82,7 @@ type Query { .AddGraphQLGateway() .AddInMemoryConfiguration(schemaDocument) .UseDefaultPipeline() - .InsertUseRequest( - before: WellKnownRequestMiddleware.OperationExecutionMiddleware, + .UseRequest( (_, _) => { return context => @@ -94,7 +93,9 @@ type Query { ImmutableOrderedDictionary.Empty.Add("operationPlan", plan)); return ValueTask.CompletedTask; }; - }) + }, + before: WellKnownRequestMiddleware.OperationExecutionMiddleware, + allowMultiple: true) .Services .BuildServiceProvider(); diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/OperationPlanSingleFlightTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/OperationPlanSingleFlightTests.cs index 7bd1d835b1a..60922611151 100644 --- a/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/OperationPlanSingleFlightTests.cs +++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Execution.Tests/Execution/OperationPlanSingleFlightTests.cs @@ -24,18 +24,22 @@ public async Task Concurrent_Same_Operation_Should_Be_Coalesced_To_One_Planning_ .AddGraphQLGateway() .UseDefaultPipeline() .AddDiagnosticEventListener(_ => listener) - .InsertUseRequest( + .UseRequest( + (_, next) => CreateGateMiddleware(next, gate), before: WellKnownRequestMiddleware.OperationPlanCacheMiddleware, - (_, next) => CreateGateMiddleware(next, gate)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, next) => CreateSingleFlightLeaderDelayMiddleware(next, TimeSpan.FromMilliseconds(100)), before: WellKnownRequestMiddleware.OperationPlanMiddleware, - (_, next) => CreateSingleFlightLeaderDelayMiddleware(next, TimeSpan.FromMilliseconds(100))) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds), before: WellKnownRequestMiddleware.OperationPlanMiddleware, - (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, _) => CreatePlanCaptureMiddleware(), before: WellKnownRequestMiddleware.OperationExecutionMiddleware, - (_, _) => CreatePlanCaptureMiddleware()) + allowMultiple: true) .AddInMemoryConfiguration( ComposeSchemaDocument( """ @@ -78,15 +82,18 @@ public async Task Concurrent_Distinct_Operations_Should_Not_Be_Coalesced() .AddGraphQLGateway() .UseDefaultPipeline() .AddDiagnosticEventListener(_ => listener) - .InsertUseRequest( + .UseRequest( + (_, next) => CreateGateMiddleware(next, gate), before: WellKnownRequestMiddleware.OperationPlanCacheMiddleware, - (_, next) => CreateGateMiddleware(next, gate)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds), before: WellKnownRequestMiddleware.OperationPlanMiddleware, - (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, _) => CreatePlanCaptureMiddleware(), before: WellKnownRequestMiddleware.OperationExecutionMiddleware, - (_, _) => CreatePlanCaptureMiddleware()) + allowMultiple: true) .AddInMemoryConfiguration( ComposeSchemaDocument( """ @@ -139,15 +146,18 @@ public async Task Leader_Planning_Failure_Should_Be_Observed_By_Followers() .UseDefaultPipeline() .AddDiagnosticEventListener(_ => listener) .ModifyPlannerOptions(o => o.MaxPlanningTime = TimeSpan.FromTicks(1)) - .InsertUseRequest( + .UseRequest( + (_, next) => CreateSecondRequestEnteredDownstreamMiddleware(next, secondRequestObserver), before: WellKnownRequestMiddleware.OperationPlanCacheMiddleware, - (_, next) => CreateSecondRequestEnteredDownstreamMiddleware(next, secondRequestObserver)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, next) => CreateSingleFlightLeaderBlockMiddleware(next, leaderGate), before: WellKnownRequestMiddleware.OperationPlanMiddleware, - (_, next) => CreateSingleFlightLeaderBlockMiddleware(next, leaderGate)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds), before: WellKnownRequestMiddleware.OperationPlanMiddleware, - (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds)) + allowMultiple: true) .AddInMemoryConfiguration( ComposeSchemaDocument( """ @@ -201,12 +211,14 @@ public async Task Follower_Cancellation_Should_Not_Cancel_Leader_Planning() .UseDefaultPipeline() .AddDiagnosticEventListener(_ => listener) .AddOperationPlannerInterceptor(_ => blockingInterceptor) - .InsertUseRequest( + .UseRequest( + (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds), before: WellKnownRequestMiddleware.OperationPlanMiddleware, - (_, next) => CreateOperationIdCaptureMiddleware(next, operationIds)) - .InsertUseRequest( + allowMultiple: true) + .UseRequest( + (_, _) => CreatePlanCaptureMiddleware(), before: WellKnownRequestMiddleware.OperationExecutionMiddleware, - (_, _) => CreatePlanCaptureMiddleware()) + allowMultiple: true) .AddInMemoryConfiguration( ComposeSchemaDocument( """