diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/AllowedContentType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/AllowedContentType.cs similarity index 63% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/AllowedContentType.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/AllowedContentType.cs index 5c8aef6a200..83db03809c3 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/AllowedContentType.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/AllowedContentType.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { public enum AllowedContentType { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ContentType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs similarity index 88% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ContentType.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs index 119c451deb0..8195baa7226 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ContentType.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs @@ -1,13 +1,11 @@ using System; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { - public static class ContentType + internal static class ContentType { public const string GraphQL = "application/graphql; charset=utf-8"; - public const string Json = "application/json; charset=utf-8"; - public const string MultiPart = "multipart/mixed; boundary=\"-\""; public static ReadOnlySpan JsonSpan() => new char[] diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs similarity index 87% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpRequestInterceptor.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs index e97996a5f87..d6a9b843c46 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpRequestInterceptor.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultHttpRequestInterceptor.cs @@ -1,10 +1,10 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using HotChocolate.Execution; using Microsoft.AspNetCore.Http; +using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { public class DefaultHttpRequestInterceptor : IHttpRequestInterceptor { @@ -21,7 +21,7 @@ public class DefaultHttpRequestInterceptor : IHttpRequestInterceptor if (context.IsTracingEnabled()) { - requestBuilder.TryAddProperty(ContextDataKeys.EnableTracing, true); + requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true); } return default; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultSocketSessionInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs similarity index 92% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultSocketSessionInterceptor.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs index 9bbd4e6bfd5..2ca33fbebf5 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultSocketSessionInterceptor.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/DefaultSocketSessionInterceptor.cs @@ -6,7 +6,7 @@ using HotChocolate.AspNetCore.Subscriptions.Messages; using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { public class DefaultSocketSessionInterceptor : ISocketSessionInterceptor { @@ -29,7 +29,7 @@ public class DefaultSocketSessionInterceptor : ISocketSessionInterceptor if (connection.HttpContext.IsTracingEnabled()) { - requestBuilder.TryAddProperty(ContextDataKeys.EnableTracing, true); + requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true); } return default; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DelegateHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/DelegateHttpRequestInterceptor.cs similarity index 95% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DelegateHttpRequestInterceptor.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/DelegateHttpRequestInterceptor.cs index 165a729d097..ff6a6615d34 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DelegateHttpRequestInterceptor.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/DelegateHttpRequestInterceptor.cs @@ -1,10 +1,10 @@ using System; using System.Threading; using System.Threading.Tasks; -using HotChocolate.Execution; using Microsoft.AspNetCore.Http; +using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { internal sealed class DelegateHttpRequestInterceptor : DefaultHttpRequestInterceptor { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ErrorHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs similarity index 94% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ErrorHelper.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs index 47aad335176..63d1fdd9174 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ErrorHelper.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs @@ -1,6 +1,6 @@ using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { internal static class ErrorHelper { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLEndpointConventionBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLEndpointConventionBuilder.cs new file mode 100644 index 00000000000..18dbce0bde6 --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/GraphQLEndpointConventionBuilder.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Builder; + +namespace HotChocolate.AspNetCore.Extensions +{ + /// + /// Represents the endpoint convention builder for GraphQL. + /// + public sealed class GraphQLEndpointConventionBuilder : IEndpointConventionBuilder + { + private readonly IEndpointConventionBuilder _builder; + + internal GraphQLEndpointConventionBuilder(IEndpointConventionBuilder builder) + { + _builder = builder; + } + + /// + public void Add(Action convention) => + _builder.Add(convention); + } +} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs index b7acbb1f30b..9837262686b 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs @@ -1,12 +1,25 @@ using System; -using HotChocolate.AspNetCore.Utilities; -using HotChocolate.Execution.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; +using HotChocolate.AspNetCore; +using HotChocolate.AspNetCore.Serialization; +using HotChocolate.Execution.Configuration; namespace Microsoft.Extensions.DependencyInjection { public static partial class HotChocolateAspNetCoreServiceCollectionExtensions { + /// + /// Adds an interceptor for GraphQL requests to the GraphQL configuration. + /// + /// + /// The . + /// + /// + /// The implementation. + /// + /// + /// Returns the so that configuration can be chained. + /// public static IRequestExecutorBuilder AddHttpRequestInterceptor( this IRequestExecutorBuilder builder) where T : class, IHttpRequestInterceptor => @@ -14,6 +27,21 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions .RemoveAll() .AddSingleton()); + /// + /// Adds an interceptor for GraphQL requests to the GraphQL configuration. + /// + /// + /// The . + /// + /// + /// A factory that creates the interceptor instance. + /// + /// + /// The implementation. + /// + /// + /// Returns the so that configuration can be chained. + /// public static IRequestExecutorBuilder AddHttpRequestInterceptor( this IRequestExecutorBuilder builder, Func factory) @@ -22,21 +50,48 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions .RemoveAll() .AddSingleton(factory)); + /// + /// Adds an interceptor for GraphQL requests to the GraphQL configuration. + /// + /// + /// The . + /// + /// + /// The interceptor instance that shall be added to the configuration. + /// + /// + /// Returns the so that configuration can be chained. + /// public static IRequestExecutorBuilder AddHttpRequestInterceptor( this IRequestExecutorBuilder builder, HttpRequestInterceptorDelegate interceptor) => AddHttpRequestInterceptor( builder, - sp => new DelegateHttpRequestInterceptor(interceptor)); + _ => new DelegateHttpRequestInterceptor(interceptor)); - private static IRequestExecutorBuilder AddHttpRequestInterceptor( + private static IRequestExecutorBuilder AddDefaultHttpRequestInterceptor( this IRequestExecutorBuilder builder) { - return builder.ConfigureSchemaServices(s => - s.TryAddSingleton()); + return builder.ConfigureSchemaServices( + s => s.TryAddSingleton()); } - public static IServiceCollection AddHttpRequestSerializer( + /// + /// Adds the with specific serialization settings + /// to the DI. + /// + /// + /// The . + /// + /// + /// Specifies the batch serialization format. + /// + /// + /// Specifies the defer/stream serialization format. + /// + /// Returns the so that configuration can be chained. + /// + public static IServiceCollection AddHttpResultSerializer( this IServiceCollection services, HttpResultSerialization batchSerialization = HttpResultSerialization.MultiPartChunked, HttpResultSerialization deferSerialization = HttpResultSerialization.MultiPartChunked) @@ -49,7 +104,19 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions return services; } - public static IServiceCollection AddHttpRequestSerializer( + /// + /// Adds a custom http request serializer to the DI. + /// + /// + /// The . + /// + /// + /// The type of the custom . + /// + /// + /// Returns the so that configuration can be chained. + /// + public static IServiceCollection AddHttpResultSerializer( this IServiceCollection services) where T : class, IHttpResultSerializer { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs index 836bddaf026..1d585409227 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs @@ -1,14 +1,26 @@ using System; +using Microsoft.Extensions.DependencyInjection.Extensions; +using HotChocolate.AspNetCore; using HotChocolate.AspNetCore.Subscriptions; using HotChocolate.AspNetCore.Subscriptions.Messages; -using HotChocolate.AspNetCore.Utilities; using HotChocolate.Execution.Configuration; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.Extensions.DependencyInjection { public static partial class HotChocolateAspNetCoreServiceCollectionExtensions { + /// + /// Adds an interceptor for GraphQL socket sessions to the GraphQL configuration. + /// + /// + /// The . + /// + /// + /// The implementation. + /// + /// + /// Returns the so that configuration can be chained. + /// public static IRequestExecutorBuilder AddSocketSessionInterceptor( this IRequestExecutorBuilder builder) where T : class, ISocketSessionInterceptor => @@ -16,7 +28,21 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions .RemoveAll() .AddSingleton()); - + /// + /// Adds an interceptor for GraphQL socket sessions to the GraphQL configuration. + /// + /// + /// The . + /// + /// + /// A factory that creates the interceptor instance. + /// + /// + /// The implementation. + /// + /// + /// Returns the so that configuration can be chained. + /// public static IRequestExecutorBuilder AddSocketSessionInterceptor( this IRequestExecutorBuilder builder, Func factory) diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs index 2dde0cfb591..1dd496ba02c 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Warmup.cs @@ -1,11 +1,23 @@ using System; -using HotChocolate.AspNetCore.Utilities; +using HotChocolate.AspNetCore.Warmup; using HotChocolate.Execution.Configuration; namespace Microsoft.Extensions.DependencyInjection { public static partial class HotChocolateAspNetCoreServiceCollectionExtensions { + /// + /// Adds the current GraphQL configuration to the warmup background service. + /// + /// + /// The . + /// + /// + /// Returns the so that configuration can be chained. + /// + /// + /// The is null. + /// public static IRequestExecutorBuilder InitializeOnStartup( this IRequestExecutorBuilder builder) { @@ -14,7 +26,7 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions throw new ArgumentNullException(nameof(builder)); } - builder.Services.AddHostedService(); + builder.Services.AddHostedService(); builder.Services.AddSingleton(new WarmupSchema(builder.Name)); return builder; } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs index 6da5f9d3bcb..d2b460d041f 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs @@ -1,15 +1,32 @@ using System; using Microsoft.Extensions.DependencyInjection.Extensions; using HotChocolate; -using HotChocolate.AspNetCore; -using HotChocolate.AspNetCore.Utilities; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.Execution.Configuration; using HotChocolate.Language; namespace Microsoft.Extensions.DependencyInjection { + /// + /// Provides DI extension methods to configure a GraphQL server. + /// public static partial class HotChocolateAspNetCoreServiceCollectionExtensions { + /// + /// Adds the GraphQL server core services to the DI. + /// + /// + /// The . + /// + /// + /// The max allowed GraphQL request size. + /// + /// + /// Returns the so that configuration can be chained. + /// + /// + /// The is null. + /// public static IServiceCollection AddGraphQLServerCore( this IServiceCollection services, int maxAllowedRequestSize = 20 * 1000 * 1000) @@ -31,6 +48,21 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions return services; } + /// + /// Adds a GraphQL server configuration to the DI. + /// + /// + /// The . + /// + /// + /// The name of the schema. Use explicit schema names if you host multiple schemas. + /// + /// + /// The max allowed GraphQL request size. + /// + /// + /// Returns the so that configuration can be chained. + /// public static IRequestExecutorBuilder AddGraphQLServer( this IServiceCollection services, NameString schemaName = default, @@ -38,7 +70,7 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions services .AddGraphQLServerCore(maxAllowedRequestSize) .AddGraphQL(schemaName) - .AddHttpRequestInterceptor() + .AddDefaultHttpRequestInterceptor() .AddSubscriptionServices(); public static IRequestExecutorBuilder AddGraphQLServer( @@ -57,7 +89,7 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions services .AddGraphQLServerCore(maxAllowedRequestSize) .AddGraphQL() - .AddHttpRequestInterceptor() + .AddDefaultHttpRequestInterceptor() .AddSubscriptionServices(), schema) .Services; @@ -73,7 +105,7 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions services .AddGraphQLServerCore(maxAllowedRequestSize) .AddGraphQL() - .AddHttpRequestInterceptor() + .AddDefaultHttpRequestInterceptor() .AddSubscriptionServices(), schemaFactory) .Services; @@ -89,7 +121,7 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions services .AddGraphQLServerCore(maxAllowedRequestSize) .AddGraphQL() - .AddHttpRequestInterceptor() + .AddDefaultHttpRequestInterceptor() .AddSubscriptionServices(), schemaBuilder) .Services; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpContextExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs similarity index 50% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpContextExtensions.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs index 0d8eaf7679b..a2d1d288492 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpContextExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpContextExtensions.cs @@ -1,18 +1,23 @@ using System.Linq; -using HotChocolate.AspNetCore.Subscriptions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { - public static class HttpContextExtensions + internal static class HttpContextExtensions { + public static GraphQLServerOptions? GetGraphQLServerOptions(this HttpContext context) => + context.GetEndpoint()?.Metadata.GetMetadata(); + + public static GraphQLToolOptions? GetGraphQLToolOptions(this HttpContext context) => + GetGraphQLServerOptions(context)?.Tool; + public static bool IsTracingEnabled(this HttpContext context) { IHeaderDictionary headers = context.Request.Headers; if ((headers.TryGetValue(HttpHeaderKeys.Tracing, out StringValues values) - || headers.TryGetValue(HttpHeaderKeys.ApolloTracing, out values)) + || headers.TryGetValue(HttpHeaderKeys.ApolloTracing, out values)) && values.Any(v => v == HttpHeaderValues.TracingEnabled)) { return true; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpEndpointRouteBuilderExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpEndpointRouteBuilderExtensions.cs index cc25e580b3a..ce6cee23ef1 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpEndpointRouteBuilderExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpEndpointRouteBuilderExtensions.cs @@ -1,22 +1,62 @@ using System; -using HotChocolate; -using HotChocolate.AspNetCore; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.FileProviders; +using HotChocolate; +using HotChocolate.AspNetCore; +using HotChocolate.AspNetCore.Extensions; namespace Microsoft.AspNetCore.Builder { + /// + /// Provides GraphQL extensions to the . + /// public static class HttpEndpointRouteBuilderExtensions { - public static IEndpointConventionBuilder MapGraphQL( + /// + /// Adds a GraphQL endpoint to the endpoint configurations. + /// + /// + /// The . + /// + /// + /// The path to which the GraphQL endpoint shall be mapped. + /// + /// + /// The name of the schema that shall be used by this endpoint. + /// + /// + /// Returns the so that + /// configuration can be chained. + /// + public static GraphQLEndpointConventionBuilder MapGraphQL( this IEndpointRouteBuilder endpointRouteBuilder, string path = "/graphql", NameString schemaName = default) => MapGraphQL(endpointRouteBuilder, new PathString(path), schemaName); - public static IEndpointConventionBuilder MapGraphQL( + /// + /// Adds a GraphQL endpoint to the endpoint configurations. + /// + /// + /// The . + /// + /// + /// The path to which the GraphQL endpoint shall be mapped. + /// + /// + /// The name of the schema that shall be used by this endpoint. + /// + /// + /// Returns the so that + /// configuration can be chained. + /// + /// + /// The is null. + /// + public static GraphQLEndpointConventionBuilder MapGraphQL( this IEndpointRouteBuilder endpointRouteBuilder, PathString path, NameString schemaName = default) @@ -40,13 +80,37 @@ public static class HttpEndpointRouteBuilderExtensions .UseMiddleware(fileProvider, path) .UseMiddleware(schemaNameOrDefault, path) .UseMiddleware(fileProvider, path) - .UseMiddleware(schemaNameOrDefault); + .UseMiddleware(schemaNameOrDefault) + .Use(next => context => + { + context.Response.StatusCode = 404; + return Task.CompletedTask; + }); - return endpointRouteBuilder - .Map(pattern, requestPipeline.Build()) - .WithDisplayName("Hot Chocolate GraphQL Pipeline"); + return new GraphQLEndpointConventionBuilder( + endpointRouteBuilder + .Map(pattern, requestPipeline.Build()) + .WithDisplayName("Hot Chocolate GraphQL Pipeline")); } + /// + /// Specifies the GraphQL server options. + /// + /// + /// The . + /// + /// + /// The GraphQL server options. + /// + /// + /// Returns the so that + /// configuration can be chained. + /// + public static GraphQLEndpointConventionBuilder WithOptions( + this GraphQLEndpointConventionBuilder builder, + GraphQLServerOptions serverOptions) => + builder.WithMetadata(serverOptions); + [Obsolete("Use the new routing API.")] public static IApplicationBuilder UseGraphQL( this IApplicationBuilder applicationBuilder, diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs index 7e15c134195..5a9926a7bb5 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HttpResponseExtensions.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; using Microsoft.AspNetCore.Http; namespace HotChocolate.AspNetCore diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs index a520f2c9270..8cfd7f65210 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RequestExecutorExtensions.cs @@ -1,6 +1,5 @@ -using HotChocolate.AspNetCore.Utilities; -using HotChocolate.Execution; using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; namespace HotChocolate.AspNetCore { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RoutingEndpointConventionBuilderExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RoutingEndpointConventionBuilderExtensions.cs deleted file mode 100644 index a8de61100bf..00000000000 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/RoutingEndpointConventionBuilderExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using HotChocolate.AspNetCore; - -namespace Microsoft.AspNetCore.Builder -{ - public static class RoutingEndpointConventionBuilderExtensions - { - public static TBuilder WithToolOptions(this TBuilder builder, ToolOptions options) - where TBuilder : IEndpointConventionBuilder - { - return builder.WithMetadata(options); - } - } -} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/GraphQLRequestException.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLRequestException.cs similarity index 94% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/GraphQLRequestException.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLRequestException.cs index 72b38066db8..932b6393743 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/GraphQLRequestException.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLRequestException.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; using System; +using System.Collections.Generic; using System.Runtime.Serialization; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { [Serializable] public class GraphQLRequestException diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs new file mode 100644 index 00000000000..d50d5977bc1 --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Http; + +namespace HotChocolate.AspNetCore +{ + /// + /// Represents the GraphQL server options. + /// + public class GraphQLServerOptions + { + /// + /// Gets the GraphQL tool options for Banana Cake Pop. + /// + public GraphQLToolOptions Tool { get; } = new(); + + /// + /// Gets or sets which GraphQL options are allowed on GET requests. + /// + public AllowedGetOperations AllowedGetOperations { get; set; } = + AllowedGetOperations.Query; + + /// + /// Defines if GraphQL HTTP GET requests are allowed. + /// + /// + public bool EnableGetRequests { get; set; } = true; + + /// + /// Defines if the GraphQL schema SDL can be downloaded. + /// + /// + public bool EnableSchemaRequests { get; set; } = true; + } + + /// + /// Represents the GraphQL tool options for Banana Cake Pop. + /// + public class GraphQLToolOptions + { + /// + /// Gets or sets the default document content. + /// + public string? Document { get; set; } + + /// + /// Gets or sets the default method. + /// + public DefaultCredentials? Credentials { get; set; } + + /// + /// Gets or sets the default http headers for Banana Cake Pop. + /// + public IHeaderDictionary? HttpHeaders { get; set; } + + /// + /// Gets or sets the default + /// + public DefaultHttpMethod? HttpMethod { get; set; } + + /// + /// Defines if Banana Cake Pop is enabled. + /// + public bool Enable { get; set; } = true; + } + + public enum DefaultCredentials + { + Include, + Omit, + SameOrigin, + } + + public enum DefaultHttpMethod + { + Get, + Post + } + + public enum AllowedGetOperations + { + Query, + QueryAndMutation + } +} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs index 95ccc0e459c..efda9b84314 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs @@ -3,7 +3,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using HotChocolate.AspNetCore.Utilities; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.Execution; using HotChocolate.Language; using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; @@ -12,6 +12,8 @@ namespace HotChocolate.AspNetCore { public class HttpGetMiddleware : MiddlewareBase { + private static readonly OperationType[] _onlyQueries = { OperationType.Query }; + private readonly IHttpRequestParser _requestParser; public HttpGetMiddleware( @@ -28,7 +30,8 @@ public class HttpGetMiddleware : MiddlewareBase public async Task InvokeAsync(HttpContext context) { - if (HttpMethods.IsGet(context.Request.Method)) + if (HttpMethods.IsGet(context.Request.Method) && + (context.GetGraphQLServerOptions()?.EnableGetRequests ?? true)) { await HandleRequestAsync(context); } @@ -54,8 +57,15 @@ private async Task HandleRequestAsync(HttpContext context) { // next we parse the GraphQL request. GraphQLRequest request = _requestParser.ReadParamsRequest(context.Request.Query); + GraphQLServerOptions? options = context.GetGraphQLServerOptions(); result = await ExecuteSingleAsync( - context, requestExecutor, requestInterceptor, request); + context, + requestExecutor, + requestInterceptor, + request, + options is null or { AllowedGetOperations: AllowedGetOperations.Query } + ? _onlyQueries + : null); } catch (GraphQLRequestException ex) { @@ -74,7 +84,7 @@ private async Task HandleRequestAsync(HttpContext context) // in any case we will have a valid GraphQL result at this point that can be written // to the HTTP response stream. - Debug.Assert(result is not null, "No GraphQL result was created."); + Debug.Assert(result is not null!, "No GraphQL result was created."); await WriteResultAsync(context.Response, result, statusCode, context.RequestAborted); } } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs index ce4b3b3aae8..853872d4a35 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetSchemaMiddleware.cs @@ -1,7 +1,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using HotChocolate.AspNetCore.Utilities; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.Execution; using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; @@ -22,7 +22,8 @@ public class HttpGetSchemaMiddleware : MiddlewareBase public async Task InvokeAsync(HttpContext context) { if (HttpMethods.IsGet(context.Request.Method) && - context.Request.Query.ContainsKey("SDL")) + context.Request.Query.ContainsKey("SDL") && + (context.GetGraphQLServerOptions()?.EnableSchemaRequests ?? true)) { await HandleRequestAsync(context); } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpHeaderKeys.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs similarity index 62% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpHeaderKeys.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs index a85643efb7f..e77d095964f 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpHeaderKeys.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderKeys.cs @@ -1,6 +1,6 @@ -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { - public static class HttpHeaderKeys + internal static class HttpHeaderKeys { public const string Tracing = "GraphQL-Tracing"; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs new file mode 100644 index 00000000000..34dba630b99 --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpHeaderValues.cs @@ -0,0 +1,7 @@ +namespace HotChocolate.AspNetCore +{ + internal static class HttpHeaderValues + { + public const string TracingEnabled = "1"; + } +} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs index 9c95deb79f3..9e28ea6a1eb 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using HotChocolate.AspNetCore.Utilities; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.Execution; using HotChocolate.Language; using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpRequestInterceptorDelegate.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpRequestInterceptorDelegate.cs similarity index 88% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpRequestInterceptorDelegate.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/HttpRequestInterceptorDelegate.cs index 907821f5fa8..75b851cfab5 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpRequestInterceptorDelegate.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpRequestInterceptorDelegate.cs @@ -1,9 +1,9 @@ using System.Threading; using System.Threading.Tasks; -using HotChocolate.Execution; using Microsoft.AspNetCore.Http; +using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { public delegate ValueTask HttpRequestInterceptorDelegate( HttpContext context, diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpRequestInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/IHttpRequestInterceptor.cs similarity index 89% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpRequestInterceptor.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/IHttpRequestInterceptor.cs index c0659e46bd0..6af5873aeca 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpRequestInterceptor.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/IHttpRequestInterceptor.cs @@ -1,9 +1,9 @@ using System.Threading; using System.Threading.Tasks; -using HotChocolate.Execution; using Microsoft.AspNetCore.Http; +using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { public interface IHttpRequestInterceptor { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ISocketSessionInterceptor.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ISocketSessionInterceptor.cs similarity index 94% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ISocketSessionInterceptor.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/ISocketSessionInterceptor.cs index e9c6fd011bb..5735abab8c8 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ISocketSessionInterceptor.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ISocketSessionInterceptor.cs @@ -4,7 +4,7 @@ using HotChocolate.AspNetCore.Subscriptions.Messages; using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore { public interface ISocketSessionInterceptor { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs index 92e0f0fe7e7..9fe8f15e166 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs @@ -3,8 +3,8 @@ using System.Net; using System.Threading; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; using Microsoft.AspNetCore.Http; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.Execution; using HotChocolate.Language; using RequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; @@ -74,9 +74,11 @@ public class MiddlewareBase : IDisposable HttpContext context, IRequestExecutor requestExecutor, IHttpRequestInterceptor requestInterceptor, - GraphQLRequest request) + GraphQLRequest request, + OperationType[]? allowedOperations = null) { QueryRequestBuilder requestBuilder = QueryRequestBuilder.From(request); + requestBuilder.SetAllowedOperations(allowedOperations); await requestInterceptor.OnCreateAsync( context, requestExecutor, requestBuilder, context.RequestAborted); diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpRequestParser.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpRequestParser.cs similarity index 98% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpRequestParser.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpRequestParser.cs index 4a85eec57c1..6426d8c64ca 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpRequestParser.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpRequestParser.cs @@ -3,13 +3,13 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using HotChocolate.Language; using HotChocolate.Utilities; -using Microsoft.AspNetCore.Http; using static HotChocolate.Language.Utf8GraphQLRequestParser; -using static HotChocolate.AspNetCore.Utilities.ThrowHelper; +using static HotChocolate.AspNetCore.ThrowHelper; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore.Serialization { internal class DefaultHttpRequestParser : IHttpRequestParser { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpResultSerializer.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResultSerializer.cs similarity index 77% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpResultSerializer.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResultSerializer.cs index 2d659304a5c..9697bbbd26d 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/DefaultHttpResultSerializer.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResultSerializer.cs @@ -1,22 +1,20 @@ using System; using System.IO; +using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using HotChocolate.Execution; using HotChocolate.Execution.Serialization; -using static HotChocolate.AspNetCore.Utilities.ErrorHelper; +using static HotChocolate.AspNetCore.ErrorHelper; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore.Serialization { public class DefaultHttpResultSerializer : IHttpResultSerializer { - private readonly JsonQueryResultSerializer _jsonSerializer = - new JsonQueryResultSerializer(); - private readonly JsonArrayResponseStreamSerializer _jsonArraySerializer = - new JsonArrayResponseStreamSerializer(); - private readonly MultiPartResponseStreamSerializer _multiPartSerializer = - new MultiPartResponseStreamSerializer(); + private readonly JsonQueryResultSerializer _jsonSerializer = new(); + private readonly JsonArrayResponseStreamSerializer _jsonArraySerializer = new(); + private readonly MultiPartResponseStreamSerializer _multiPartSerializer = new(); private readonly HttpResultSerialization _batchSerialization; private readonly HttpResultSerialization _deferSerialization; @@ -58,23 +56,36 @@ public virtual string GetContentType(IExecutionResult result) public virtual HttpStatusCode GetStatusCode(IExecutionResult result) { - switch (result) + return result switch { - case QueryResult queryResult: - return queryResult.Data is null - ? queryResult.ContextData is not null && - queryResult.ContextData.ContainsKey(ContextDataKeys.ValidationErrors) - ? HttpStatusCode.BadRequest - : HttpStatusCode.InternalServerError - : HttpStatusCode.OK; + QueryResult queryResult => GetStatusCode(queryResult), + DeferredQueryResult => HttpStatusCode.OK, + BatchQueryResult => HttpStatusCode.OK, + _ => HttpStatusCode.InternalServerError + }; + } - case DeferredQueryResult: - case BatchQueryResult: - return HttpStatusCode.OK; + private HttpStatusCode GetStatusCode(IQueryResult result) + { + if (result.Data is not null) + { + return HttpStatusCode.OK; + } - default: - return HttpStatusCode.InternalServerError; + if (result.ContextData is not null) + { + if (result.ContextData.ContainsKey(WellKnownContextData.ValidationErrors)) + { + return HttpStatusCode.BadRequest; + } + + if (result.ContextData.ContainsKey(WellKnownContextData.OperationNotAllowed)) + { + return HttpStatusCode.MethodNotAllowed; + } } + + return HttpStatusCode.InternalServerError; } public virtual async ValueTask SerializeAsync( diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpResultSerialization.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResultSerialization.cs similarity index 67% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpResultSerialization.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResultSerialization.cs index 70f27e27f46..83fd2dd81b2 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpResultSerialization.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/HttpResultSerialization.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore.Serialization { public enum HttpResultSerialization { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpRequestParser.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpRequestParser.cs similarity index 90% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpRequestParser.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpRequestParser.cs index 97782dd2695..9d2e9bf73e7 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpRequestParser.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpRequestParser.cs @@ -2,10 +2,10 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using HotChocolate.Language; using Microsoft.AspNetCore.Http; +using HotChocolate.Language; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore.Serialization { public interface IHttpRequestParser { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpResultSerializer.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpResultSerializer.cs new file mode 100644 index 00000000000..a1e2318f6cc --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/IHttpResultSerializer.cs @@ -0,0 +1,54 @@ +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution; + +namespace HotChocolate.AspNetCore.Serialization +{ + /// + /// This interface specifies how a GraphQL result is serialized to a HTTP response. + /// + public interface IHttpResultSerializer + { + /// + /// Gets the HTTP content type for the specified execution result. + /// + /// + /// The GraphQL execution result. + /// + /// + /// Returns a string representing the content type, + /// eg. "application/json; charset=utf-8". + /// + string GetContentType(IExecutionResult result); + + /// + /// Gets the HTTP status code for the specified execution result. + /// + /// + /// The GraphQL execution result. + /// + /// + /// Returns the HTTP status code, eg. . + /// + HttpStatusCode GetStatusCode(IExecutionResult result); + + /// + /// Serializes the specified execution result. + /// + /// + /// The GraphQL execution result. + /// + /// + /// The HTTP response stream. + /// + /// + /// The request cancellation token. + /// + ValueTask SerializeAsync( + IExecutionResult result, + Stream stream, + CancellationToken cancellationToken); + } +} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/DataStartMessageHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/DataStartMessageHandler.cs index 20eec275341..c925bb58929 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/DataStartMessageHandler.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/DataStartMessageHandler.cs @@ -1,9 +1,8 @@ using System; using System.Threading; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; using HotChocolate.Execution; -using static HotChocolate.AspNetCore.Utilities.ThrowHelper; +using static HotChocolate.AspNetCore.ThrowHelper; namespace HotChocolate.AspNetCore.Subscriptions.Messages { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/InitializeConnectionMessageHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/InitializeConnectionMessageHandler.cs index 8b611c9a502..ea4ea0f611a 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/InitializeConnectionMessageHandler.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/Messages/InitializeConnectionMessageHandler.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; namespace HotChocolate.AspNetCore.Subscriptions.Messages { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/SubscriptionManager.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/SubscriptionManager.cs index f994127bff0..3932e975670 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/SubscriptionManager.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Subscriptions/SubscriptionManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; namespace HotChocolate.AspNetCore.Subscriptions diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ThrowHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ThrowHelper.cs new file mode 100644 index 00000000000..2546fb61b34 --- /dev/null +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ThrowHelper.cs @@ -0,0 +1,46 @@ +using System; +using HotChocolate.Language; +using static HotChocolate.AspNetCore.Properties.AspNetCoreResources; + +namespace HotChocolate.AspNetCore +{ + internal static class ThrowHelper + { + public static GraphQLRequestException DefaultHttpRequestParser_QueryAndIdMissing() => + new(ErrorBuilder.New() + .SetMessage(ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing) + .SetCode(ErrorCodes.Server.QueryAndIdMissing) + .Build()); + + public static GraphQLRequestException DefaultHttpRequestParser_SyntaxError( + SyntaxException ex) => + new(ErrorBuilder.New() + .SetMessage(ex.Message) + .AddLocation(ex.Line, ex.Column) + .SetCode(ErrorCodes.Server.SyntaxError) + .Build()); + + public static GraphQLRequestException DefaultHttpRequestParser_UnexpectedError( + Exception ex) => + new(ErrorBuilder.New() + .SetMessage(ex.Message) + .SetException(ex) + .SetCode(ErrorCodes.Server.UnexpectedRequestParserError) + .Build()); + + public static GraphQLRequestException DefaultHttpRequestParser_RequestIsEmpty() => + new(ErrorBuilder.New() + .SetMessage(ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty) + .SetCode(ErrorCodes.Server.RequestInvalid) + .Build()); + + public static GraphQLRequestException DefaultHttpRequestParser_MaxRequestSizeExceeded() => + new(ErrorBuilder.New() + .SetMessage(ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded) + .SetCode(ErrorCodes.Server.MaxRequestSize) + .Build()); + + public static NotSupportedException DataStartMessageHandler_RequestTypeNotSupported() => + new(ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported); + } +} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolDefaultFileMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolDefaultFileMiddleware.cs index ccc20d55761..0f28aeb527f 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolDefaultFileMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolDefaultFileMiddleware.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.FileProviders; using Microsoft.Net.Http.Headers; -using HotChocolate.AspNetCore.Utilities; namespace HotChocolate.AspNetCore { @@ -57,7 +56,8 @@ public Task Invoke(HttpContext context) { if (context.Request.IsGetOrHeadMethod() && context.Request.AcceptHeaderContainsHtml() && - context.Request.TryMatchPath(_matchUrl, true, out PathString subPath)) + context.Request.TryMatchPath(_matchUrl, true, out PathString subPath) && + (context.GetGraphQLToolOptions()?.Enable ?? true)) { var dirContents = _fileProvider.GetDirectoryContents(subPath.Value); diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptions.cs deleted file mode 100644 index e68ae44d44d..00000000000 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace HotChocolate.AspNetCore -{ - public class ToolOptions - { - public string? Document { get; set; } - - public DefaultCredentials? Credentials { get; set; } - - public IHeaderDictionary? HttpHeaders { get; set; } - - public DefaultHttpMethod? HttpMethod { get; set; } - } - - public enum DefaultCredentials - { - Include, - Omit, - SameOrigin, - } - - public enum DefaultHttpMethod - { - Get, - Post - } -} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptionsFileMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptionsFileMiddleware.cs index 6061281e01c..6f272884989 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptionsFileMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptionsFileMiddleware.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; -using HotChocolate.Execution; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using HotChocolate.AspNetCore.Serialization; +using HotChocolate.Execution; using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; -using Microsoft.AspNetCore.StaticFiles; namespace HotChocolate.AspNetCore { @@ -15,7 +15,6 @@ public class ToolOptionsFileMiddleware : MiddlewareBase { private const string _configFile = "/bcp-config.json"; - private readonly IContentTypeProvider _contentTypeProvider; private readonly PathString _matchUrl; public ToolOptionsFileMiddleware( @@ -26,7 +25,6 @@ public class ToolOptionsFileMiddleware PathString matchUrl) : base(next, executorResolver, resultSerializer, schemaName) { - _contentTypeProvider = new FileExtensionContentTypeProvider(); _matchUrl = matchUrl; } @@ -38,9 +36,10 @@ public async Task Invoke(HttpContext context) { if (context.Request.IsGetOrHeadMethod() && context.Request.TryMatchPath(_matchUrl, false, out PathString subPath) && - subPath.Value == _configFile) + subPath.Value == _configFile && + (context.GetGraphQLToolOptions()?.Enable ?? true)) { - ToolOptions? options = context.GetEndpoint()?.Metadata.GetMetadata(); + GraphQLToolOptions? options = context.GetGraphQLToolOptions(); string endpointPath = context.Request.Path.Value!.Replace(_configFile, ""); string schemaEndpoint = CreateEndpointUri( context.Request.Host.Value, @@ -78,9 +77,9 @@ public async Task Invoke(HttpContext context) } } - private string? ConvertCredentialsToString(DefaultCredentials? credentials) + private static string? ConvertCredentialsToString(DefaultCredentials? credentials) { - if (credentials is { }) + if (credentials is not null) { switch (credentials) { @@ -96,13 +95,14 @@ public async Task Invoke(HttpContext context) return null; } - private IDictionary? ConvertHttpHeadersToDictionary(IHeaderDictionary? httpHeaders) + private static IDictionary? ConvertHttpHeadersToDictionary( + IHeaderDictionary? httpHeaders) { - if (httpHeaders is { }) + if (httpHeaders is not null) { var result = new Dictionary(); - foreach (var (key, value) in httpHeaders) + foreach ((var key, StringValues value) in httpHeaders) { result.Add(key, value.ToString()); } @@ -115,7 +115,7 @@ public async Task Invoke(HttpContext context) private string? ConvertHttpMethodToString(DefaultHttpMethod? httpMethod) { - if (httpMethod is { }) + if (httpMethod is not null) { switch (httpMethod) { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolStaticFileMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolStaticFileMiddleware.cs index ffc0f44b907..08880d46e62 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/ToolStaticFileMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ToolStaticFileMiddleware.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; -using HotChocolate.AspNetCore.Utilities; namespace HotChocolate.AspNetCore { @@ -59,7 +58,8 @@ public Task Invoke(HttpContext context) { if (context.Request.IsGetOrHeadMethod() && context.Request.TryMatchPath(_matchUrl, false, out PathString subPath) && - _contentTypeProvider.TryGetContentType(subPath.Value, out string contentType)) + _contentTypeProvider.TryGetContentType(subPath.Value, out string contentType) && + (context.GetGraphQLToolOptions()?.Enable ?? true)) { return TryServeStaticFile(context, contentType, subPath); } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpHeaderValues.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpHeaderValues.cs deleted file mode 100644 index be32052ffe5..00000000000 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/HttpHeaderValues.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace HotChocolate.AspNetCore.Utilities -{ - public static class HttpHeaderValues - { - public const string TracingEnabled = "1"; - } -} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpResultSerializer.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpResultSerializer.cs deleted file mode 100644 index e139ded22dd..00000000000 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/IHttpResultSerializer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using HotChocolate.Execution; - -namespace HotChocolate.AspNetCore.Utilities -{ - public interface IHttpResultSerializer - { - string GetContentType(IExecutionResult result); - - HttpStatusCode GetStatusCode(IExecutionResult result); - - ValueTask SerializeAsync( - IExecutionResult result, - Stream stream, - CancellationToken cancellationToken); - } -} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ThrowHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ThrowHelper.cs deleted file mode 100644 index 34ae086dc9c..00000000000 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ThrowHelper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using HotChocolate.Language; -using static HotChocolate.AspNetCore.Properties.AspNetCoreResources; - -namespace HotChocolate.AspNetCore.Utilities -{ - internal static class ThrowHelper - { - public static GraphQLRequestException DefaultHttpRequestParser_QueryAndIdMissing() => - new GraphQLRequestException( - ErrorBuilder.New() - .SetMessage(ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing) - .SetCode(ErrorCodes.Server.QueryAndIdMissing) - .Build()); - - public static GraphQLRequestException DefaultHttpRequestParser_SyntaxError( - SyntaxException ex) => - new GraphQLRequestException( - ErrorBuilder.New() - .SetMessage(ex.Message) - .AddLocation(ex.Line, ex.Column) - .SetCode(ErrorCodes.Server.SyntaxError) - .Build()); - - public static GraphQLRequestException DefaultHttpRequestParser_UnexpectedError( - Exception ex) => - new GraphQLRequestException( - ErrorBuilder.New() - .SetMessage(ex.Message) - .SetException(ex) - .SetCode(ErrorCodes.Server.UnexpectedRequestParserError) - .Build()); - - public static GraphQLRequestException DefaultHttpRequestParser_RequestIsEmpty() => - new GraphQLRequestException( - ErrorBuilder.New() - .SetMessage(ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty) - .SetCode(ErrorCodes.Server.RequestInvalid) - .Build()); - - public static GraphQLRequestException DefaultHttpRequestParser_MaxRequestSizeExceeded() => - new GraphQLRequestException( - ErrorBuilder.New() - .SetMessage(ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded) - .SetCode(ErrorCodes.Server.MaxRequestSize) - .Build()); - - public static NotSupportedException DataStartMessageHandler_RequestTypeNotSupported() => - new NotSupportedException(ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported); - } -} diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ExecutorWarmupTask.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/ExecutorWarmupService.cs similarity index 86% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ExecutorWarmupTask.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/ExecutorWarmupService.cs index 42d73c21402..ffea514eb68 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/ExecutorWarmupTask.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/ExecutorWarmupService.cs @@ -3,21 +3,21 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using HotChocolate.Execution; using Microsoft.Extensions.Hosting; +using HotChocolate.Execution; -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore.Warmup { - internal class ExecutorWarmupTask : BackgroundService + internal class ExecutorWarmupService : BackgroundService { private readonly IRequestExecutorResolver _executorResolver; private readonly HashSet _schemaNames; - public ExecutorWarmupTask( + public ExecutorWarmupService( IRequestExecutorResolver executorResolver, IEnumerable schemas) { - if (executorResolver is null) + if (executorResolver is null!) { throw new ArgumentNullException(nameof(executorResolver)); } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/WarmupSchema.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/WarmupSchema.cs similarity index 82% rename from src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/WarmupSchema.cs rename to src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/WarmupSchema.cs index adce77f7087..95f873eeb85 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Utilities/WarmupSchema.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Warmup/WarmupSchema.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.AspNetCore.Utilities +namespace HotChocolate.AspNetCore.Warmup { internal class WarmupSchema { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs index cc174290d76..564ade0eb3e 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/WebSocketSubscriptionMiddleware.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.AspNetCore.Subscriptions; -using HotChocolate.AspNetCore.Utilities; using HotChocolate.Execution; using RequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Extensions/ServiceCollectionExtensionTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Extensions/ServiceCollectionExtensionTests.cs index 30db824db3f..a8c8c434a20 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Extensions/ServiceCollectionExtensionTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Extensions/ServiceCollectionExtensionTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.AspNetCore.Utilities; using HotChocolate.Execution.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -16,7 +17,7 @@ public static void AddHttpRequestSerializer_OfT() // act HotChocolateAspNetCoreServiceCollectionExtensions - .AddHttpRequestSerializer(serviceCollection); + .AddHttpResultSerializer(serviceCollection); // assert Assert.Collection( diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetMiddlewareTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetMiddlewareTests.cs index 087dedd9a08..13f6d0d75d7 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetMiddlewareTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetMiddlewareTests.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; +using HotChocolate.AspNetCore.Utilities; using Snapshooter; using Snapshooter.Xunit; using Xunit; @@ -270,7 +271,12 @@ public async Task SingleRequest_GetHumanName_With_StringVariable() public async Task SingleRequest_CreateReviewForEpisode_With_ObjectVariable() { // arrange - TestServer server = CreateStarWarsServer(); + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + AllowedGetOperations = AllowedGetOperations.QueryAndMutation + })); // act ClientQueryResult result = @@ -307,7 +313,12 @@ public async Task SingleRequest_CreateReviewForEpisode_With_ObjectVariable() public async Task SingleRequest_CreateReviewForEpisode_Omit_NonNull_Variable() { // arrange - TestServer server = CreateStarWarsServer(); + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + AllowedGetOperations = AllowedGetOperations.QueryAndMutation + })); // act ClientQueryResult result = @@ -343,7 +354,12 @@ public async Task SingleRequest_CreateReviewForEpisode_Omit_NonNull_Variable() public async Task SingleRequest_CreateReviewForEpisode_Variables_In_ObjectValue() { // arrange - TestServer server = CreateStarWarsServer(); + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + AllowedGetOperations = AllowedGetOperations.QueryAndMutation + })); // act ClientQueryResult result = @@ -458,10 +474,7 @@ public async Task SingleRequest_ValidationError() name } }", - Variables = new Dictionary - { - { "episode", "NEW_HOPE" } - } + Variables = new Dictionary {{"episode", "NEW_HOPE"}} }); // assert @@ -489,5 +502,111 @@ public async Task SingleRequest_SyntaxError() // assert result.MatchSnapshot(); } + + [Fact] + public async Task SingleRequest_Mutation_ByDefault_NotAllowed_OnGet() + { + // arrange + TestServer server = CreateStarWarsServer(); + + // act + ClientQueryResult result = + await server.GetAsync(new ClientQueryRequest + { + Query = @" + mutation CreateReviewForEpisode( + $ep: Episode! + $review: ReviewInput!) { + createReview(episode: $ep, review: $review) { + stars + commentary + } + }", + Variables = new Dictionary + { + { "ep", "EMPIRE" }, + { + "review", + new Dictionary + { + { "stars", 5 }, + { "commentary", "This is a great movie!" }, + } + } + } + }); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task SingleRequest_Mutation_Set_To_Be_Allowed_on_Get() + { + // arrange + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + AllowedGetOperations = AllowedGetOperations.QueryAndMutation + })); + + // act + ClientQueryResult result = + await server.GetAsync(new ClientQueryRequest + { + Query = @" + mutation CreateReviewForEpisode( + $ep: Episode! + $review: ReviewInput!) { + createReview(episode: $ep, review: $review) { + stars + commentary + } + }", + Variables = new Dictionary + { + { "ep", "EMPIRE" }, + { + "review", + new Dictionary + { + { "stars", 5 }, + { "commentary", "This is a great movie!" }, + } + } + } + }); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Get_Middleware_Is_Disabled() + { + // arrange + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + EnableGetRequests = false + })); + + // act + ClientQueryResult result = + await server.GetAsync(new ClientQueryRequest + { + Query = @" + { + hero { + name + } + }" + }); + + // assert + result.MatchSnapshot(); + } } } diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSchemaMiddlewareTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSchemaMiddlewareTests.cs index c8379d1be3f..54943d92f7b 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSchemaMiddlewareTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpGetSchemaMiddlewareTests.cs @@ -1,8 +1,9 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; +using HotChocolate.AspNetCore.Utilities; using Snapshooter.Xunit; using Xunit; @@ -31,5 +32,27 @@ public async Task Download_GraphQL_SDL() var result = await response.Content.ReadAsStringAsync(); result.MatchSnapshot(); } + + [Fact] + public async Task Download_GraphQL_SDL_Disabled() + { + // arrange + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + EnableSchemaRequests = false + })); + var url = TestServerExtensions.CreateUrl("/graphql?sdl"); + var request = new HttpRequestMessage(HttpMethod.Get, url); + + // act + HttpResponseMessage response = await server.CreateClient().SendAsync(request); + + // assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + result.MatchSnapshot(); + } } } diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/ToolConfigurationFileMiddlewareTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/ToolConfigurationFileMiddlewareTests.cs index 4c3c3992174..bd6c8769b79 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/ToolConfigurationFileMiddlewareTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/ToolConfigurationFileMiddlewareTests.cs @@ -1,13 +1,13 @@ -using System.Net.Http.Headers; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; -using HotChocolate.AspNetCore.Utilities; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; +using HotChocolate.AspNetCore.Utilities; using Snapshooter.Xunit; using Xunit; -using Microsoft.AspNetCore.Http; namespace HotChocolate.AspNetCore { @@ -23,7 +23,25 @@ public ToolConfigurationFileMiddlewareTests(TestServerFactory serverFactory) public async Task Fetch_Tool_Config_Without_Options() { // arrange - TestServer server = CreateStarWarsServer("/graphql"); + TestServer server = CreateStarWarsServer(); + + // act + Result result = await GetAsync(server); + + // assert + result.MatchSnapshot(); + } + + [Fact] + public async Task Fetch_Tool_When_Disabled() + { + // arrange + TestServer server = CreateStarWarsServer( + configureConventions: e => e.WithOptions( + new GraphQLServerOptions + { + Tool = { Enable = false } + })); // act Result result = await GetAsync(server); @@ -36,18 +54,21 @@ public async Task Fetch_Tool_Config_Without_Options() public async Task Fetch_Tool_Config_With_Options() { // arrange - ToolOptions options = new ToolOptions + var options = new GraphQLServerOptions { - Document = "# foo", - Credentials = DefaultCredentials.SameOrigin, - HttpHeaders = new HeaderDictionary + Tool = { - { "Content-Type", "application/json" } - }, - HttpMethod = DefaultHttpMethod.Get + Document = "# foo", + Credentials = DefaultCredentials.SameOrigin, + HttpHeaders = new HeaderDictionary + { + { "Content-Type", "application/json" } + }, + HttpMethod = DefaultHttpMethod.Get + } }; TestServer server = CreateStarWarsServer("/graphql", - builder => builder.WithToolOptions(options)); + builder => builder.WithOptions(options)); // act Result result = await GetAsync(server); @@ -60,7 +81,7 @@ private async Task GetAsync(TestServer server) { HttpResponseMessage response = await server.CreateClient().GetAsync( TestServerExtensions.CreateUrl("/graphql/bcp-config.json")); - string content = await response.Content.ReadAsStringAsync(); + var content = await response.Content.ReadAsStringAsync(); return new Result { diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Utilities/ServerTestBase.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Utilities/ServerTestBase.cs index 28328dd8a44..c960f20e20a 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Utilities/ServerTestBase.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/Utilities/ServerTestBase.cs @@ -5,6 +5,8 @@ using HotChocolate.Types; using Xunit; using System; +using HotChocolate.AspNetCore.Extensions; +using HotChocolate.AspNetCore.Serialization; namespace HotChocolate.AspNetCore.Utilities { @@ -19,12 +21,12 @@ public ServerTestBase(TestServerFactory serverFactory) protected virtual TestServer CreateStarWarsServer( string pattern = "/graphql", - Action configureConventions = default) + Action configureConventions = default) { return ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddStarWarsTypes() .AddTypeExtension() @@ -57,12 +59,8 @@ public ServerTestBase(TestServerFactory serverFactory) .UseRouting() .UseEndpoints(endpoints => { - IEndpointConventionBuilder builder = endpoints.MapGraphQL(pattern); - - if (configureConventions is { }) - { - configureConventions(builder); - } + GraphQLEndpointConventionBuilder builder = endpoints.MapGraphQL(pattern); + configureConventions?.Invoke(builder); endpoints.MapGraphQL("/evict", "evict"); endpoints.MapGraphQL("/arguments", "arguments"); diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.Get_Middleware_Is_Disabled.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.Get_Middleware_Is_Disabled.snap new file mode 100644 index 00000000000..7fb2df22e21 --- /dev/null +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.Get_Middleware_Is_Disabled.snap @@ -0,0 +1,7 @@ +{ + "ContentType": null, + "StatusCode": "NotFound", + "Data": null, + "Errors": null, + "Extensions": null +} diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.SingleRequest_Mutation_ByDefault_NotAllowed_OnGet.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.SingleRequest_Mutation_ByDefault_NotAllowed_OnGet.snap new file mode 100644 index 00000000000..5a3093786e4 --- /dev/null +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.SingleRequest_Mutation_ByDefault_NotAllowed_OnGet.snap @@ -0,0 +1,11 @@ +{ + "ContentType": "application/json; charset=utf-8", + "StatusCode": "MethodNotAllowed", + "Data": null, + "Errors": [ + { + "message": "The specified operation kind is not allowed." + } + ], + "Extensions": null +} diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.SingleRequest_Mutation_Set_To_Be_Allowed_on_Get.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.SingleRequest_Mutation_Set_To_Be_Allowed_on_Get.snap new file mode 100644 index 00000000000..d5f5df399eb --- /dev/null +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetMiddlewareTests.SingleRequest_Mutation_Set_To_Be_Allowed_on_Get.snap @@ -0,0 +1,12 @@ +{ + "ContentType": "application/json; charset=utf-8", + "StatusCode": "OK", + "Data": { + "createReview": { + "stars": 5, + "commentary": "This is a great movie!" + } + }, + "Errors": null, + "Extensions": null +} diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSchemaMiddlewareTests.Download_GraphQL_SDL_Disabled.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSchemaMiddlewareTests.Download_GraphQL_SDL_Disabled.snap new file mode 100644 index 00000000000..0f262c27fc4 --- /dev/null +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpGetSchemaMiddlewareTests.Download_GraphQL_SDL_Disabled.snap @@ -0,0 +1 @@ +{"errors":[{"message":"Either the parameter query or the parameter id has to be set.","extensions":{"code":"HC0013"}}]} diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/ToolConfigurationFileMiddlewareTests.Fetch_Tool_When_Disabled.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/ToolConfigurationFileMiddlewareTests.Fetch_Tool_When_Disabled.snap new file mode 100644 index 00000000000..d6685c7169d --- /dev/null +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/ToolConfigurationFileMiddlewareTests.Fetch_Tool_When_Disabled.snap @@ -0,0 +1,14 @@ +{ + "Content": "{\"errors\":[{\"message\":\"Either the parameter query or the parameter id has to be set.\",\"extensions\":{\"code\":\"HC0013\"}}]}", + "ContentType": { + "CharSet": "utf-8", + "Parameters": [ + { + "Name": "charset", + "Value": "utf-8" + } + ], + "MediaType": "application/json" + }, + "StatusCode": "BadRequest" +} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequest.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequest.cs index 9788c11965b..ae1d85f4ad9 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequest.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using HotChocolate.Language; #nullable enable @@ -24,5 +25,7 @@ public interface IQueryRequest IReadOnlyDictionary? Extensions { get; } IServiceProvider? Services { get; } + + OperationType[]? AllowedOperations { get; } } } diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequestBuilder.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequestBuilder.cs index 8c891410c51..31502e10eaa 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequestBuilder.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/IQueryRequestBuilder.cs @@ -2,87 +2,92 @@ using System.Collections.Generic; using HotChocolate.Language; +#nullable enable + namespace HotChocolate.Execution { public interface IQueryRequestBuilder { IQueryRequestBuilder SetQuery( - string querySource); + string sourceText); IQueryRequestBuilder SetQuery( - DocumentNode queryDocument); + DocumentNode document); IQueryRequestBuilder SetQueryId( - string queryName); + string? queryName); IQueryRequestBuilder SetQueryHash( - string queryHash); + string? queryHash); IQueryRequestBuilder SetOperation( - string operationName); + string? operationName); IQueryRequestBuilder SetVariableValues( - Dictionary variableValues); + Dictionary? variableValues); IQueryRequestBuilder SetVariableValues( - IDictionary variableValues); + IDictionary? variableValues); IQueryRequestBuilder SetVariableValues( - IReadOnlyDictionary variableValues); + IReadOnlyDictionary? variableValues); IQueryRequestBuilder AddVariableValue( - string name, object value); + string name, object? value); IQueryRequestBuilder TryAddVariableValue( - string name, object value); + string name, object? value); IQueryRequestBuilder SetVariableValue( - string name, object value); + string name, object? value); IQueryRequestBuilder SetInitialValue( - object initialValue); + object? initialValue); IQueryRequestBuilder SetProperties( - Dictionary properties); + Dictionary? properties); IQueryRequestBuilder SetProperties( - IDictionary properties); + IDictionary? properties); IQueryRequestBuilder SetProperties( - IReadOnlyDictionary properties); + IReadOnlyDictionary? properties); IQueryRequestBuilder AddProperty( - string name, object value); + string name, object? value); IQueryRequestBuilder TryAddProperty( - string name, object value); + string name, object? value); IQueryRequestBuilder SetProperty( - string name, object value); + string name, object? value); IQueryRequestBuilder SetExtensions( - Dictionary extensions); + Dictionary? extensions); IQueryRequestBuilder SetExtensions( - IDictionary extensions); + IDictionary? extensions); IQueryRequestBuilder SetExtensions( - IReadOnlyDictionary extensions); + IReadOnlyDictionary? extensions); IQueryRequestBuilder AddExtension( - string name, object value); + string name, object? value); IQueryRequestBuilder TryAddExtension( - string name, object value); + string name, object? value); IQueryRequestBuilder SetExtension( - string name, object value); + string name, object? value); IQueryRequestBuilder SetServices( - IServiceProvider services); + IServiceProvider? services); IQueryRequestBuilder TrySetServices( - IServiceProvider services); + IServiceProvider? services); + + IQueryRequestBuilder SetAllowedOperations( + OperationType[]? allowedOperations); IReadOnlyQueryRequest Create(); } diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequest.cs b/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequest.cs index 4ddf7afd1a3..cd357a5fa46 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequest.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using HotChocolate.Language; using HotChocolate.Properties; #nullable enable @@ -17,7 +18,8 @@ public class QueryRequest : IReadOnlyQueryRequest IReadOnlyDictionary? contextData = null, IReadOnlyDictionary? extensions = null, IServiceProvider? services = null, - object? initialValue = null) + object? initialValue = null, + OperationType[]? allowedOperations = null) { if (query is null && queryId is null) { @@ -34,6 +36,7 @@ public class QueryRequest : IReadOnlyQueryRequest Extensions = extensions; Services = services; InitialValue = initialValue; + AllowedOperations = allowedOperations; } public IQuery? Query { get; } @@ -53,5 +56,7 @@ public class QueryRequest : IReadOnlyQueryRequest public IServiceProvider? Services { get; } public object? InitialValue { get; } + + public OperationType[]? AllowedOperations { get; } } } diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequestBuilder.cs b/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequestBuilder.cs index db017aabaa2..b765c1f1c13 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequestBuilder.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/QueryRequestBuilder.cs @@ -4,109 +4,119 @@ using HotChocolate.Language; using HotChocolate.Properties; +#nullable enable + namespace HotChocolate.Execution { public class QueryRequestBuilder : IQueryRequestBuilder { - private IQuery _query; - private string _queryName; - private string _queryHash; - private string _operationName; - private IReadOnlyDictionary _readOnlyVariableValues; - private Dictionary _variableValuesDict; - private object _initialValue; - private IReadOnlyDictionary _readOnlyProperties; - private Dictionary _properties; - private IReadOnlyDictionary _readOnlyExtensions; - private Dictionary _extensions; - private IServiceProvider _services; - - public IQueryRequestBuilder SetQuery(string querySource) - { - if (string.IsNullOrEmpty(querySource)) + private IQuery? _query; + private string? _queryName; + private string? _queryHash; + private string? _operationName; + private IReadOnlyDictionary? _readOnlyVariableValues; + private Dictionary? _variableValuesDict; + private object? _initialValue; + private IReadOnlyDictionary? _readOnlyProperties; + private Dictionary? _properties; + private IReadOnlyDictionary? _readOnlyExtensions; + private Dictionary? _extensions; + private IServiceProvider? _services; + private OperationType[]? _allowedOperations; + + public IQueryRequestBuilder SetQuery(string sourceText) + { + if (string.IsNullOrEmpty(sourceText)) { throw new ArgumentException( AbstractionResources.QueryRequestBuilder_QueryIsNullOrEmpty, - nameof(querySource)); + nameof(sourceText)); } - _query = new QuerySourceText(querySource); + _query = new QuerySourceText(sourceText); return this; } - public IQueryRequestBuilder SetQuery(DocumentNode queryDocument) + public IQueryRequestBuilder SetQuery(DocumentNode document) { - if (queryDocument is null) + if (document is null) { - throw new ArgumentNullException(nameof(queryDocument)); + throw new ArgumentNullException(nameof(document)); } - _query = new QueryDocument(queryDocument); + _query = new QueryDocument(document); return this; } - public IQueryRequestBuilder SetQueryId(string queryName) + public IQueryRequestBuilder SetQueryId(string? queryName) { _queryName = queryName; return this; } - public IQueryRequestBuilder SetQueryHash(string queryHash) + public IQueryRequestBuilder SetQueryHash(string? queryHash) { _queryHash = queryHash; return this; } - public IQueryRequestBuilder SetOperation(string operationName) + public IQueryRequestBuilder SetOperation(string? operationName) { _operationName = operationName; return this; } - public IQueryRequestBuilder SetInitialValue(object initialValue) + public IQueryRequestBuilder SetInitialValue(object? initialValue) { _initialValue = initialValue; return this; } public IQueryRequestBuilder SetServices( - IServiceProvider services) + IServiceProvider? services) { _services = services; return this; } public IQueryRequestBuilder TrySetServices( - IServiceProvider services) + IServiceProvider? services) { _services ??= services; return this; } + public IQueryRequestBuilder SetAllowedOperations( + OperationType[]? allowedOperations) + { + _allowedOperations = allowedOperations; + return this; + } + public IQueryRequestBuilder SetVariableValues( - Dictionary variableValues) => - SetVariableValues((IDictionary)variableValues); + Dictionary? variableValues) => + SetVariableValues((IDictionary)variableValues); public IQueryRequestBuilder SetVariableValues( - IDictionary variableValues) + IDictionary? variableValues) { _variableValuesDict = variableValues is null ? null - : new Dictionary(variableValues); + : new Dictionary(variableValues); _readOnlyVariableValues = null; return this; } public IQueryRequestBuilder SetVariableValues( - IReadOnlyDictionary variableValues) + IReadOnlyDictionary? variableValues) { _variableValuesDict = null; _readOnlyVariableValues = variableValues; return this; } - public IQueryRequestBuilder SetVariableValue(string name, object value) + public IQueryRequestBuilder SetVariableValue(string name, object? value) { InitializeVariables(); @@ -115,7 +125,7 @@ public IQueryRequestBuilder SetVariableValue(string name, object value) } public IQueryRequestBuilder AddVariableValue( - string name, object value) + string name, object? value) { InitializeVariables(); @@ -124,7 +134,7 @@ public IQueryRequestBuilder SetVariableValue(string name, object value) } public IQueryRequestBuilder TryAddVariableValue( - string name, object value) + string name, object? value) { InitializeVariables(); @@ -136,29 +146,29 @@ public IQueryRequestBuilder SetVariableValue(string name, object value) } public IQueryRequestBuilder SetProperties( - Dictionary properties) => - SetProperties((IDictionary)properties); + Dictionary? properties) => + SetProperties((IDictionary?)properties); public IQueryRequestBuilder SetProperties( - IDictionary properties) + IDictionary? properties) { _properties = properties is null ? null - : new Dictionary(properties); + : new Dictionary(properties); _readOnlyProperties = null; return this; } public IQueryRequestBuilder SetProperties( - IReadOnlyDictionary properties) + IReadOnlyDictionary? properties) { _properties = null; _readOnlyProperties = properties; return this; } - public IQueryRequestBuilder SetProperty(string name, object value) + public IQueryRequestBuilder SetProperty(string name, object? value) { InitializeProperties(); @@ -167,7 +177,7 @@ public IQueryRequestBuilder SetProperty(string name, object value) } public IQueryRequestBuilder AddProperty( - string name, object value) + string name, object? value) { InitializeProperties(); @@ -176,7 +186,7 @@ public IQueryRequestBuilder SetProperty(string name, object value) } public IQueryRequestBuilder TryAddProperty( - string name, object value) + string name, object? value) { InitializeProperties(); @@ -188,28 +198,28 @@ public IQueryRequestBuilder SetProperty(string name, object value) } public IQueryRequestBuilder SetExtensions( - Dictionary extensions) => - SetExtensions((IDictionary)extensions); + Dictionary? extensions) => + SetExtensions((IDictionary?)extensions); public IQueryRequestBuilder SetExtensions( - IDictionary extensions) + IDictionary? extensions) { _extensions = extensions is null ? null - : new Dictionary(extensions); + : new Dictionary(extensions); _readOnlyExtensions = null; return this; } public IQueryRequestBuilder SetExtensions( - IReadOnlyDictionary extensions) + IReadOnlyDictionary? extensions) { _extensions = null; _readOnlyExtensions = extensions; return this; } - public IQueryRequestBuilder SetExtension(string name, object value) + public IQueryRequestBuilder SetExtension(string name, object? value) { InitializeExtensions(); @@ -218,7 +228,7 @@ public IQueryRequestBuilder SetExtension(string name, object value) } public IQueryRequestBuilder AddExtension( - string name, object value) + string name, object? value) { InitializeExtensions(); @@ -227,7 +237,7 @@ public IQueryRequestBuilder SetExtension(string name, object value) } public IQueryRequestBuilder TryAddExtension( - string name, object value) + string name, object? value) { InitializeExtensions(); @@ -250,11 +260,12 @@ public IReadOnlyQueryRequest Create() services: _services, variableValues: GetVariableValues(), contextData: GetProperties(), - extensions: GetExtensions() + extensions: GetExtensions(), + allowedOperations: _allowedOperations ); } - private IReadOnlyDictionary GetVariableValues() + private IReadOnlyDictionary GetVariableValues() { return _variableValuesDict ?? _readOnlyVariableValues; } @@ -264,14 +275,14 @@ private void InitializeVariables() if (_variableValuesDict is null) { _variableValuesDict = _readOnlyVariableValues is null - ? new Dictionary() + ? new Dictionary() : _readOnlyVariableValues.ToDictionary( t => t.Key, t => t.Value); _readOnlyVariableValues = null; } } - private IReadOnlyDictionary GetProperties() + private IReadOnlyDictionary GetProperties() { return _properties ?? _readOnlyProperties; } @@ -281,14 +292,14 @@ private void InitializeProperties() if (_properties is null) { _properties = _readOnlyProperties is null - ? new Dictionary() + ? new Dictionary() : _readOnlyProperties.ToDictionary( t => t.Key, t => t.Value); _readOnlyProperties = null; } } - private IReadOnlyDictionary GetExtensions() + private IReadOnlyDictionary GetExtensions() { return _extensions ?? _readOnlyProperties; } @@ -298,7 +309,7 @@ private void InitializeExtensions() if (_extensions is null) { _extensions = _readOnlyExtensions is null - ? new Dictionary() + ? new Dictionary() : _readOnlyExtensions.ToDictionary( t => t.Key, t => t.Value); _readOnlyExtensions = null; @@ -308,8 +319,7 @@ private void InitializeExtensions() public static IReadOnlyQueryRequest Create(string query) => New().SetQuery(query).Create(); - public static QueryRequestBuilder New() => - new QueryRequestBuilder(); + public static QueryRequestBuilder New() => new(); public static QueryRequestBuilder From(IQueryRequest request) { diff --git a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs index 80c02f8b633..68c53c6716d 100644 --- a/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs +++ b/src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs @@ -3,5 +3,9 @@ namespace HotChocolate public static class WellKnownContextData { public const string EventMessage = "HotChocolate.Execution.EventMessage"; + public const string EnableTracing = "HotChocolate.Execution.EnableTracing"; + public const string DocumentSaved = "HotChocolate.Execution.DocumentSaved"; + public const string ValidationErrors = "HotChocolate.Execution.ValidationErrors"; + public const string OperationNotAllowed = "HotChocolate.Execution.OperationNotAllowed"; } } diff --git a/src/HotChocolate/Core/src/Execution/ContextDataKeys.cs b/src/HotChocolate/Core/src/Execution/ContextDataKeys.cs deleted file mode 100644 index 9075823c271..00000000000 --- a/src/HotChocolate/Core/src/Execution/ContextDataKeys.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace HotChocolate.Execution -{ - public static class ContextDataKeys - { - public const string EnableTracing = "EnableTracing"; - public const string DocumentSaved = "DocumentSaved"; - public const string ValidationErrors = "ValidationErrors"; - } -} diff --git a/src/HotChocolate/Core/src/Execution/ErrorHelper.cs b/src/HotChocolate/Core/src/Execution/ErrorHelper.cs index 22a26860791..a713f12aa06 100644 --- a/src/HotChocolate/Core/src/Execution/ErrorHelper.cs +++ b/src/HotChocolate/Core/src/Execution/ErrorHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using HotChocolate.Execution.Processing; using HotChocolate.Language; using static HotChocolate.Execution.Properties.Resources; @@ -160,5 +161,15 @@ internal static class ErrorHelper .SetMessage(ErrorHelper_StateInvalidForDocumentValidation_Message) .SetCode(ErrorCodes.Execution.QueryNotFound) .Build()); + + public static IQueryResult OperationKindNotAllowed() => + QueryResultBuilder.CreateError( + ErrorBuilder.New() + .SetMessage("The specified operation kind is not allowed.") + .Build(), + new Dictionary + { + { WellKnownContextData.OperationNotAllowed, null } + }); } } diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingDiagnosticEventListener.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingDiagnosticEventListener.cs index 4cf160aad4c..4f22504ce7a 100644 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingDiagnosticEventListener.cs +++ b/src/HotChocolate/Core/src/Execution/Instrumentation/ApolloTracingDiagnosticEventListener.cs @@ -88,7 +88,7 @@ private bool IsEnabled(IDictionary contextData) { return (_tracingPreference == TracingPreference.Always || (_tracingPreference == TracingPreference.OnDemand && - contextData.ContainsKey(ContextDataKeys.EnableTracing))); + contextData.ContainsKey(WellKnownContextData.EnableTracing))); } private class RequestScope : IActivityScope @@ -208,7 +208,7 @@ public void Dispose() { if (!_disposed) { - long stopTimestamp = _timestampProvider.NowInNanoseconds(); + var stopTimestamp = _timestampProvider.NowInNanoseconds(); _builder.AddResolverResult( new ApolloTracingResolverRecord( diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs index f315040e5ca..adc8dec8911 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/DocumentValidationMiddleware.cs @@ -49,7 +49,7 @@ public async ValueTask InvokeAsync(IRequestContext context) validationResult.Errors, new Dictionary { - { ContextDataKeys.ValidationErrors, true } + { WellKnownContextData.ValidationErrors, true } }); _diagnosticEvents.ValidationErrors(context, validationResult.Errors); } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs index f8201bd3697..225d7d34cdd 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/OperationExecutionMiddleware.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.ObjectPool; using HotChocolate.Execution.Instrumentation; using HotChocolate.Execution.Processing; using HotChocolate.Fetching; using HotChocolate.Language; -using Microsoft.Extensions.ObjectPool; using static HotChocolate.Execution.ThrowHelper; namespace HotChocolate.Execution.Pipeline @@ -51,102 +51,156 @@ internal sealed class OperationExecutionMiddleware throw OperationExecutionMiddleware_NoBatchDispatcher(); } - if (context.Operation is { } && context.Variables is { }) + if (context is { Operation: not null, Variables: not null }) { - if (context.Operation.Definition.Operation == OperationType.Subscription) + if (IsOperationAllowed(context)) { - context.Result = await _subscriptionExecutor - .ExecuteAsync(context) + await ExecuteOperationAsync( + context, batchDispatcher, context.Operation) .ConfigureAwait(false); - - await _next(context).ConfigureAwait(false); } else { - OperationContext? operationContext = _operationContextPool.Get(); + context.Result = ErrorHelper.OperationKindNotAllowed(); + } + } + else + { + context.Result = ErrorHelper.StateInvalidForOperationExecution(); + } + } + + private async Task ExecuteOperationAsync( + IRequestContext context, + IBatchDispatcher? batchDispatcher, + IPreparedOperation operation) + { + if (operation.Definition.Operation == OperationType.Subscription) + { + context.Result = await _subscriptionExecutor + .ExecuteAsync(context) + .ConfigureAwait(false); - try + await _next(context).ConfigureAwait(false); + } + else + { + OperationContext? operationContext = _operationContextPool.Get(); + + try + { + IQueryResult? result = await ExecuteQueryOrMutationAsync( + context, batchDispatcher, operation, operationContext) + .ConfigureAwait(false); + + if (operationContext.Execution.DeferredTaskBacklog.IsEmpty || + result is null) { - IQueryResult? result = null; - - if (context.Operation.Definition.Operation == OperationType.Query) - { - object? query = RootValueResolver.TryResolve( - context, - context.Services, - context.Operation.RootType, - ref _cachedQueryValue); - - operationContext.Initialize( - context, - context.Services, - batchDispatcher, - context.Operation, - query, - context.Variables); - - result = await _queryExecutor - .ExecuteAsync(operationContext) - .ConfigureAwait(false); - } - else if (context.Operation.Definition.Operation == OperationType.Mutation) - { - object? mutation = RootValueResolver.TryResolve( - context, - context.Services, - context.Operation.RootType, - ref _cachedMutation); - - operationContext.Initialize( - context, - context.Services, - batchDispatcher, - context.Operation, - mutation, - context.Variables); - - result = await _mutationExecutor - .ExecuteAsync(operationContext) - .ConfigureAwait(false); - } - - if (operationContext.Execution.DeferredTaskBacklog.IsEmpty || - result is null) - { - context.Result = result; - } - else - { - // if we have deferred query task we will take ownership - // of the life time handling and return the operation context - // once we handled all deferred tasks. - var operationContextOwner = new OperationContextOwner( - operationContext, _operationContextPool); - operationContext = null; - - context.Result = new DeferredQueryResult - ( - result, - new DeferredTaskExecutor(operationContextOwner), - session: operationContextOwner - ); - } - - await _next(context).ConfigureAwait(false); + context.Result = result; } - finally + else + { + // if we have deferred query task we will take ownership + // of the life time handling and return the operation context + // once we handled all deferred tasks. + var operationContextOwner = new OperationContextOwner( + operationContext, _operationContextPool); + operationContext = null; + + context.Result = new DeferredQueryResult + ( + result, + new DeferredTaskExecutor(operationContextOwner), + session: operationContextOwner + ); + } + + await _next(context).ConfigureAwait(false); + } + finally + { + if (operationContext is not null) { - if (operationContext is not null) - { - _operationContextPool.Return(operationContext); - } + _operationContextPool.Return(operationContext); } } } - else + } + + private async Task ExecuteQueryOrMutationAsync( + IRequestContext context, + IBatchDispatcher? batchDispatcher, + IPreparedOperation operation, + OperationContext operationContext) + { + IQueryResult? result = null; + + if (operation.Definition.Operation == OperationType.Query) { - context.Result = ErrorHelper.StateInvalidForOperationExecution(); + object? query = RootValueResolver.TryResolve( + context, + context.Services, + operation.RootType, + ref _cachedQueryValue); + + operationContext.Initialize( + context, + context.Services, + batchDispatcher, + operation, + query, + context.Variables); + + result = await _queryExecutor + .ExecuteAsync(operationContext) + .ConfigureAwait(false); + } + else if (operation.Definition.Operation == OperationType.Mutation) + { + object? mutation = RootValueResolver.TryResolve( + context, + context.Services, + operation.RootType, + ref _cachedMutation); + + operationContext.Initialize( + context, + context.Services, + batchDispatcher, + operation, + mutation, + context.Variables); + + result = await _mutationExecutor + .ExecuteAsync(operationContext) + .ConfigureAwait(false); + } + + return result; + } + + private bool IsOperationAllowed(IRequestContext context) + { + if (context.Request.AllowedOperations is null or { Length: 0 }) + { + return true; + } + + if (context.Request.AllowedOperations is { Length: 1 } allowed && + allowed[0] == context.Operation?.Type) + { + return true; } + + for (var i = 0; i < context.Request.AllowedOperations.Length; i++) + { + if (context.Request.AllowedOperations[i] == context.Operation?.Type) + { + return true; + } + } + + return false; } } } diff --git a/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs b/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs index 0a7424088b6..c533bd15aca 100644 --- a/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs +++ b/src/HotChocolate/Core/src/Execution/Pipeline/WritePersistedQueryMiddleware.cs @@ -29,7 +29,7 @@ internal sealed class WritePersistedQueryMiddleware throw new ArgumentNullException(nameof(next)); _diagnosticEvents = diagnosticEvents ?? throw new ArgumentNullException(nameof(diagnosticEvents)); - _hashProvider = documentHashProvider ?? + _hashProvider = documentHashProvider ?? throw new ArgumentNullException(nameof(documentHashProvider)); _persistedQueryStore = persistedQueryStore ?? throw new ArgumentNullException(nameof(persistedQueryStore)); @@ -67,7 +67,7 @@ await _persistedQueryStore.WriteQueryAsync(documentId, query) { _persisted, true } }); - context.ContextData[ContextDataKeys.DocumentSaved] = true; + context.ContextData[WellKnownContextData.DocumentSaved] = true; } else { diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/QueryRequestBuilderTests.cs b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/QueryRequestBuilderTests.cs index e57854cb97a..cda62bef366 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/QueryRequestBuilderTests.cs +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/QueryRequestBuilderTests.cs @@ -60,14 +60,14 @@ public void SetQuery_NullOrEmpty_ArgumentException(string query) { // arrange // act - Action action = () => + void Action() => QueryRequestBuilder.New() .SetQuery(query) .Create(); // assert - Assert.Equal("querySource", - Assert.Throws(action).ParamName); + Assert.Equal("sourceText", + Assert.Throws(Action).ParamName); } [Fact] diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryDocIsSet_RequestHasOnlyQuery.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryDocIsSet_RequestHasOnlyQuery.snap index 4ef45ab46a3..3b8eb64c51f 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryDocIsSet_RequestHasOnlyQuery.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryDocIsSet_RequestHasOnlyQuery.snap @@ -66,5 +66,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap index 6a96db8d93e..dcb2538bd3e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_OnlyQueryIsSet_RequestHasOnlyQuery.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddExtension_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddExtension_RequestIsCreated.snap index db9cbc7b49f..82fd7c4f073 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddExtension_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddExtension_RequestIsCreated.snap @@ -12,5 +12,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap index 24829c0757d..18bd9394592 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddProperties_RequestIsCreated.snap @@ -12,5 +12,6 @@ "two": "bar" }, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap index b418568e939..e8f483edf48 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndAddVariables_RequestIsCreated.snap @@ -12,5 +12,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap index 61a7bf26d50..1d7f66041d2 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndInitialValue_RequestIsCreated.snap @@ -11,5 +11,6 @@ "Services": null, "InitialValue": { "a": "123" - } + }, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap index 321a55ebb40..2c73f6214f7 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndOperation_RequestIsCreated.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap index 6a96db8d93e..dcb2538bd3e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetOperation_RequestIsCreated.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap index 6a96db8d93e..dcb2538bd3e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetProperties_RequestIsCreated.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap index 6a96db8d93e..dcb2538bd3e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndResetVariables_RequestIsCreated.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap index 45aec7f0b6b..20583f05aed 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndServices_RequestIsCreated.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": {}, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtension_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtension_RequestIsCreated.snap index c0f10a572d7..d6600f60ed2 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtension_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtension_RequestIsCreated.snap @@ -11,5 +11,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_1.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_1.snap index 30dc6b5682d..37818b68615 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_1.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_1.snap @@ -11,5 +11,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_2.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_2.snap index 6a96db8d93e..dcb2538bd3e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_2.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_2.snap @@ -9,5 +9,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_3.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_3.snap index e7e5c88761f..358ab324c1b 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_3.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_3.snap @@ -12,5 +12,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_4.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_4.snap index 30dc6b5682d..37818b68615 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_4.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_4.snap @@ -11,5 +11,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_5.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_5.snap index e7e5c88761f..358ab324c1b 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_5.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetExtensions_RequestIsCreated_5.snap @@ -12,5 +12,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewProperty_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewProperty_RequestIsCreated.snap index 64268f76e85..f6ed2cf3a1e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewProperty_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewProperty_RequestIsCreated.snap @@ -11,5 +11,6 @@ "one": "bar" }, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewVariable_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewVariable_RequestIsCreated.snap index c7ab0684215..a6d60dc87e7 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewVariable_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetNewVariable_RequestIsCreated.snap @@ -11,5 +11,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap index 66667d6b6be..a0c3e59c9b4 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperties_RequestIsCreated.snap @@ -11,5 +11,6 @@ "three": "baz" }, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperty_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperty_RequestIsCreated.snap index 64268f76e85..f6ed2cf3a1e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperty_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetProperty_RequestIsCreated.snap @@ -11,5 +11,6 @@ "one": "bar" }, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariable_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariable_RequestIsCreated.snap index c7ab0684215..a6d60dc87e7 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariable_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariable_RequestIsCreated.snap @@ -11,5 +11,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap index 3800218ea0c..6ecffdd5d2b 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndSetVariables_RequestIsCreated.snap @@ -11,5 +11,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsNotSet.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsNotSet.snap index c91a4eb24d1..3c6908914f2 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsNotSet.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsNotSet.snap @@ -11,5 +11,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsSet.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsSet.snap index c0f10a572d7..d6600f60ed2 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsSet.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddExtension_ExtensionIsSet.snap @@ -11,5 +11,6 @@ }, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsNotSet.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsNotSet.snap index 78ada532370..369c556591d 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsNotSet.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsNotSet.snap @@ -11,5 +11,6 @@ "one": "foo" }, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsSet.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsSet.snap index 64268f76e85..f6ed2cf3a1e 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsSet.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddProperties_PropertyIsSet.snap @@ -11,5 +11,6 @@ "one": "bar" }, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsNotSet.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsNotSet.snap index 99730c0ab40..0332839f173 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsNotSet.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsNotSet.snap @@ -11,5 +11,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsSet.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsSet.snap index c7ab0684215..a6d60dc87e7 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsSet.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_QueryAndTryAddVariable_VariableIsSet.snap @@ -11,5 +11,6 @@ "Extensions": null, "ContextData": null, "Services": null, - "InitialValue": null + "InitialValue": null, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap index 9960d7168b9..03fdebeb6bc 100644 --- a/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap +++ b/src/HotChocolate/Core/test/Abstractions.Tests/Execution/__snapshots__/QueryRequestBuilderTests.BuildRequest_SetAll_RequestIsCreated.snap @@ -15,5 +15,6 @@ "Services": {}, "InitialValue": { "a": "456" - } + }, + "AllowedOperations": null } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Instrumentation/ApolloTracingTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Instrumentation/ApolloTracingTests.cs index 07fb3654e5c..ea73c13c957 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Instrumentation/ApolloTracingTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Instrumentation/ApolloTracingTests.cs @@ -90,7 +90,7 @@ public async Task ApolloTracing_OnDemand_WithHeader() IExecutionResult result = await executor.ExecuteAsync( QueryRequestBuilder.New() .SetQuery("{ a }") - .SetProperty(ContextDataKeys.EnableTracing, true) + .SetProperty(WellKnownContextData.EnableTracing, true) .Create()); // assert diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonLineStringTypeTests.LineString_Execution_With_Fragments.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonLineStringTypeTests.LineString_Execution_With_Fragments.snap index a33ce108335..a89bb4e6616 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonLineStringTypeTests.LineString_Execution_With_Fragments.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonLineStringTypeTests.LineString_Execution_With_Fragments.snap @@ -26,7 +26,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiLineStringTypeTests.MultiLineString_Execution_With_Fragments.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiLineStringTypeTests.MultiLineString_Execution_With_Fragments.snap index a4b88e66c6c..608f56ba7a3 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiLineStringTypeTests.MultiLineString_Execution_With_Fragments.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiLineStringTypeTests.MultiLineString_Execution_With_Fragments.snap @@ -26,7 +26,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPointTypeTests.MultiPoint_Execution_With_Fragments.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPointTypeTests.MultiPoint_Execution_With_Fragments.snap index f51b6dba073..4682164e634 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPointTypeTests.MultiPoint_Execution_With_Fragments.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPointTypeTests.MultiPoint_Execution_With_Fragments.snap @@ -26,7 +26,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonInputTests.Execution_Tests.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonInputTests.Execution_Tests.snap index b7d3f54f056..6c0a43c8e16 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonInputTests.Execution_Tests.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonInputTests.Execution_Tests.snap @@ -28,7 +28,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonTypeTests.MultiPolygon_Execution_With_Fragments.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonTypeTests.MultiPolygon_Execution_With_Fragments.snap index 90a0619d137..7ffa9a106ae 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonTypeTests.MultiPolygon_Execution_With_Fragments.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonMultiPolygonTypeTests.MultiPolygon_Execution_With_Fragments.snap @@ -26,7 +26,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPointTypeTests.Point_Execution_With_Fragments.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPointTypeTests.Point_Execution_With_Fragments.snap index f57861eedde..c8a2079bca6 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPointTypeTests.Point_Execution_With_Fragments.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPointTypeTests.Point_Execution_With_Fragments.snap @@ -26,7 +26,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPolygonTypeTests.Polygon_Execution_With_Fragments.snap b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPolygonTypeTests.Polygon_Execution_With_Fragments.snap index ce66d88f806..e69964caeb9 100644 --- a/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPolygonTypeTests.Polygon_Execution_With_Fragments.snap +++ b/src/HotChocolate/Spatial/test/Types.Tests/__snapshots__/GeoJsonPolygonTypeTests.Polygon_Execution_With_Fragments.snap @@ -26,7 +26,7 @@ ], "Extensions": null, "ContextData": { - "ValidationErrors": true + "HotChocolate.Execution.ValidationErrors": true }, "HasNext": null } diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs index a15f8f4389f..0878da82ddd 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedRedisSchemaTests.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using ChilliCream.Testing; +using HotChocolate.AspNetCore.Serialization; using HotChocolate.AspNetCore.Utilities; using HotChocolate.Execution; using HotChocolate.Stitching.Schemas.Accounts; @@ -228,7 +229,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddAccountsSchema() .InitializeOnStartup() @@ -253,7 +254,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddInventorySchema() .InitializeOnStartup() @@ -278,7 +279,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddProductsSchema() .InitializeOnStartup() @@ -303,7 +304,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddReviewSchema() .InitializeOnStartup() diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs index 7c2a455b6f3..0cb82431fa5 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaErrorTests.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using HotChocolate.AspNetCore.Serialization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -125,7 +126,7 @@ public async Task Execute_Ok_StatusCode_With_Error_On_DownStream_Request() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddAccountsSchema() .PublishSchemaDefinition(c => c @@ -148,7 +149,7 @@ public async Task Execute_Ok_StatusCode_With_Error_On_DownStream_Request() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddInventorySchema() .PublishSchemaDefinition(c => c @@ -170,7 +171,7 @@ public async Task Execute_Ok_StatusCode_With_Error_On_DownStream_Request() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddProductsSchema() .AddTypeExtension(new ObjectTypeExtension(d => @@ -213,7 +214,7 @@ public async Task Execute_Ok_StatusCode_With_Error_On_DownStream_Request() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddReviewSchema() .PublishSchemaDefinition(c => c diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs index 4a5dd51bab7..d8c837de36f 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using HotChocolate.AspNetCore.Serialization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -129,7 +130,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddAccountsSchema() .PublishSchemaDefinition(c => c @@ -152,7 +153,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddInventorySchema() .PublishSchemaDefinition(c => c @@ -174,7 +175,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddProductsSchema() .PublishSchemaDefinition(c => c @@ -197,7 +198,7 @@ public async Task AutoMerge_AddLocal_Field_Execute() Context.ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddReviewSchema() .PublishSchemaDefinition(c => c diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs index a76c4b811b4..b3201faf7ca 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/StitchingTestContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using HotChocolate.AspNetCore.Serialization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -23,7 +24,7 @@ public class StitchingTestContext ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddCustomerSchema(), app => app @@ -35,7 +36,7 @@ public class StitchingTestContext ServerFactory.Create( services => services .AddRouting() - .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddHttpResultSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() .AddContractSchema(), app => app diff --git a/website/src/docs/docs.json b/website/src/docs/docs.json index 36f363453eb..5465bc8988b 100644 --- a/website/src/docs/docs.json +++ b/website/src/docs/docs.json @@ -321,6 +321,10 @@ { "path": "visitors", "title": "Visitors" + }, + { + "path": "aspnetcore", + "title": "ASP.NET Core" } ] } diff --git a/website/src/docs/hotchocolate/api-reference/aspnetcore.md b/website/src/docs/hotchocolate/api-reference/aspnetcore.md new file mode 100644 index 00000000000..3c7ff83139d --- /dev/null +++ b/website/src/docs/hotchocolate/api-reference/aspnetcore.md @@ -0,0 +1,491 @@ +--- +title: ASP.NET Core +--- + +Hot Chocolate comes with integration to the ASP.NET Core endpoints API. The middleware implementation follows the current GraphQL over HTTP Spec. + +```csharp +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) +{ + app.UseRouting() + + app.UseEndpoints(endpoints => + { + endpoints.MapGraphQL(); + }); +} +``` + +# GraphQL over HTTP Spec + +The following GraphQL requests follow the current GraphQL over HTTP spec draft. + +If no path is specified, the GraphQL middleware will follow the spec recommendation to map the endpoint to `/graphql`. + +`http://example.com/graphql` + +`http://product.example.com/graphql` + +`http://example.com/product/graphql` + +## GraphQL HTTP POST requests + +The GraphQL HTTP POST request is the most commonly used variant for GraphQL requests over HTTP and is specified [here](https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#post). + +**request:** + +``` +POST /graphql +HOST: foo.example +Content-Type: application/json + +{ + "query": "query($id: ID!){user(id:$id){name}}", + "variables": { "id": "QVBJcy5ndXJ1" } +} +``` + +**response:** + +``` +Content-Type: application/json + +{ + "data": { + "user": { + "name": "Jon Doe" + } + } +} +``` + +## GraphQL HTTP GET request + +GraphQL can also be served through an HTTP GET request. You have the same options as the HTTP POST request, just that the request properties are provided as query parameters. GraphQL HTTP GET requests can be a good choice if you are looking to cache GraphQL requests. + +For example, if we wanted to execute the following GraphQL query: + +```graphql +query($id: ID!) { + user(id: $id) { + name + } +} +``` + +With the following query variables: + +```json +{ + "id": "QVBJcy5ndXJ1" +} +``` + +This request could be sent via an HTTP GET as follows: + +**request:** + +``` +GET /graphql?query=query(%24id%3A%20ID!)%7Buser(id%3A%24id)%7Bname%7D%7D&variables=%7B%22id%22%3A%22QVBJcy5ndXJ1%22%7D` +HOST: foo.example +``` + +**response:** + +``` +Content-Type: application/json + +{ + "data": { + "user": { + "name": "Jon Doe" + } + } +} +``` + +> Note: {query} and {operationName} parameters are encoded as raw strings in the query component. Therefore if the query string contained operationName=null then it should be interpreted as the {operationName} being the string "null". If a literal null is desired, the parameter (e.g. {operationName}) should be omitted. + +The GraphQL HTTP GET request is specified [here](https://github.com/graphql/graphql-over-http/blob/master/spec/GraphQLOverHTTP.md#get). + +By default, Hot Chocolate will only serve query operations when HTTP GET requests are used. You can change this default by specifying the GraphQL server options. + +```csharp +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) +{ + app.UseRouting() + + app.UseEndpoints(endpoints => + { + endpoints + .MapGraphQL() + .WithOptions(new GraphQLServerOptions + { + AllowedGetOperations = AllowedGetOperations.QueryAndMutation + }); + }); +} +``` + +You can also entirely deactivate HTTP GET request handling. + +```csharp +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) +{ + app.UseRouting() + + app.UseEndpoints(endpoints => + { + endpoints + .MapGraphQL() + .WithOptions(new GraphQLServerOptions + { + EnableGetRequests = false + }); + }); +} +``` + +## Incremental Delivery over HTTP + +The Hot Chocolate GraphQL server supports incremental delivery over HTTP, which essentially uses HTTP chunked transfer encoding combined with the [specification of multipart content defined by the W3 in rfc1341](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html). + +The incremental delivery is at the moment at the RFC stage and is specified [here](https://github.com/graphql/graphql-over-http/blob/master/rfcs/IncrementalDelivery.md). + +Incremental delivery is used with `@defer`, `@stream`, and with request batching. + +# Additional Requests + +Apart from the requests defined by the GraphQL over HTTP spec, Hot Chocolate allows you to batch requests, download the GraphQL SDL, and many more things. + +> Many of the request types stated in this section are on their way into the GraphQL over HTTP spec, and we will update this document as the spec, and its RFCs change. + +## GraphQL Schema request + +Although you can access and query the schema definition through introspection, we support fetching the GraphQL schema SDL as a file. The GraphQL schema SDL is richer with more information and easier to read. + +**request:** + +``` +GET /graphql?sdl +HOST: foo.example +``` + +**response:** + +``` +Content-Type: application/graphql + +type Query { + hello: String! +} +``` + +## GraphQL HTTP POST batching request + +We support two kinds of batching variants. + +The first variant to batch GraphQL requests is by sending in an array of GraphQL requests. Hot Chocolate will execute them in order. + +``` +POST /graphql +HOST: foo.example +Content-Type: application/json + +[ + { + # The query document. + "query": "query getHero { hero { name } }", + + # The name of the operation that shall be executed. + "operationName": "getHero", + + # A key under which a query document was saved on the server. + "id": "W5vrrAIypCbniaIYeroNnw==", + + # The variable values for this request. + "variables": { + "a": 1, + "b": "abc" + }, + + # Custom properties that can be passed to the execution engine context data. + "extensions": { + "a": 1, + "b": "abc" + } + }, + { + # The query document. + "query": "query getHero { hero { name } }", + + # The name of the operation that shall be executed. + "operationName": "getHero", + + # A key under which a query document was saved on the server. + "id": "W5vrrAIypCbniaIYeroNnw==", + + # The variable values for this request. + "variables": { + "a": 1, + "b": "abc" + }, + + # Custom properties that can be passed to the execution engine context data. + "extensions": { + "a": 1, + "b": "abc" + } + }, +] +``` + +The second GraphQL batching variant is called operation batching, where you send in one GraphQL request document with multiple operations. The operation execution order is then specified as a query param. + +``` +POST /graphql?batchOperations=[a,b] +HOST: foo.example +Content-Type: application/json + +{ + # The query document. + "query": "query a { hero { name } } query b { hero { name } }", + + # The name of the operation that shall be executed. + "operationName": "getHero", + + # A key under which a query document was saved on the server. + "id": "W5vrrAIypCbniaIYeroNnw==", + + # The variable values for this request. + "variables": { + "a": 1, + "b": "abc" + }, + + # Custom properties that can be passed to the execution engine context data. + "extensions": { + "a": 1, + "b": "abc" + } +} +``` + +By default, the GraphQL server will use the **incremental delivery over HTTP**specification to write the stream results as soon as they are available. This means that depending on your client implementation; you can start using the results as they appear in order. + +The serialization defaults can be changed like the following: + +```csharp +services.AddHttpResultSerializer( + batchSerialization: HttpResultSerialization.JsonArray, + deferSerialization: HttpResultSerialization.MultiPartChunked) +``` + +> More about batching can be found [here](batching). + +# Subscription Transport + +Subscriptions are by default delivered over WebSocket. We have implemented the [GraphQL over WebSocket Protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) specified by Apollo. + +## Alternative Transport Protocols + +With version 11.1, we will add alternative transport protocols like the [new proposal for the GraphQL over HTTP spec](https://github.com/graphql/graphql-over-http/pull/140). + +Moreover, we are working on allowing this protocol to be used over SignalR, which gives more flexibility to use subscriptions. + +# Tooling + +The Hot Chocolate GraphQL server comes right out of the gate with excellent tooling. By default, we are mapping our GraphQL IDE Banana Cake Pop to the GraphQL endpoint. This means you just need to open your browser and navigate to the configured endpoint to send requests to your server, explore your schema, or build-up tests. + +![GraphQL IDE](../../../images/get-started-bcp-query.png) + +The GraphQL IDE can be disabled by specifying tool options: + +```csharp +endpoints + .MapGraphQL() + .WithOptions( + new GraphQLServerOptions + { + Tool = { Enable = false } + })); +``` + +# Serialization + +The Hot Chocolate GraphQL server has abstracted the result serialization with the `IHttpResultSerializer` interface. The server uses the registered implementation to resolve the HTTP status code, the HTTP content type, and the serialized response from a GraphQL execution result. + +```csharp +/// +/// This interface specifies how a GraphQL result is serialized to a HTTP response. +/// +public interface IHttpResultSerializer +{ + /// + /// Gets the HTTP content type for the specified execution result. + /// + /// + /// The GraphQL execution result. + /// + /// + /// Returns a string representing the content type, + /// eg. "application/json; charset=utf-8". + /// + string GetContentType(IExecutionResult result); + + /// + /// Gets the HTTP status code for the specified execution result. + /// + /// + /// The GraphQL execution result. + /// + /// + /// Returns the HTTP status code, eg. . + /// + HttpStatusCode GetStatusCode(IExecutionResult result); + + /// + /// Serializes the specified execution result. + /// + /// + /// The GraphQL execution result. + /// + /// + /// The HTTP response stream. + /// + /// + /// The request cancellation token. + /// + ValueTask SerializeAsync( + IExecutionResult result, + Stream stream, + CancellationToken cancellationToken); +} +``` + +We have a default implementation (`DefaultHttpResultSerializer`) that can be used to built custom logic on top of the original implementation to make extensibility easier. By default, we are using `System.Text.Json` to serialize GraphQL execution results to JSON. + +A custom implementation of the result serializer is registered like the following: + +```csharp +services.AddHttpResultSerializer(); +``` + +If you, for instance, wanted to add some special error code handling when some error happened during execution, you could implement this like the following: + +```csharp +public class MyCustomHttpResultSerializer : DefaultHttpResultSerializer +{ + public override HttpStatusCode GetStatusCode(IExecutionResult result) + { + if (result is IQueryResult queryResult && + queryResult.Errors?.Count > 0 && + queryResult.Errors.Any(error => error.Code == "SOME_AUTH_ISSUE")) + { + return HttpStatusCode.Forbidden; + } + + return base.GetStatusCode(result); + } +} +``` + +# GraphQL request customization + +The GraphQL server allows you to customize how the GraphQL request is created. For this, you need to implement the `IHttpRequestInterceptor`. For convenience reasons, we provide a default implementation (`DefaultHttpRequestInterceptor`) that can be extended. + +```csharp +public class DefaultHttpRequestInterceptor : IHttpRequestInterceptor +{ + public virtual ValueTask OnCreateAsync( + HttpContext context, + IRequestExecutor requestExecutor, + IQueryRequestBuilder requestBuilder, + CancellationToken cancellationToken) + { + requestBuilder.TrySetServices(context.RequestServices); + requestBuilder.TryAddProperty(nameof(HttpContext), context); + requestBuilder.TryAddProperty(nameof(ClaimsPrincipal), context.User); + requestBuilder.TryAddProperty(nameof(CancellationToken), context.RequestAborted); + + if (context.IsTracingEnabled()) + { + requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true); + } + + return default; + } +} +``` + +Suppose you want to add more data to a GraphQL request; override the `OnCreateAsync` method, and add your custom data as a request property. These request properties are mapped to the request context data, which can be accessed in the field resolver or a field middleware through the `context`. + +```csharp +if(context.ContextData.ContainsKey(nameof(HttpContext))) +{ + // some logic +} +``` + +The context data can also be injected into resolver methods. + +```csharp +public string MyResolver([GlobalState(nameof(HttpContext))] HttpContext context) +{ + // some logic +} +``` + +A good practice is to inherit from the `GlobalStateAttribute` to create a custom typed attribute. + +```csharp +public string MyResolver([HttpContext] HttpContext context) +{ + // some logic +} +``` + +# Subscription session handling + +The Hot Chocolate GraphQL server allows you to interact with the server's socket session handling by implementing `ISocketSessionInterceptor`. For convenience reasons, we provide a default implementation (`DefaultSocketSessionInterceptor`) that can be extended. + +```csharp +public class DefaultSocketSessionInterceptor : ISocketSessionInterceptor +{ + public virtual ValueTask OnConnectAsync( + ISocketConnection connection, + InitializeConnectionMessage message, + CancellationToken cancellationToken) => + new ValueTask(ConnectionStatus.Accept()); + + public virtual ValueTask OnRequestAsync( + ISocketConnection connection, + IQueryRequestBuilder requestBuilder, + CancellationToken cancellationToken) + { + HttpContext context = connection.HttpContext; + requestBuilder.TrySetServices(connection.RequestServices); + requestBuilder.TryAddProperty(nameof(CancellationToken), connection.RequestAborted); + requestBuilder.TryAddProperty(nameof(HttpContext), context); + requestBuilder.TryAddProperty(nameof(ClaimsPrincipal), context.User); + + if (connection.HttpContext.IsTracingEnabled()) + { + requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true); + } + + return default; + } + + public virtual ValueTask OnCloseAsync( + ISocketConnection connection, + CancellationToken cancellationToken) => + default; +} +``` + +A custom socket session interceptor can be registered like the following: + +```csharp +services.AddSocketSessionInterceptor(); +```