From 8397331e8d63c68e0d6b363b5b00a2c39889fd34 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Sat, 17 Feb 2024 16:53:19 +1100 Subject: [PATCH] Rewrite Middleware Compiler with Source Generators (#6896) --- ...ryCacheRequestExecutorBuilderExtensions.cs | 2 +- .../src/Caching/QueryCacheMiddleware.cs | 17 +- .../test/Caching.Tests/HttpCachingTests.cs | 2 +- ...chingTests.JustScope_Should_Not_Cache.snap | 4 +- ...hingTests.MaxAgeAndScope_Should_Cache.snap | 4 +- ...hingTests.MaxAge_NonZero_Should_Cache.snap | 4 +- ...CachingTests.MaxAge_Zero_Should_Cache.snap | 4 +- .../src/Abstractions/MutationAttribute.cs | 11 + .../Abstractions/MutationFieldAttribute.cs | 6 - .../Core/src/Abstractions/QueryAttribute.cs | 11 + .../src/Abstractions/QueryFieldAttribute.cs | 6 - .../Abstractions/SchemaServiceAttribute.cs | 4 +- .../src/Abstractions/SubscriptionAttribute.cs | 11 + .../SubscriptionFieldAttribute.cs | 6 - .../Caching/DefaultPreparedOperationCache.cs | 9 +- ...estExecutorBuilderExtensions.UseRequest.cs | 79 ++--- .../Pipeline/DocumentCacheMiddleware.cs | 23 +- .../Pipeline/DocumentParserMiddleware.cs | 25 +- .../Pipeline/DocumentValidationMiddleware.cs | 23 +- .../Execution/Pipeline/ExceptionMiddleware.cs | 24 +- .../Pipeline/InstrumentationMiddleware.cs | 14 +- .../OnlyPersistedQueriesAllowedMiddleware.cs | 21 +- .../Pipeline/OperationCacheMiddleware.cs | 15 +- .../Pipeline/OperationComplexityMiddleware.cs | 18 +- .../Pipeline/OperationExecutionMiddleware.cs | 33 +- .../Pipeline/OperationResolverMiddleware.cs | 20 +- .../OperationVariableCoercionMiddleware.cs | 12 +- .../PersistedQueryNotFoundMiddleware.cs | 21 +- .../Pipeline/ReadPersistedQueryMiddleware.cs | 17 +- .../Execution/Pipeline/TimeoutMiddleware.cs | 13 +- .../Pipeline/WritePersistedQueryMiddleware.cs | 15 +- .../Generators/ModuleSyntaxGenerator.cs | 18 +- .../RequestMiddlewareSyntaxGenerator.cs | 287 ++++++++++++++++++ .../Helpers/CodeWriterExtensions.cs | 1 + .../HotChocolate.Types.Analyzers.csproj | 1 + .../Types.Analyzers/Inspectors/ModuleInfo.cs | 2 +- .../Inspectors/RequestMiddlewareInfo.cs | 107 +++++++ .../Inspectors/RequestMiddlewareInspector.cs | 172 +++++++++++ .../RequestMiddlewareParameterInfo.cs | 45 +++ .../RequestMiddlewareParameterKind.cs | 10 + .../MiddlewareModuleGenerator.cs | 115 +++++++ .../Properties/launchSettings.json | 9 + .../Types.Analyzers/TypeModuleGenerator.cs | 4 +- .../Types.Analyzers/WellKnownAttributes.cs | 6 +- .../src/Types.Analyzers/WellKnownFileNames.cs | 1 + .../src/Types.Analyzers/WellKnownTypes.cs | 3 + .../Pipeline/DocumentCacheMiddlewareTests.cs | 25 +- .../Pipeline/DocumentParserMiddlewareTests.cs | 15 +- .../DocumentValidationMiddlewareTests.cs | 10 +- .../Pipeline/ExceptionMiddlewareTests.cs | 24 +- .../AnnotationBasedSchemaTests.cs | 25 +- .../HotChocolate.Types.Analyzers.Tests.csproj | 3 +- .../Types.Analyzers.Tests/RootTypeTests.cs | 4 +- .../SomeRequestMiddleware.cs | 18 ++ .../StaticQueryExtension.cs | 2 +- .../SchemaTests.ExecuteWithMiddleware.snap | 5 + .../Core/test/Types.Analyzers.Tests/foo.cs | 7 + .../FusionRequestExecutorBuilderExtensions.cs | 22 +- .../Pipeline/OperationExecutionMiddleware.cs | 25 ++ .../Utilities/src/Utilities/Cache.cs | 12 +- .../src/Utilities/CacheEntryEventArgs.cs | 3 +- .../src/Utilities/CreateServiceDelegate.cs | 5 - .../src/Utilities/CreateServiceDelegate~1.cs | 5 - .../src/Utilities/MiddlewareCompiler.cs | 2 +- 64 files changed, 1229 insertions(+), 238 deletions(-) create mode 100644 src/HotChocolate/Core/src/Abstractions/MutationAttribute.cs delete mode 100644 src/HotChocolate/Core/src/Abstractions/MutationFieldAttribute.cs create mode 100644 src/HotChocolate/Core/src/Abstractions/QueryAttribute.cs delete mode 100644 src/HotChocolate/Core/src/Abstractions/QueryFieldAttribute.cs create mode 100644 src/HotChocolate/Core/src/Abstractions/SubscriptionAttribute.cs delete mode 100644 src/HotChocolate/Core/src/Abstractions/SubscriptionFieldAttribute.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/MiddlewareModuleGenerator.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Properties/launchSettings.json create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeRequestMiddleware.cs create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/SchemaTests.ExecuteWithMiddleware.snap create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/foo.cs delete mode 100644 src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate.cs delete mode 100644 src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate~1.cs diff --git a/src/HotChocolate/Caching/src/Caching/Extensions/QueryCacheRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Caching/src/Caching/Extensions/QueryCacheRequestExecutorBuilderExtensions.cs index d23973e80a9..66d165a0b2f 100644 --- a/src/HotChocolate/Caching/src/Caching/Extensions/QueryCacheRequestExecutorBuilderExtensions.cs +++ b/src/HotChocolate/Caching/src/Caching/Extensions/QueryCacheRequestExecutorBuilderExtensions.cs @@ -16,7 +16,7 @@ public static class QueryCacheRequestExecutorBuilderExtensions /// public static IRequestExecutorBuilder UseQueryCache( this IRequestExecutorBuilder builder) - => builder.UseRequest(); + => builder.UseRequest(QueryCacheMiddleware.Create()); /// /// Uses the default request pipeline including the diff --git a/src/HotChocolate/Caching/src/Caching/QueryCacheMiddleware.cs b/src/HotChocolate/Caching/src/Caching/QueryCacheMiddleware.cs index 5568e8704ca..caad6ddc2b6 100644 --- a/src/HotChocolate/Caching/src/Caching/QueryCacheMiddleware.cs +++ b/src/HotChocolate/Caching/src/Caching/QueryCacheMiddleware.cs @@ -1,15 +1,16 @@ using System.Threading.Tasks; using HotChocolate.Execution; +using Microsoft.Extensions.DependencyInjection; using static HotChocolate.WellKnownContextData; namespace HotChocolate.Caching; internal sealed class QueryCacheMiddleware { - private readonly RequestDelegate _next; private readonly ICacheControlOptions _options; - - public QueryCacheMiddleware( + private readonly RequestDelegate _next; + + private QueryCacheMiddleware( RequestDelegate next, [SchemaService] ICacheControlOptionsAccessor optionsAccessor) { @@ -58,4 +59,12 @@ queryResult.ContextData is not null queryResult.HasNext); } } -} + + internal static RequestCoreMiddleware Create() + => (core, next) => + { + var options = core.SchemaServices.GetRequiredService(); + var middleware = new QueryCacheMiddleware(next, options); + return context => middleware.InvokeAsync(context); + }; +} \ No newline at end of file diff --git a/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs b/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs index c19157d9cf8..9d011736be1 100644 --- a/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs +++ b/src/HotChocolate/Caching/test/Caching.Tests/HttpCachingTests.cs @@ -2,9 +2,9 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using CookieCrumble; using HotChocolate.Types; using Microsoft.Extensions.DependencyInjection; -using Snapshooter.Xunit; using Xunit; namespace HotChocolate.Caching.Http.Tests; diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.JustScope_Should_Not_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.JustScope_Should_Not_Cache.snap index 59a24341c33..140ac8d2711 100644 --- a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.JustScope_Should_Not_Cache.snap +++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.JustScope_Should_Not_Cache.snap @@ -1,4 +1,4 @@ -{ +{ "Headers": [], "ContentHeaders": [ { @@ -9,4 +9,4 @@ } ], "Body": "{\"data\":{\"field\":\"\"}}" -} +} \ No newline at end of file diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAgeAndScope_Should_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAgeAndScope_Should_Cache.snap index e063c8da1c9..6a061c5626f 100644 --- a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAgeAndScope_Should_Cache.snap +++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAgeAndScope_Should_Cache.snap @@ -1,4 +1,4 @@ -{ +{ "Headers": [ { "Key": "Cache-Control", @@ -16,4 +16,4 @@ } ], "Body": "{}" -} +} \ No newline at end of file diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_NonZero_Should_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_NonZero_Should_Cache.snap index a2024046a4b..045af20227f 100644 --- a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_NonZero_Should_Cache.snap +++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_NonZero_Should_Cache.snap @@ -1,4 +1,4 @@ -{ +{ "Headers": [ { "Key": "Cache-Control", @@ -16,4 +16,4 @@ } ], "Body": "{}" -} +} \ No newline at end of file diff --git a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_Zero_Should_Cache.snap b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_Zero_Should_Cache.snap index 1284ae02eae..0a3c8d90948 100644 --- a/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_Zero_Should_Cache.snap +++ b/src/HotChocolate/Caching/test/Caching.Tests/__snapshots__/HttpCachingTests.MaxAge_Zero_Should_Cache.snap @@ -1,4 +1,4 @@ -{ +{ "Headers": [ { "Key": "Cache-Control", @@ -16,4 +16,4 @@ } ], "Body": "{}" -} +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/MutationAttribute.cs b/src/HotChocolate/Core/src/Abstractions/MutationAttribute.cs new file mode 100644 index 00000000000..3b9441f8089 --- /dev/null +++ b/src/HotChocolate/Core/src/Abstractions/MutationAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace HotChocolate; + +/// +/// Marks a public/internal static method or property as a mutation root field. +/// The Hot Chocolate source generator will collect these and merge them into +/// the mutation type. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] +public sealed class MutationAttribute : Attribute; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/MutationFieldAttribute.cs b/src/HotChocolate/Core/src/Abstractions/MutationFieldAttribute.cs deleted file mode 100644 index 299e8c2f769..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/MutationFieldAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace HotChocolate; - -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] -public sealed class MutationFieldAttribute : Attribute; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/QueryAttribute.cs b/src/HotChocolate/Core/src/Abstractions/QueryAttribute.cs new file mode 100644 index 00000000000..a99a3cdc3c2 --- /dev/null +++ b/src/HotChocolate/Core/src/Abstractions/QueryAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace HotChocolate; + +/// +/// Marks a public/internal static method or property as a query root field. +/// The Hot Chocolate source generator will collect these and merge them into +/// the query type. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] +public sealed class QueryAttribute : Attribute; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/QueryFieldAttribute.cs b/src/HotChocolate/Core/src/Abstractions/QueryFieldAttribute.cs deleted file mode 100644 index a49b8021eb2..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/QueryFieldAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace HotChocolate; - -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] -public sealed class QueryFieldAttribute : Attribute; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/SchemaServiceAttribute.cs b/src/HotChocolate/Core/src/Abstractions/SchemaServiceAttribute.cs index 59e0d6b6148..a8a85b31c65 100644 --- a/src/HotChocolate/Core/src/Abstractions/SchemaServiceAttribute.cs +++ b/src/HotChocolate/Core/src/Abstractions/SchemaServiceAttribute.cs @@ -3,6 +3,4 @@ namespace HotChocolate; [AttributeUsage(AttributeTargets.Parameter)] -public sealed class SchemaServiceAttribute : Attribute -{ -} +public sealed class SchemaServiceAttribute : Attribute; diff --git a/src/HotChocolate/Core/src/Abstractions/SubscriptionAttribute.cs b/src/HotChocolate/Core/src/Abstractions/SubscriptionAttribute.cs new file mode 100644 index 00000000000..bfb127e012f --- /dev/null +++ b/src/HotChocolate/Core/src/Abstractions/SubscriptionAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace HotChocolate; + +/// +/// Marks a public/internal static method or property as a subscription root field. +/// The Hot Chocolate source generator will collect these and merge them into +/// the subscription type. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] +public sealed class SubscriptionAttribute : Attribute; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/SubscriptionFieldAttribute.cs b/src/HotChocolate/Core/src/Abstractions/SubscriptionFieldAttribute.cs deleted file mode 100644 index 048a86d6e29..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/SubscriptionFieldAttribute.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace HotChocolate; - -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] -public sealed class SubscriptionFieldAttribute : Attribute; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/Caching/DefaultPreparedOperationCache.cs b/src/HotChocolate/Core/src/Execution/Caching/DefaultPreparedOperationCache.cs index 375a47a08f0..0282a63fccd 100644 --- a/src/HotChocolate/Core/src/Execution/Caching/DefaultPreparedOperationCache.cs +++ b/src/HotChocolate/Core/src/Execution/Caching/DefaultPreparedOperationCache.cs @@ -4,14 +4,9 @@ namespace HotChocolate.Execution.Caching; -internal sealed class DefaultPreparedOperationCache : IPreparedOperationCache +internal sealed class DefaultPreparedOperationCache(int capacity = 100) : IPreparedOperationCache { - private readonly Cache _cache; - - public DefaultPreparedOperationCache(int capacity = 100) - { - _cache = new Cache(capacity); - } + private readonly Cache _cache = new(capacity); public int Capacity => _cache.Capacity; diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs index a336f27aeb5..efa200df49f 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.UseRequest.cs @@ -84,85 +84,86 @@ public static IRequestExecutorBuilder UseRequest( public static IRequestExecutorBuilder UseDocumentCache( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(DocumentCacheMiddleware.Create()); public static IRequestExecutorBuilder UseDocumentParser( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(DocumentParserMiddleware.Create()); public static IRequestExecutorBuilder UseDocumentValidation( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(DocumentValidationMiddleware.Create()); public static IRequestExecutorBuilder UseExceptions( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(ExceptionMiddleware.Create()); public static IRequestExecutorBuilder UseTimeout( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(DocumentParserMiddleware.Create()); public static IRequestExecutorBuilder UseInstrumentation( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(InstrumentationMiddleware.Create()); public static IRequestExecutorBuilder UseOperationCache( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(OperationCacheMiddleware.Create()); public static IRequestExecutorBuilder UseOperationComplexityAnalyzer( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(OperationComplexityMiddleware.Create()); public static IRequestExecutorBuilder UseOperationExecution( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(OperationExecutionMiddleware.Create()); public static IRequestExecutorBuilder UseOperationResolver( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(OperationResolverMiddleware.Create()); public static IRequestExecutorBuilder UseOperationVariableCoercion( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(OperationVariableCoercionMiddleware.Create()); public static IRequestExecutorBuilder UseReadPersistedQuery( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(ReadPersistedQueryMiddleware.Create()); public static IRequestExecutorBuilder UseAutomaticPersistedQueryNotFound( this IRequestExecutorBuilder builder) => builder.UseRequest(next => context => { - if (context.Document is null && context.Request.Query is null) + if (context.Document is not null || context.Request.Query is not null) { - var error = ReadPersistedQueryMiddleware_PersistedQueryNotFound(); - var result = QueryResultBuilder.CreateError( - error, - new Dictionary - { - { WellKnownContextData.HttpStatusCode, HttpStatusCode.BadRequest }, - }); - - context.DiagnosticEvents.RequestError(context, new GraphQLException(error)); - context.Result = result; - return default; + return next(context); } + + var error = ReadPersistedQueryMiddleware_PersistedQueryNotFound(); + var result = QueryResultBuilder.CreateError( + error, + new Dictionary + { + { WellKnownContextData.HttpStatusCode, HttpStatusCode.BadRequest }, + }); + + context.DiagnosticEvents.RequestError(context, new GraphQLException(error)); + context.Result = result; + return default; - return next(context); }); public static IRequestExecutorBuilder UseWritePersistedQuery( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(WritePersistedQueryMiddleware.Create()); public static IRequestExecutorBuilder UsePersistedQueryNotFound( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(PersistedQueryNotFoundMiddleware.Create()); public static IRequestExecutorBuilder UseOnlyPersistedQueriesAllowed( this IRequestExecutorBuilder builder) => - builder.UseRequest(); + builder.UseRequest(OnlyPersistedQueriesAllowedMiddleware.Create()); public static IRequestExecutorBuilder UseDefaultPipeline( this IRequestExecutorBuilder builder) @@ -229,16 +230,16 @@ public static IRequestExecutorBuilder UseAutomaticPersistedQueryPipeline( internal static void AddDefaultPipeline(this IList pipeline) { - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); + pipeline.Add(InstrumentationMiddleware.Create()); + pipeline.Add(ExceptionMiddleware.Create()); + pipeline.Add(TimeoutMiddleware.Create()); + pipeline.Add(DocumentCacheMiddleware.Create()); + pipeline.Add(DocumentParserMiddleware.Create()); + pipeline.Add(DocumentValidationMiddleware.Create()); + pipeline.Add(OperationCacheMiddleware.Create()); + pipeline.Add(OperationComplexityMiddleware.Create()); + pipeline.Add(OperationResolverMiddleware.Create()); + pipeline.Add(OperationVariableCoercionMiddleware.Create()); + pipeline.Add(OperationExecutionMiddleware.Create()); } } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentCacheMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentCacheMiddleware.cs index 56c89421663..63ab8c0cb80 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentCacheMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentCacheMiddleware.cs @@ -3,6 +3,7 @@ using HotChocolate.Execution.Instrumentation; using HotChocolate.Language; using HotChocolate.Validation; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -13,9 +14,8 @@ internal sealed class DocumentCacheMiddleware private readonly IDocumentCache _documentCache; private readonly IDocumentHashProvider _documentHashProvider; - public DocumentCacheMiddleware( - RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents, + private DocumentCacheMiddleware(RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, IDocumentCache documentCache, IDocumentHashProvider documentHashProvider) { @@ -84,4 +84,21 @@ public async ValueTask InvokeAsync(IRequestContext context) _diagnosticEvents.AddedDocumentToCache(context); } } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var documentCache = core.Services.GetRequiredService(); + var documentHashProvider = core.Services.GetRequiredService(); + var middleware = Create(next, diagnosticEvents, documentCache, documentHashProvider); + return context => middleware.InvokeAsync(context); + }; + + internal static DocumentCacheMiddleware Create( + RequestDelegate next, + IExecutionDiagnosticEvents diagnosticEvents, + IDocumentCache documentCache, + IDocumentHashProvider documentHashProvider) + => new(next, diagnosticEvents, documentCache, documentHashProvider); } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentParserMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentParserMiddleware.cs index f40dc9dde04..6beeb78d67c 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentParserMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentParserMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using HotChocolate.Execution.Instrumentation; using HotChocolate.Language; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -12,9 +13,8 @@ internal sealed class DocumentParserMiddleware private readonly IDocumentHashProvider _documentHashProvider; private readonly ParserOptions _parserOptions; - public DocumentParserMiddleware( - RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents, + private DocumentParserMiddleware(RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, IDocumentHashProvider documentHashProvider, ParserOptions parserOptions) { @@ -96,4 +96,21 @@ private string ComputeDocumentHash(string? documentHash, string? queryHash, IQue { return documentHash ?? queryHash ?? _documentHashProvider.ComputeHash(query.AsSpan()); } -} + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var documentHashProvider = core.Services.GetRequiredService(); + var parserOptions = core.Services.GetRequiredService(); + var middleware = Create(next, diagnosticEvents, documentHashProvider, parserOptions); + return context => middleware.InvokeAsync(context); + }; + + internal static DocumentParserMiddleware Create( + RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, + IDocumentHashProvider documentHashProvider, + ParserOptions parserOptions) + => new(next, diagnosticEvents, documentHashProvider, parserOptions); +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs index 21f0233aecb..376ca96d94b 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using HotChocolate.Execution.Instrumentation; using HotChocolate.Validation; +using Microsoft.Extensions.DependencyInjection; using static HotChocolate.WellKnownContextData; using static HotChocolate.Execution.ErrorHelper; @@ -14,9 +15,9 @@ internal sealed class DocumentValidationMiddleware private readonly IExecutionDiagnosticEvents _diagnosticEvents; private readonly IDocumentValidator _documentValidator; - public DocumentValidationMiddleware( + private DocumentValidationMiddleware( RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, IDocumentValidator documentValidator) { _next = next ?? @@ -85,4 +86,20 @@ await _documentValidator await _next(context).ConfigureAwait(false); } } -} + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var documentValidatorFactory = core.Services.GetRequiredService(); + var documentValidator = documentValidatorFactory.CreateValidator(core.SchemaName); + var middleware = Create(next, diagnosticEvents, documentValidator); + return context => middleware.InvokeAsync(context); + }; + + internal static DocumentValidationMiddleware Create( + RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, + IDocumentValidator documentValidator) + => new(next, diagnosticEvents, documentValidator); +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/ExceptionMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/ExceptionMiddleware.cs index 76f23a667c3..7c08d665c0f 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/ExceptionMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/ExceptionMiddleware.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -8,10 +9,12 @@ internal sealed class ExceptionMiddleware private readonly RequestDelegate _next; private readonly IErrorHandler _errorHandler; - public ExceptionMiddleware(RequestDelegate next, IErrorHandler errorHandler) + private ExceptionMiddleware(RequestDelegate next, [SchemaService] IErrorHandler errorHandler) { - _next = next ?? throw new ArgumentNullException(nameof(next)); - _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); + _next = next ?? + throw new ArgumentNullException(nameof(next)); + _errorHandler = errorHandler ?? + throw new ArgumentNullException(nameof(errorHandler)); } public async ValueTask InvokeAsync(IRequestContext context) @@ -37,4 +40,17 @@ public async ValueTask InvokeAsync(IRequestContext context) context.Result = QueryResultBuilder.CreateError(_errorHandler.Handle(error)); } } -} + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var errorHandler = core.SchemaServices.GetRequiredService(); + var middleware = Create(next, errorHandler); + return context => middleware.InvokeAsync(context); + }; + + internal static ExceptionMiddleware Create( + RequestDelegate next, + IErrorHandler errorHandler) + => new(next, errorHandler); +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/InstrumentationMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/InstrumentationMiddleware.cs index fcfe9be4391..9b91d4039e3 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/InstrumentationMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/InstrumentationMiddleware.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using HotChocolate.Execution.Instrumentation; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -9,9 +10,8 @@ internal sealed class InstrumentationMiddleware private readonly RequestDelegate _next; private readonly IExecutionDiagnosticEvents _diagnosticEvents; - public InstrumentationMiddleware( - RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents) + private InstrumentationMiddleware(RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents) { _next = next ?? throw new ArgumentNullException(nameof(next)); @@ -31,4 +31,12 @@ public async ValueTask InvokeAsync(IRequestContext context) } } } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var middleware = new InstrumentationMiddleware(next, diagnosticEvents); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OnlyPersistedQueriesAllowedMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OnlyPersistedQueriesAllowedMiddleware.cs index c31063b1314..75922bb74f1 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OnlyPersistedQueriesAllowedMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OnlyPersistedQueriesAllowedMiddleware.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using HotChocolate.Execution.Instrumentation; using HotChocolate.Execution.Options; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -13,15 +14,12 @@ internal sealed class OnlyPersistedQueriesAllowedMiddleware private readonly bool _allowAllQueries; private readonly IQueryResult _errorResult; private readonly GraphQLException _exception; - private readonly Dictionary _statusCode = new() - { - { WellKnownContextData.HttpStatusCode, 400 }, - }; + private readonly Dictionary _statusCode = new() { { WellKnownContextData.HttpStatusCode, 400 }, }; - public OnlyPersistedQueriesAllowedMiddleware( + private OnlyPersistedQueriesAllowedMiddleware( RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents, - IPersistedQueryOptionsAccessor options) + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, + [SchemaService] IPersistedQueryOptionsAccessor options) { if (options is null) { @@ -56,4 +54,13 @@ context.Request.Query is null || return default; } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var options = core.SchemaServices.GetRequiredService(); + var middleware = new OnlyPersistedQueriesAllowedMiddleware(next, diagnosticEvents, options); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationCacheMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationCacheMiddleware.cs index d2fea0f49fd..1fb45254cc7 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationCacheMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationCacheMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using HotChocolate.Execution.Caching; using HotChocolate.Execution.Instrumentation; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -11,9 +12,8 @@ internal sealed class OperationCacheMiddleware private readonly IExecutionDiagnosticEvents _diagnosticEvents; private readonly IPreparedOperationCache _operationCache; - public OperationCacheMiddleware( - RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents, + private OperationCacheMiddleware(RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, IPreparedOperationCache operationCache) { _next = next ?? @@ -61,4 +61,13 @@ context.Document is not null && } } } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var cache = core.Services.GetRequiredService(); + var middleware = new OperationCacheMiddleware(next, diagnosticEvents, cache); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationComplexityMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationComplexityMiddleware.cs index 52acb073adc..d0bb699d316 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationComplexityMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationComplexityMiddleware.cs @@ -6,6 +6,7 @@ using HotChocolate.Execution.Processing; using HotChocolate.Language; using HotChocolate.Validation; +using Microsoft.Extensions.DependencyInjection; using static HotChocolate.Execution.ErrorHelper; using static HotChocolate.Execution.Pipeline.PipelineTools; using static HotChocolate.WellKnownContextData; @@ -21,10 +22,10 @@ internal sealed class OperationComplexityMiddleware private readonly ComplexityAnalyzerCompiler _compiler; private readonly VariableCoercionHelper _coercionHelper; - public OperationComplexityMiddleware( + private OperationComplexityMiddleware( RequestDelegate next, DocumentValidatorContextPool contextPool, - IComplexityAnalyzerOptionsAccessor options, + [SchemaService] IComplexityAnalyzerOptionsAccessor options, IComplexityAnalyzerCache cache, VariableCoercionHelper coercionHelper) { @@ -127,7 +128,7 @@ private ComplexityAnalyzerDelegate CompileAnalyzer( } } - private void PrepareContext( + private static void PrepareContext( IRequestContext requestContext, DocumentNode document, DocumentValidatorContext validatorContext) @@ -155,4 +156,15 @@ private int GetMaximumAllowedComplexity(IRequestContext requestContext) return _settings.MaximumAllowed; } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var contextPool = core.Services.GetRequiredService(); + var options = core.SchemaServices.GetRequiredService(); + var cache = core.Services.GetRequiredService(); + var coercionHelper = core.Services.GetRequiredService(); + var middleware = new OperationComplexityMiddleware(next, contextPool, options, cache, coercionHelper); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs index 2e4074ebfe9..825aad6e45a 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs @@ -4,6 +4,7 @@ using HotChocolate.Execution.Processing; using HotChocolate.Fetching; using HotChocolate.Language; +using Microsoft.Extensions.DependencyInjection; using static HotChocolate.Execution.GraphQLRequestFlags; using static HotChocolate.Execution.ThrowHelper; @@ -18,12 +19,11 @@ internal sealed class OperationExecutionMiddleware private readonly ITransactionScopeHandler _transactionScopeHandler; private object? _cachedQuery; private object? _cachedMutation; - - public OperationExecutionMiddleware( - RequestDelegate next, + + private OperationExecutionMiddleware(RequestDelegate next, IFactory contextFactory, - QueryExecutor queryExecutor, - SubscriptionExecutor subscriptionExecutor, + [SchemaService] QueryExecutor queryExecutor, + [SchemaService] SubscriptionExecutor subscriptionExecutor, [SchemaService] ITransactionScopeHandler transactionScopeHandler) { _next = next ?? @@ -213,4 +213,25 @@ private static bool IsOperationAllowed(IOperation operation, IQueryRequest reque return allowed; } -} + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var contextFactory = core.Services.GetRequiredService>(); + var queryExecutor = core.SchemaServices.GetRequiredService(); + var subscriptionExecutor = core.SchemaServices.GetRequiredService(); + var transactionScopeHandler = core.SchemaServices.GetRequiredService(); + var middleware = new OperationExecutionMiddleware( + next, + contextFactory, + queryExecutor, + subscriptionExecutor, + transactionScopeHandler); + + return async context => + { + var batchDispatcher = context.Services.GetService(); + await middleware.InvokeAsync(context, batchDispatcher).ConfigureAwait(false); + }; + }; +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs index 3c1be80925f..257d98021e3 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationResolverMiddleware.cs @@ -6,6 +6,7 @@ using HotChocolate.Language; using HotChocolate.Types; using HotChocolate.Utilities; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.ObjectPool; using static HotChocolate.Execution.ErrorHelper; using static HotChocolate.WellKnownDirectives; @@ -21,7 +22,7 @@ internal sealed class OperationResolverMiddleware private readonly VariableCoercionHelper _coercionHelper; private readonly IReadOnlyList? _optimizers; - public OperationResolverMiddleware( + private OperationResolverMiddleware( RequestDelegate next, ObjectPool operationCompilerPool, IEnumerable optimizers, @@ -161,4 +162,19 @@ private bool CoerceVariable( var variables = CoerceVariables(context, _coercionHelper, operationDefinition.VariableDefinitions); return variables.GetVariable(variable.Name.Value); } -} + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var operationCompilerPool = core.Services.GetRequiredService>(); + var optimizers1 = core.Services.GetRequiredService>(); + var optimizers2 = core.SchemaServices.GetRequiredService>(); + var coercionHelper = core.Services.GetRequiredService(); + var middleware = new OperationResolverMiddleware( + next, + operationCompilerPool, + optimizers1.Concat(optimizers2), + coercionHelper); + return context => middleware.InvokeAsync(context); + }; +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationVariableCoercionMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationVariableCoercionMiddleware.cs index 8c231e2fe64..606119fd3ac 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationVariableCoercionMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationVariableCoercionMiddleware.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using HotChocolate.Execution.Processing; +using Microsoft.Extensions.DependencyInjection; using static HotChocolate.Execution.Pipeline.PipelineTools; namespace HotChocolate.Execution.Pipeline; @@ -10,8 +11,7 @@ internal sealed class OperationVariableCoercionMiddleware private readonly RequestDelegate _next; private readonly VariableCoercionHelper _coercionHelper; - public OperationVariableCoercionMiddleware( - RequestDelegate next, + private OperationVariableCoercionMiddleware(RequestDelegate next, VariableCoercionHelper coercionHelper) { _next = next ?? @@ -36,4 +36,12 @@ public async ValueTask InvokeAsync(IRequestContext context) context.Result = ErrorHelper.StateInvalidForOperationVariableCoercion(); } } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var coercionHelper = core.Services.GetRequiredService(); + var middleware = new OperationVariableCoercionMiddleware(next, coercionHelper); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/PersistedQueryNotFoundMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/PersistedQueryNotFoundMiddleware.cs index e18733a4ebc..be903bf5706 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/PersistedQueryNotFoundMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/PersistedQueryNotFoundMiddleware.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using HotChocolate.Execution.Instrumentation; +using Microsoft.Extensions.DependencyInjection; using static HotChocolate.WellKnownContextData; namespace HotChocolate.Execution.Pipeline; @@ -10,20 +11,16 @@ internal sealed class PersistedQueryNotFoundMiddleware { private readonly RequestDelegate _next; private readonly IExecutionDiagnosticEvents _diagnosticEvents; - private readonly Dictionary _statusCode; - - public PersistedQueryNotFoundMiddleware( + private readonly Dictionary _statusCode = new() { { HttpStatusCode, 400 }, }; + + private PersistedQueryNotFoundMiddleware( RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents) + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents) { _next = next ?? throw new ArgumentNullException(nameof(next)); _diagnosticEvents = diagnosticEvents ?? throw new ArgumentNullException(nameof(diagnosticEvents)); - _statusCode = new Dictionary - { - { HttpStatusCode, 400 }, - }; } public ValueTask InvokeAsync(IRequestContext context) @@ -47,4 +44,12 @@ public ValueTask InvokeAsync(IRequestContext context) return default; } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var middleware = new PersistedQueryNotFoundMiddleware(next, diagnosticEvents); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/ReadPersistedQueryMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/ReadPersistedQueryMiddleware.cs index 2fc0056723b..9290caf4504 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/ReadPersistedQueryMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/ReadPersistedQueryMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using HotChocolate.Execution.Instrumentation; using HotChocolate.Validation; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -11,10 +12,9 @@ internal sealed class ReadPersistedQueryMiddleware private readonly IExecutionDiagnosticEvents _diagnosticEvents; private readonly IReadStoredQueries _persistedQueryStore; - public ReadPersistedQueryMiddleware( - RequestDelegate next, - IExecutionDiagnosticEvents diagnosticEvents, - IReadStoredQueries persistedQueryStore) + private ReadPersistedQueryMiddleware(RequestDelegate next, + [SchemaService] IExecutionDiagnosticEvents diagnosticEvents, + [SchemaService] IReadStoredQueries persistedQueryStore) { _next = next ?? throw new ArgumentNullException(nameof(next)); @@ -61,4 +61,13 @@ await _persistedQueryStore.TryReadQueryAsync( } } } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var diagnosticEvents = core.SchemaServices.GetRequiredService(); + var persistedQueryStore = core.SchemaServices.GetRequiredService(); + var middleware = new ReadPersistedQueryMiddleware(next, diagnosticEvents, persistedQueryStore); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/TimeoutMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/TimeoutMiddleware.cs index 41d4c9f81a5..682a28e63a3 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/TimeoutMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/TimeoutMiddleware.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using HotChocolate.Execution.Options; +using Microsoft.Extensions.DependencyInjection; using static System.Threading.CancellationTokenSource; namespace HotChocolate.Execution.Pipeline; @@ -12,9 +13,9 @@ internal sealed class TimeoutMiddleware private readonly RequestDelegate _next; private readonly TimeSpan _timeout; - public TimeoutMiddleware( + private TimeoutMiddleware( RequestDelegate next, - IRequestExecutorOptionsAccessor options) + [SchemaService] IRequestExecutorOptionsAccessor options) { if (options is null) { @@ -85,5 +86,13 @@ public async ValueTask InvokeAsync(IRequestContext context) } } } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var optionsAccessor = core.SchemaServices.GetRequiredService(); + var middleware = new TimeoutMiddleware(next, optionsAccessor); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs index 017a6e1e0da..de2d131fac8 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using HotChocolate.Language; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution.Pipeline; @@ -17,10 +18,9 @@ internal sealed class WritePersistedQueryMiddleware private readonly IDocumentHashProvider _hashProvider; private readonly IWriteStoredQueries _persistedQueryStore; - public WritePersistedQueryMiddleware( - RequestDelegate next, + private WritePersistedQueryMiddleware(RequestDelegate next, IDocumentHashProvider documentHashProvider, - IWriteStoredQueries persistedQueryStore) + [SchemaService] IWriteStoredQueries persistedQueryStore) { _next = next ?? throw new ArgumentNullException(nameof(next)); @@ -97,4 +97,13 @@ private static bool DoHashesMatch( userHash = null; return false; } + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var documentHashProvider = core.Services.GetRequiredService(); + var persistedQueryStore = core.SchemaServices.GetRequiredService(); + var middleware = new WritePersistedQueryMiddleware(next, documentHashProvider, persistedQueryStore); + return context => middleware.InvokeAsync(context); + }; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs index 62a8ed795f2..7058b97f7b8 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs @@ -1,5 +1,8 @@ using System.Text; using HotChocolate.Types.Analyzers.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Text; namespace HotChocolate.Types.Analyzers.Generators; @@ -11,7 +14,7 @@ public sealed class ModuleSyntaxGenerator : IDisposable private StringBuilder _sb; private CodeWriter _writer; private bool _disposed; - + public ModuleSyntaxGenerator(string moduleName, string ns) { _moduleName = moduleName; @@ -21,7 +24,10 @@ public ModuleSyntaxGenerator(string moduleName, string ns) } public void WriterHeader() - => _writer.WriteFileHeader(); + { + _writer.WriteFileHeader(); + _writer.WriteLine(); + } public void WriteBeginNamespace() { @@ -74,10 +80,10 @@ public void WriteRegisterTypeExtension(string typeName, bool staticType) ? "builder.AddTypeExtension(typeof(global::{0}));" : "builder.AddTypeExtension();", typeName); - + public void WriteRegisterDataLoader(string typeName) => _writer.WriteIndentedLine("builder.AddDataLoader();", typeName); - + public void WriteRegisterDataLoader(string typeName, string interfaceTypeName) => _writer.WriteIndentedLine("builder.AddDataLoader();", interfaceTypeName, typeName); @@ -102,7 +108,7 @@ public void WriteTryAddOperationType(OperationType type) } } } - + public override string ToString() => _sb.ToString(); @@ -115,7 +121,7 @@ public void Dispose() { return; } - + StringBuilderPool.Return(_sb); _sb = default!; _writer = default!; diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs new file mode 100644 index 00000000000..c1a16fdba83 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs @@ -0,0 +1,287 @@ +using System.Text; +using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Inspectors; +using Microsoft.CodeAnalysis.Text; +using static HotChocolate.Types.Analyzers.WellKnownTypes; + +namespace HotChocolate.Types.Analyzers.Generators; + +public sealed class RequestMiddlewareSyntaxGenerator : IDisposable +{ + private readonly string _moduleName; + private readonly string _ns; + private readonly string _id; + private StringBuilder _sb; + private CodeWriter _writer; + private bool _disposed; + + public RequestMiddlewareSyntaxGenerator(string moduleName, string ns) + { + _moduleName = moduleName; + _ns = ns; + + _id = Guid.NewGuid().ToString("N"); + _sb = StringBuilderPool.Get(); + _writer = new(_sb); + } + + public void WriterHeader() + { + _writer.WriteFileHeader(); + _writer.WriteIndentedLine("using Microsoft.Extensions.DependencyInjection;"); + _writer.WriteLine(); + } + + public void WriteBeginNamespace() + { + _writer.WriteIndentedLine("namespace {0}", _ns); + _writer.WriteIndentedLine("{"); + _writer.IncreaseIndent(); + } + + public void WriteEndNamespace() + { + _writer.DecreaseIndent(); + _writer.WriteIndentedLine("}"); + _writer.WriteLine(); + } + + public string WriteBeginClass() + { + var typeName = $"{_moduleName}MiddlewareFactories{_id}"; + _writer.WriteIndentedLine("public static class {0}", typeName); + _writer.WriteIndentedLine("{"); + _writer.IncreaseIndent(); + return typeName; + } + + public void WriteEndClass() + { + _writer.DecreaseIndent(); + _writer.WriteIndentedLine("}"); + } + + public void WriteFactory(int middlewareIndex, RequestMiddlewareInfo middleware) + { + _writer.WriteIndentedLine("// {0}", middleware.TypeName); + _writer.WriteIndentedLine( + "private static global::{0} CreateMiddleware{1}()", + RequestCoreMiddleware, + middlewareIndex); + + using (_writer.IncreaseIndent()) + { + _writer.WriteIndentedLine("=> (core, next) =>"); + + using (_writer.IncreaseIndent()) + { + _writer.WriteIndentedLine("{"); + + using (_writer.IncreaseIndent()) + { + WriteCtorServiceResolution(middleware.CtorParameters); + WriteFactory(middleware.TypeName, middleware.CtorParameters); + _writer.WriteIndentedLine("return async context =>"); + _writer.WriteIndentedLine("{"); + + using (_writer.IncreaseIndent()) + { + WriteInvokeServiceResolution(middleware.InvokeParameters); + WriteInvoke(middleware.InvokeMethodName, middleware.InvokeParameters); + } + _writer.WriteIndentedLine("};"); + } + _writer.WriteIndentedLine("};"); + } + } + } + + private void WriteCtorServiceResolution(List parameters) + { + for (var i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + + switch (parameter.Kind) + { + case RequestMiddlewareParameterKind.Service when !parameter.IsNullable: + _writer.WriteIndentedLine( + "var cp{0} = core.Services.GetRequiredService();", + i, + parameter.TypeName); + break; + + case RequestMiddlewareParameterKind.Service when parameter.IsNullable: + _writer.WriteIndentedLine( + "var cp{0} = core.Services.GetService();", + i, + parameter.TypeName); + break; + + case RequestMiddlewareParameterKind.SchemaService when !parameter.IsNullable: + _writer.WriteIndentedLine( + "var cp{0} = core.SchemaServices.GetRequiredService();", + i, + parameter.TypeName); + break; + + case RequestMiddlewareParameterKind.SchemaService when parameter.IsNullable: + _writer.WriteIndentedLine( + "var cp{0} = core.SchemaServices.GetService();", + i, + parameter.TypeName); + break; + + case RequestMiddlewareParameterKind.Schema: + _writer.WriteIndentedLine( + "var cp{0} = core.SchemaServices.GetRequiredService();", + i, + Schema); + break; + + case RequestMiddlewareParameterKind.Next: + break; + + default: + throw new NotSupportedException("Service kind not supported in location."); + } + } + } + + private void WriteFactory(string typeName, List parameters) + { + _writer.WriteIndented("var middleware = new {0}(", typeName); + + for (var i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + + if (parameter.Kind is RequestMiddlewareParameterKind.Next) + { + _writer.Write("next"); + } + else + { + _writer.Write("cp{0}", i); + } + } + + _writer.WriteLine(");"); + } + + private void WriteInvokeServiceResolution(List parameters) + { + for (var i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + + switch (parameter.Kind) + { + case RequestMiddlewareParameterKind.Service when !parameter.IsNullable: + _writer.WriteIndentedLine( + "var ip{0} = context.Services.GetRequiredService();", + i, + parameter.TypeName); + break; + + case RequestMiddlewareParameterKind.Service when parameter.IsNullable: + _writer.WriteIndentedLine( + "var ip{0} = context.Services.GetService();", + i, + parameter.TypeName); + break; + + case RequestMiddlewareParameterKind.Schema: + _writer.WriteIndentedLine( + "var ip{0} = context.Schema;", + i, + Schema); + break; + + case RequestMiddlewareParameterKind.Next: + break; + + case RequestMiddlewareParameterKind.Context: + break; + + default: + throw new NotSupportedException("Service kind not supported in location."); + } + } + } + + private void WriteInvoke(string methodName, List parameters) + { + _writer.WriteIndented("await middleware.{0}(", methodName); + + for (var i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + + if (parameter.Kind is RequestMiddlewareParameterKind.Next) + { + _writer.Write("next"); + } + else if (parameter.Kind is RequestMiddlewareParameterKind.Context) + { + _writer.Write("context"); + } + else + { + _writer.Write("ip{0}", i); + } + } + + _writer.WriteLine(").ConfigureAwait(false);"); + } + + public void WriteInterceptMethod( + int middlewareIndex, + (string FilePath, int LineNumber, int CharacterNumber) location) + { + _writer.WriteLine(); + + _writer.WriteIndentedLine( + "[InterceptsLocation(\"{0}\", {1}, {2})]", + location.FilePath, + location.LineNumber, + location.CharacterNumber); + _writer.WriteIndentedLine( + "public static global::{0} UseRequestGen{1}(", + RequestExecutorBuilder, + middlewareIndex); + + using (_writer.IncreaseIndent()) + { + _writer.WriteIndentedLine("this {0} builder) where TMiddleware : class", RequestExecutorBuilder); + } + + using (_writer.IncreaseIndent()) + { + _writer.WriteIndentedLine( + "=> builder.UseRequest(CreateMiddleware{2}());", + _moduleName, + "MiddlewareFactories", + middlewareIndex); + } + } + + public override string ToString() + => _sb.ToString(); + + public SourceText ToSourceText() + => SourceText.From(ToString(), Encoding.UTF8); + + public void Dispose() + { + if (_disposed) + { + return; + } + + StringBuilderPool.Return(_sb); + _sb = default!; + _writer = default!; + _disposed = true; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CodeWriterExtensions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CodeWriterExtensions.cs index 43ddf5be758..dfa0815ce57 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CodeWriterExtensions.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CodeWriterExtensions.cs @@ -33,6 +33,7 @@ public static void WriteFileHeader(this CodeWriter writer) writer.WriteIndentedLine("#nullable enable"); writer.WriteLine(); writer.WriteIndentedLine("using System;"); + writer.WriteIndentedLine("using System.Runtime.CompilerServices;"); writer.WriteIndentedLine("using HotChocolate.Execution.Configuration;"); } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj b/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj index eddb3bc294a..053c346554f 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj +++ b/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj @@ -7,6 +7,7 @@ enable false false + true diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs index d71c5c70ac8..60044dc4796 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs @@ -53,4 +53,4 @@ public override int GetHashCode() return (ModuleName.GetHashCode() * 397) ^ (int)Options; } } -} +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs new file mode 100644 index 00000000000..07d00ad6fd2 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs @@ -0,0 +1,107 @@ +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers.Inspectors; + +public sealed class RequestMiddlewareInfo( + string name, + string typeName, + string invokeMethodName, + (string, int, int) location, + List ctorParameters, + List invokeParameters) + : ISyntaxInfo, IEquatable +{ + public string Name { get; } = name; + + public string TypeName { get; } = typeName; + + public string InvokeMethodName { get; } = invokeMethodName; + + public (string FilePath, int LineNumber, int CharacterNumber) Location { get; } = location; + + public List CtorParameters { get; } = ctorParameters; + + public List InvokeParameters { get; } = invokeParameters; + + public bool Equals(RequestMiddlewareInfo? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if(Name == other.Name && + TypeName == other.TypeName && + InvokeMethodName == other.InvokeMethodName && + Location.Equals(other.Location)) + { + if (ReferenceEquals(CtorParameters, other.CtorParameters) && + ReferenceEquals(InvokeParameters, other.InvokeParameters)) + { + return true; + } + + if (CtorParameters.Count != other.CtorParameters.Count) + { + return false; + } + + for (var i = 0; i < CtorParameters.Count; i++) + { + if (!CtorParameters[i].Equals(other.CtorParameters[i])) + { + return false; + } + } + + if (InvokeParameters.Count != other.InvokeParameters.Count) + { + return false; + } + + for (var i = 0; i < InvokeParameters.Count; i++) + { + if (!InvokeParameters[i].Equals(other.InvokeParameters[i])) + { + return false; + } + } + } + + return false; + } + + public bool Equals(ISyntaxInfo obj) + => ReferenceEquals(this, obj) || obj is RequestMiddlewareInfo other && Equals(other); + + public override bool Equals(object? obj) + => ReferenceEquals(this, obj) || obj is RequestMiddlewareInfo other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + var hashCode = Name.GetHashCode(); + hashCode = (hashCode * 397) ^ TypeName.GetHashCode(); + hashCode = (hashCode * 397) ^ InvokeMethodName.GetHashCode(); + hashCode = (hashCode * 397) ^ Location.GetHashCode(); + + foreach (var p in CtorParameters) + { + hashCode = (hashCode * 397) ^ p.GetHashCode(); + } + + foreach (var p in InvokeParameters) + { + hashCode = (hashCode * 397) ^ p.GetHashCode(); + } + + return hashCode; + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs new file mode 100644 index 00000000000..8a0dcd6a26c --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs @@ -0,0 +1,172 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers.Inspectors; + +internal sealed class RequestMiddlewareInspector : ISyntaxInspector +{ + public bool TryHandle(GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name: GenericNameSyntax + { + Identifier.ValueText: "UseRequest", + TypeArgumentList: { Arguments.Count: 1, } args, + }, + }, + } node) + { + var semanticModel = context.SemanticModel; + var middlewareType = semanticModel.GetTypeInfo(args.Arguments[0]).Type; + + if (middlewareType is null) + { + syntaxInfo = default; + return false; + } + + var methods = middlewareType.GetMembers().OfType().ToArray(); + var ctor = methods.FirstOrDefault(t => t.Name.Equals(".ctor")); + var invokeMethod = methods.FirstOrDefault(t => t.Name.Equals("InvokeAsync") || t.Name.Equals("Invoke")); + + if (invokeMethod is not + { + Kind: SymbolKind.Method, + IsStatic: false, + IsAbstract: false, + DeclaredAccessibility: Accessibility.Public, + } || + ctor is not + { + Kind: SymbolKind.Method, + IsStatic: false, + IsAbstract: false, + DeclaredAccessibility: Accessibility.Public, + }) + { + syntaxInfo = default; + return false; + } + + var ctorParameters = new List(); + var invokeParameters = new List(); + + foreach (var parameter in ctor.Parameters) + { + RequestMiddlewareParameterKind kind; + var parameterTypeName = parameter.Type.ToFullyQualified(); + + if (parameterTypeName.Equals("global::HotChocolate.Schema") || + parameterTypeName.Equals("global::HotChocolate.!Schema")) + { + kind = RequestMiddlewareParameterKind.Schema; + } + else if (parameterTypeName.Equals("global::HotChocolate.Execution.RequestDelegate")) + { + kind = RequestMiddlewareParameterKind.Next; + } + else if (HasAttribute(parameter.GetAttributes(), "global::HotChocolate.SchemaServiceAttribute")) + { + kind = RequestMiddlewareParameterKind.SchemaService; + } + else + { + kind = RequestMiddlewareParameterKind.Service; + } + + ctorParameters.Add(new RequestMiddlewareParameterInfo(kind, parameterTypeName)); + } + + foreach (var parameter in invokeMethod.Parameters) + { + RequestMiddlewareParameterKind kind; + var parameterTypeName = parameter.Type.ToFullyQualified(); + + if (parameterTypeName.Equals("global::HotChocolate.Schema") || + parameterTypeName.Equals("global::HotChocolate.!Schema")) + { + kind = RequestMiddlewareParameterKind.Schema; + } + else if (parameterTypeName.Equals("global::HotChocolate.Execution.RequestDelegate")) + { + kind = RequestMiddlewareParameterKind.Next; + } + else if (parameterTypeName.Equals("global::HotChocolate.Execution.IRequestContext")) + { + kind = RequestMiddlewareParameterKind.Context; + } + else + { + kind = RequestMiddlewareParameterKind.Service; + } + + invokeParameters.Add(new RequestMiddlewareParameterInfo(kind, parameterTypeName)); + } + + + /* + public static IRequestExecutorBuilder UseRequest( + this IRequestExecutorBuilder builder) + where TMiddleware : class + + [InterceptsLocation(@"C:\testapp\Program.cs", line: 4, column: 5)] + public static RouteHandlerBuilder InterceptMapGet( // 👈 The interceptor must + this IEndpointRouteBuilder endpoints, // have the same signature + string pattern, // as the method being + Delegate handler) // intercepted + { + Console.WriteLine($"Intercepted '{pattern}'" ); + + return endpoints.MapGet(pattern, handler); + } + */ + + + syntaxInfo = new RequestMiddlewareInfo( + middlewareType.Name, + middlewareType.ToFullyQualified(), + invokeMethod.Name, + GetLocation((MemberAccessExpressionSyntax)node.Expression, semanticModel), + ctorParameters, + invokeParameters); + return true; + } + + syntaxInfo = default; + return false; + } + + private static bool HasAttribute(ImmutableArray attributes, string attributeTypeName) + { + foreach (var attribute in attributes) + { + if (attribute.AttributeClass?.ToFullyQualified().Equals(attributeTypeName) ?? false) + { + return true; + } + } + + return false; + } + + private static (string, int, int) GetLocation( + MemberAccessExpressionSyntax memberAccessorExpression, + SemanticModel semanticModel) + { + var invocationNameSpan = memberAccessorExpression.Name.Span; + var lineSpan = memberAccessorExpression.SyntaxTree.GetLineSpan(invocationNameSpan); + var filePath = GetInterceptorFilePath( + memberAccessorExpression.SyntaxTree, + semanticModel.Compilation.Options.SourceReferenceResolver); + return (filePath, lineSpan.StartLinePosition.Line + 1, lineSpan.StartLinePosition.Character + 1); + } + + private static string GetInterceptorFilePath(SyntaxTree tree, SourceReferenceResolver? resolver) => + resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath; +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs new file mode 100644 index 00000000000..69015d99ede --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs @@ -0,0 +1,45 @@ +namespace HotChocolate.Types.Analyzers.Inspectors; + +public sealed class RequestMiddlewareParameterInfo( + RequestMiddlewareParameterKind kind, + string? typeName, + bool isNullable = false) : IEquatable +{ + public RequestMiddlewareParameterKind Kind { get; } = kind; + + public string? TypeName { get; } = typeName; + + public bool IsNullable { get; } = isNullable; + + public bool Equals(RequestMiddlewareParameterInfo? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Kind == other.Kind && TypeName == other.TypeName && IsNullable == other.IsNullable; + } + + public override bool Equals(object? obj) + => ReferenceEquals(this, obj) || obj is RequestMiddlewareParameterInfo other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + var hashCode = (int)Kind; + hashCode = (hashCode * 397) ^ + (TypeName != null + ? TypeName.GetHashCode() + : 0); + hashCode = (hashCode * 397) ^ IsNullable.GetHashCode(); + return hashCode; + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs new file mode 100644 index 00000000000..8115652716c --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs @@ -0,0 +1,10 @@ +namespace HotChocolate.Types.Analyzers.Inspectors; + +public enum RequestMiddlewareParameterKind +{ + Service, + SchemaService, + Schema, + Context, + Next, +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/MiddlewareModuleGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/MiddlewareModuleGenerator.cs new file mode 100644 index 00000000000..cdb51516fc7 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/MiddlewareModuleGenerator.cs @@ -0,0 +1,115 @@ +using System.Collections.Immutable; +using HotChocolate.Types.Analyzers.Generators; +using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Inspectors; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers; + +[Generator] +public class MiddlewareModuleGenerator : IIncrementalGenerator +{ + private const string _namespace = "HotChocolate.Execution.Generated"; + + private static readonly ISyntaxInspector[] _inspectors = + [ + new TypeAttributeInspector(), + new ClassBaseClassInspector(), + new ModuleInspector(), + new DataLoaderInspector(), + new DataLoaderDefaultsInspector(), + new OperationInspector(), + new RequestMiddlewareInspector(), + ]; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var modulesAndTypes = + context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsRelevant(s), + transform: TryGetModuleOrType) + .Where(static t => t is not null)! + .WithComparer(SyntaxInfoComparer.Default); + + var valueProvider = context.CompilationProvider.Combine(modulesAndTypes.Collect()); + + context.RegisterSourceOutput( + valueProvider, + static (context, source) => Execute(context, source.Left, source.Right)); + } + + private static bool IsRelevant(SyntaxNode node) + => IsMiddlewareMethod(node); + + private static bool IsMiddlewareMethod(SyntaxNode node) + => node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name.Identifier.ValueText: var method, + }, + } && + (method.Equals("UseRequest") || method.Equals("UseField") || method.Equals("Use")); + + private static ISyntaxInfo? TryGetModuleOrType( + GeneratorSyntaxContext context, + CancellationToken cancellationToken) + { + for (var i = 0; i < _inspectors.Length; i++) + { + if (_inspectors[i].TryHandle(context, out var syntaxInfo)) + { + return syntaxInfo; + } + } + + return null; + } + + private static void Execute( + SourceProductionContext context, + Compilation compilation, + ImmutableArray syntaxInfos) + { + if (syntaxInfos.IsEmpty) + { + return; + } + + var module = syntaxInfos.GetModuleInfo(compilation.AssemblyName, out var defaultModule); + + // if there is only the module info we do not need to generate a module. + if (!defaultModule && syntaxInfos.Length == 1) + { + return; + } + + using var generator = new RequestMiddlewareSyntaxGenerator(module.ModuleName, _namespace); + + generator.WriterHeader(); + generator.WriteBeginNamespace(); + + generator.WriteBeginClass(); + + var i = 0; + foreach (var syntaxInfo in syntaxInfos) + { + if (syntaxInfo is not RequestMiddlewareInfo middleware) + { + continue; + } + + generator.WriteFactory(i, middleware); + generator.WriteInterceptMethod(i, middleware.Location); + i++; + } + + generator.WriteEndClass(); + + generator.WriteEndNamespace(); + + context.AddSource(WellKnownFileNames.MiddlewareFile, generator.ToSourceText()); + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Properties/launchSettings.json b/src/HotChocolate/Core/src/Types.Analyzers/Properties/launchSettings.json new file mode 100644 index 00000000000..730b2143384 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Generators": { + "commandName": "DebugRoslynComponent", + "targetProject": "../../test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj" + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs index 821bafedb7e..f98e6b39942 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs @@ -236,7 +236,7 @@ private static void WriteDataLoader( dataLoaders.Add(dataLoader); } - var generator = new DataLoaderSyntaxGenerator(); + using var generator = new DataLoaderSyntaxGenerator(); generator.WriterHeader(); foreach (var group in dataLoaders.GroupBy(t => t.Namespace)) @@ -316,7 +316,7 @@ private static void WriteOperationTypes( return; } - var generator = new OperationFieldSyntaxGenerator(); + using var generator = new OperationFieldSyntaxGenerator(); generator.WriterHeader(); generator.WriteBeginNamespace("Microsoft.Extensions.DependencyInjection"); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs index 33d8b89a118..95c678d9cd2 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs @@ -12,9 +12,9 @@ public static class WellKnownAttributes public const string MutationTypeAttribute = "HotChocolate.Types.MutationTypeAttribute"; public const string SubscriptionTypeAttribute = "HotChocolate.Types.SubscriptionTypeAttribute"; public const string DataLoaderAttribute = "HotChocolate.DataLoaderAttribute"; - public const string QueryAttribute = "HotChocolate.QueryFieldAttribute"; - public const string MutationAttribute = "HotChocolate.MutationFieldAttribute"; - public const string SubscriptionAttribute = "HotChocolate.SubscriptionFieldAttribute"; + public const string QueryAttribute = "HotChocolate.QueryAttribute"; + public const string MutationAttribute = "HotChocolate.MutationAttribute"; + public const string SubscriptionAttribute = "HotChocolate.SubscriptionAttribute"; public static HashSet TypeAttributes { get; } = [ diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs index 38fd2bb9d12..c54b4591781 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs @@ -6,4 +6,5 @@ public static class WellKnownFileNames public const string DataLoaderFile = "HotChocolateDataLoader.g.cs"; public const string AttributesFile = "HotChocolateAttributes.g.cs"; public const string RootTypesFile = "HotChocolateRootTypes.g.cs"; + public const string MiddlewareFile = "HotChocolateMiddleware.g.cs"; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs index 7175878d12e..173d0029b3d 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs @@ -23,6 +23,9 @@ public static class WellKnownTypes public const string ReadOnlyDictionary = "System.Collections.Generic.IReadOnlyDictionary"; public const string Lookup = "System.Linq.ILookup"; public const string Task = "System.Threading.Tasks.Task"; + public const string RequestCoreMiddleware = $"HotChocolate.Execution.{nameof(RequestCoreMiddleware)}"; + public const string Schema = $"HotChocolate.{nameof(Schema)}"; + public const string RequestExecutorBuilder = "HotChocolate.Execution.Configuration.IRequestExecutorBuilder"; public static HashSet TypeClass { get; } = [ diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentCacheMiddlewareTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentCacheMiddlewareTests.cs index 0726c36053d..6c7867ced76 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentCacheMiddlewareTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentCacheMiddlewareTests.cs @@ -13,7 +13,7 @@ public async Task RetrieveItemFromCache_DocumentFoundOnCache() var cache = new Caching.DefaultDocumentCache(); var hashProvider = new MD5DocumentHashProvider(); - var middleware = new DocumentCacheMiddleware( + var middleware = DocumentCacheMiddleware.Create( _ => default, new NoopExecutionDiagnosticEvents(), cache, @@ -51,8 +51,8 @@ public async Task RetrieveItemFromCacheByHash_DocumentFoundOnCache() var cache = new Caching.DefaultDocumentCache(); var hashProvider = new MD5DocumentHashProvider(); - var middleware = new DocumentCacheMiddleware( - context => default, + var middleware = DocumentCacheMiddleware.Create( + _ => default, new NoopExecutionDiagnosticEvents(), cache, hashProvider); @@ -89,8 +89,8 @@ public async Task RetrieveItemFromCache_DocumentNotFoundOnCache() var cache = new Caching.DefaultDocumentCache(); var hashProvider = new MD5DocumentHashProvider(); - var middleware = new DocumentCacheMiddleware( - context => default, + var middleware = DocumentCacheMiddleware.Create( + _ => default, new NoopExecutionDiagnosticEvents(), cache, hashProvider); @@ -135,7 +135,7 @@ public async Task AddItemToCacheWithDocumentId() var document = Utf8GraphQLParser.Parse("{ a }"); - var middleware = new DocumentCacheMiddleware( + var middleware = DocumentCacheMiddleware.Create( context => { context.Document = document; @@ -173,19 +173,14 @@ public async Task AddItemToCacheWithDocumentHash() .SetQuery("{ a }") .Create(); - var document = Utf8GraphQLParser.Parse("{ a }"); - - var parserMiddleware = new DocumentParserMiddleware( - context => default, + var parserMiddleware = DocumentParserMiddleware.Create( + _ => default, new NoopExecutionDiagnosticEvents(), hashProvider, new ParserOptions()); - var middleware = new DocumentCacheMiddleware( - context => - { - return parserMiddleware.InvokeAsync(context); - }, + var middleware = DocumentCacheMiddleware.Create( + context => parserMiddleware.InvokeAsync(context), new NoopExecutionDiagnosticEvents(), cache, hashProvider); diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentParserMiddlewareTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentParserMiddlewareTests.cs index aae1bfab8c1..164dba78694 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentParserMiddlewareTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentParserMiddlewareTests.cs @@ -12,11 +12,10 @@ public class DocumentParserMiddlewareTests public async Task DocumentExists_SkipParsing_DocumentIsUnchanged() { // arrange - var cache = new Caching.DefaultDocumentCache(); var hashProvider = new MD5DocumentHashProvider(); - var middleware = new DocumentParserMiddleware( - context => default, + var middleware = DocumentParserMiddleware.Create( + _ => default, new NoopExecutionDiagnosticEvents(), hashProvider, new ParserOptions()); @@ -43,11 +42,10 @@ public async Task DocumentExists_SkipParsing_DocumentIsUnchanged() public async Task NoDocument_ParseQuery_DocumentParsedAndHashed() { // arrange - var cache = new Caching.DefaultDocumentCache(); var hashProvider = new MD5DocumentHashProvider(); - var middleware = new DocumentParserMiddleware( - context => default, + var middleware = DocumentParserMiddleware.Create( + _ => default, new NoopExecutionDiagnosticEvents(), hashProvider, new ParserOptions()); @@ -74,11 +72,10 @@ public async Task NoDocument_ParseQuery_DocumentParsedAndHashed() public async Task InvalidQuery_SyntaxError_ContextHasErrorResult() { // arrange - var cache = new Caching.DefaultDocumentCache(); var hashProvider = new MD5DocumentHashProvider(); - var middleware = new DocumentParserMiddleware( - context => throw new Exception("Should not be invoked."), + var middleware = DocumentParserMiddleware.Create( + _ => throw new Exception("Should not be invoked."), new NoopExecutionDiagnosticEvents(), hashProvider, new ParserOptions()); diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentValidationMiddlewareTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentValidationMiddlewareTests.cs index 56839fddce5..f92f727b604 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentValidationMiddlewareTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentValidationMiddlewareTests.cs @@ -22,7 +22,7 @@ public async Task DocumentIsValidated_SkipValidation() It.IsAny())) .Returns(new ValueTask(DocumentValidatorResult.Ok)); - var middleware = new DocumentValidationMiddleware( + var middleware = DocumentValidationMiddleware.Create( _ => default, new NoopExecutionDiagnosticEvents(), validator.Object); @@ -64,7 +64,7 @@ public async Task DocumentIsValidated_Dynamic() It.IsAny())) .Returns(new ValueTask(DocumentValidatorResult.Ok)); - var middleware = new DocumentValidationMiddleware( + var middleware = DocumentValidationMiddleware.Create( _ => default, new NoopExecutionDiagnosticEvents(), validator.Object); @@ -107,7 +107,7 @@ public async Task DocumentNeedsValidation_DocumentIsValid() It.IsAny())) .Returns(new ValueTask(DocumentValidatorResult.Ok)); - var middleware = new DocumentValidationMiddleware( + var middleware = DocumentValidationMiddleware.Create( _ => default, new NoopExecutionDiagnosticEvents(), validator.Object); @@ -151,7 +151,7 @@ public async Task DocumentNeedsValidation_DocumentInvalid() It.IsAny())) .Returns(new ValueTask(validationResult)); - var middleware = new DocumentValidationMiddleware( + var middleware = DocumentValidationMiddleware.Create( _ => throw new Exception("Should not be called."), new NoopExecutionDiagnosticEvents(), validator.Object); @@ -194,7 +194,7 @@ public async Task NoDocument_MiddlewareWillFail() It.IsAny())) .Returns(new ValueTask(DocumentValidatorResult.Ok)); - var middleware = new DocumentValidationMiddleware( + var middleware = DocumentValidationMiddleware.Create( _ => throw new Exception("Should not be called."), new NoopExecutionDiagnosticEvents(), validator.Object); diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/ExceptionMiddlewareTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/ExceptionMiddlewareTests.cs index caa9d49f613..d89452f7a56 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/ExceptionMiddlewareTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/ExceptionMiddlewareTests.cs @@ -15,15 +15,10 @@ public async Task Unexpected_Error() Array.Empty(), new RequestExecutorOptions()); - var middleware = new ExceptionMiddleware( - context => throw new Exception("Something is wrong."), + var middleware = ExceptionMiddleware.Create( + _ => throw new Exception("Something is wrong."), errorHandler); - var request = QueryRequestBuilder.New() - .SetQuery("{ a }") - .SetQueryId("a") - .Create(); - var requestContext = new Mock(); requestContext.SetupProperty(t => t.Result); @@ -31,7 +26,7 @@ public async Task Unexpected_Error() await middleware.InvokeAsync(requestContext.Object); // assert - requestContext.Object.Result.ToJson().MatchSnapshot(); + requestContext.Object.Result!.ToJson().MatchSnapshot(); } [Fact] @@ -42,15 +37,10 @@ public async Task GraphQL_Error() Array.Empty(), new RequestExecutorOptions()); - var middleware = new ExceptionMiddleware( - context => throw new GraphQLException("Something is wrong."), + var middleware = ExceptionMiddleware.Create( + _ => throw new GraphQLException("Something is wrong."), errorHandler); - - var request = QueryRequestBuilder.New() - .SetQuery("{ a }") - .SetQueryId("a") - .Create(); - + var requestContext = new Mock(); requestContext.SetupProperty(t => t.Result); @@ -58,6 +48,6 @@ public async Task GraphQL_Error() await middleware.InvokeAsync(requestContext.Object); // assert - requestContext.Object.Result.ToJson().MatchSnapshot(); + requestContext.Object.Result!.ToJson().MatchSnapshot(); } } \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/AnnotationBasedSchemaTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/AnnotationBasedSchemaTests.cs index eb26a042d10..08d90fb91bc 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/AnnotationBasedSchemaTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/AnnotationBasedSchemaTests.cs @@ -2,6 +2,7 @@ using HotChocolate.Execution; using Microsoft.Extensions.DependencyInjection; using CookieCrumble; +using HotChocolate.Execution.Configuration; namespace HotChocolate.Types; @@ -22,11 +23,25 @@ public async Task SchemaSnapshot() [Fact] public async Task ExecuteRootField() { - var result = - await new ServiceCollection() - .AddGraphQL() - .AddCustomModule() - .ExecuteRequestAsync("{ foo }"); + var services = new ServiceCollection() + .AddGraphQL() + .AddCustomModule(); + + var result = await services.ExecuteRequestAsync("{ foo }"); + + result.MatchSnapshot(); + } + + [Fact] + public async Task ExecuteWithMiddleware() + { + var services = new ServiceCollection() + .AddGraphQL() + .AddCustomModule() + .UseRequest() + .UseDefaultPipeline(); + + var result = await services.ExecuteRequestAsync("{ foo }"); result.MatchSnapshot(); } diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj b/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj index 615559c6f7d..2a8ac1c418c 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj @@ -1,10 +1,11 @@ - net6.0 + net8.0 enable false + $(InterceptorsPreviewNamespaces);HotChocolate.Execution.Generated diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/RootTypeTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/RootTypeTests.cs index cdb0d452d00..26073f61e28 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/RootTypeTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/RootTypeTests.cs @@ -2,9 +2,9 @@ namespace HotChocolate.Types; public static class RootTypeTests { - [QueryField] + [Query] public static string Foo() => "foo"; - [MutationField] + [Mutation] public static string Bar() => "bar"; } \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeRequestMiddleware.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeRequestMiddleware.cs new file mode 100644 index 00000000000..dbd61331002 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeRequestMiddleware.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using HotChocolate.Execution; + +namespace HotChocolate.Types; + +public class SomeRequestMiddleware(RequestDelegate next) +{ + public async ValueTask InvokeAsync(IRequestContext context) + { + await next(context); + + context.Result = + QueryResultBuilder.New() + .SetData(new Dictionary { { "hello", true } }) + .Create(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/StaticQueryExtension.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/StaticQueryExtension.cs index 9ce0809d7a4..86ef83ea43a 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/StaticQueryExtension.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/StaticQueryExtension.cs @@ -6,4 +6,4 @@ namespace HotChocolate.Types; public static class StaticQueryExtension { public static string StaticField() => "foo"; -} +} \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/SchemaTests.ExecuteWithMiddleware.snap b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/SchemaTests.ExecuteWithMiddleware.snap new file mode 100644 index 00000000000..e5a843e16a3 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/SchemaTests.ExecuteWithMiddleware.snap @@ -0,0 +1,5 @@ +{ + "data": { + "hello": true + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/foo.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/foo.cs new file mode 100644 index 00000000000..988216bdf37 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/foo.cs @@ -0,0 +1,7 @@ +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + sealed class InterceptsLocationAttribute(string filePath, int line, int column) : Attribute + { + } +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs index cf07a99baca..baf82d2c72d 100644 --- a/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs +++ b/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs @@ -364,17 +364,17 @@ public static IRequestExecutorBuilder UseDistributedOperationExecution( internal static void AddDefaultPipeline(this IList pipeline) { - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); - pipeline.Add(RequestClassMiddlewareFactory.Create()); + pipeline.Add(InstrumentationMiddleware.Create()); + pipeline.Add(ExceptionMiddleware.Create()); + pipeline.Add(TimeoutMiddleware.Create()); + pipeline.Add(DocumentCacheMiddleware.Create()); + pipeline.Add(DocumentParserMiddleware.Create()); + pipeline.Add(DocumentValidationMiddleware.Create()); + pipeline.Add(OperationCacheMiddleware.Create()); + pipeline.Add(OperationComplexityMiddleware.Create()); + pipeline.Add(OperationResolverMiddleware.Create()); + pipeline.Add(OperationVariableCoercionMiddleware.Create()); + pipeline.Add(DistributedOperationExecutionMiddleware.Create()); } private static GraphQLClientFactory CreateGraphQLClientFactory( diff --git a/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs b/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs index a78c0cafc22..8d704a9d5df 100644 --- a/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs @@ -1,5 +1,7 @@ using HotChocolate.Execution; +using HotChocolate.Execution.Caching; using HotChocolate.Execution.DependencyInjection; +using HotChocolate.Execution.Instrumentation; using HotChocolate.Execution.Processing; using HotChocolate.Fetching; using HotChocolate.Fusion.Clients; @@ -8,6 +10,7 @@ using HotChocolate.Fusion.Utilities; using HotChocolate.Language; using HotChocolate.Types.Relay; +using Microsoft.Extensions.DependencyInjection; using ErrorHelper = HotChocolate.Execution.ErrorHelper; namespace HotChocolate.Fusion.Execution.Pipeline; @@ -92,4 +95,26 @@ private static object GetRootObject(IOperation operation) OperationType.Subscription => _subscriptionRoot, _ => throw new NotSupportedException(), }; + + public static RequestCoreMiddleware Create() + => (core, next) => + { + var contextFactory = core.Services.GetRequiredService>(); + var idSerializer = core.Services.GetRequiredService(); + var serviceConfig = core.SchemaServices.GetRequiredService(); + var clientFactory = core.SchemaServices.GetRequiredService(); + var nodeIdParser = core.SchemaServices.GetRequiredService(); + var middleware = new DistributedOperationExecutionMiddleware( + next, + contextFactory, + idSerializer, + serviceConfig, + clientFactory, + nodeIdParser); + return async context => + { + var batchDispatcher = context.Services.GetRequiredService(); + await middleware.InvokeAsync(context, batchDispatcher); + }; + }; } diff --git a/src/HotChocolate/Utilities/src/Utilities/Cache.cs b/src/HotChocolate/Utilities/src/Utilities/Cache.cs index 8e48c36eb0e..ae564240e8b 100644 --- a/src/HotChocolate/Utilities/src/Utilities/Cache.cs +++ b/src/HotChocolate/Utilities/src/Utilities/Cache.cs @@ -5,22 +5,16 @@ namespace HotChocolate.Utilities; #pragma warning disable CA1724 -public sealed class Cache +public sealed class Cache(int size) { private const int _minimumSize = 10; private readonly object _sync = new(); private readonly ConcurrentDictionary _map = new(StringComparer.Ordinal); - private readonly int _capacity; - private readonly int _order; + private readonly int _capacity = size < _minimumSize ? _minimumSize : size; + private readonly int _order = Convert.ToInt32(size * 0.7); private int _usage; private Entry? _head; - public Cache(int size) - { - _capacity = size < _minimumSize ? _minimumSize : size; - _order = Convert.ToInt32(size * 0.7); - } - /// /// Gets the maximum allowed item count that can be stored in this cache. /// diff --git a/src/HotChocolate/Utilities/src/Utilities/CacheEntryEventArgs.cs b/src/HotChocolate/Utilities/src/Utilities/CacheEntryEventArgs.cs index 45babd1fd69..65d68876c77 100644 --- a/src/HotChocolate/Utilities/src/Utilities/CacheEntryEventArgs.cs +++ b/src/HotChocolate/Utilities/src/Utilities/CacheEntryEventArgs.cs @@ -5,8 +5,7 @@ namespace HotChocolate.Utilities; /// /// Represents cache entry event args. /// -public sealed class CacheEntryEventArgs - : EventArgs +public sealed class CacheEntryEventArgs : EventArgs { /// /// Initializes a new instance of the diff --git a/src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate.cs b/src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate.cs deleted file mode 100644 index 2e1f46d7527..00000000000 --- a/src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System; - -namespace HotChocolate.Utilities; - -public delegate object? CreateServiceDelegate(IServiceProvider services); diff --git a/src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate~1.cs b/src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate~1.cs deleted file mode 100644 index 374d094c01e..00000000000 --- a/src/HotChocolate/Utilities/src/Utilities/CreateServiceDelegate~1.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System; - -namespace HotChocolate.Utilities; - -public delegate T CreateServiceDelegate(IServiceProvider services); diff --git a/src/HotChocolate/Utilities/src/Utilities/MiddlewareCompiler.cs b/src/HotChocolate/Utilities/src/Utilities/MiddlewareCompiler.cs index 66c54899f23..6bf84656d74 100644 --- a/src/HotChocolate/Utilities/src/Utilities/MiddlewareCompiler.cs +++ b/src/HotChocolate/Utilities/src/Utilities/MiddlewareCompiler.cs @@ -50,7 +50,7 @@ internal static MiddlewareFactory CompileFactory(); handlers.Add(new TypeParameterHandler(typeof(TNext), next)); - if (createParameters is { }) + if (createParameters is not null) { handlers.AddRange(createParameters(context, next)); }