From c7f30fbd5f79c69fb49be324d35f1dd8b8918f06 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 27 Nov 2020 12:30:47 +0100 Subject: [PATCH] Added more type extension overloads for schema stitching. (#2684) --- ...olateStitchingRequestExecutorExtensions.cs | 67 +++++++++++++++++++ .../Stitching/src/Stitching/ThrowHelper.cs | 23 +++++-- ...StitchingRequestExecutorExtensionsTests.cs | 42 +++++++++++- .../HotChocolate.Stitching.Tests.csproj | 7 ++ .../Stitching.Tests/Integration/BaseTests.cs | 51 ++++++++++++++ .../__resources__/DummyDirective.graphql | 1 + 6 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql diff --git a/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs index e905b522838..8d96df9e0cf 100644 --- a/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs +++ b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -20,6 +21,7 @@ using HotChocolate.Stitching.Utilities; using HotChocolate.Utilities; using HotChocolate.Utilities.Introspection; +using ThrowHelper = HotChocolate.Stitching.ThrowHelper; namespace Microsoft.Extensions.DependencyInjection { @@ -571,6 +573,71 @@ await loadSchema(services, cancellationToken) }); } + /// + /// Adds a schema SDL that contains type extensions. + /// Extension documents can be used to extend merged types + /// or even replace them. + /// + /// + /// The . + /// + /// + /// The assembly from which the type extension file shall be resolved. + /// + /// + /// The resource key of the type extension file + /// + /// + /// Returns the . + /// + /// + /// is null, or + /// is null. + /// is null. + /// + public static IRequestExecutorBuilder AddTypeExtensionsFromResource( + this IRequestExecutorBuilder builder, + Assembly assembly, + string key) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (assembly is null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + + return builder.ConfigureSchemaAsync( + async (s, ct) => + { + Stream? stream = assembly.GetManifestResourceStream(key); + + if (stream is null) + { + throw ThrowHelper.RequestExecutorBuilder_ResourceNotFound(key); + } + +#if NET5_0 + await using (stream) +#else + using (stream) +#endif + { + var buffer = new byte[stream.Length]; + await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); + s.AddTypeExtensions(Utf8GraphQLParser.Parse(buffer)); + } + }); + } + /// /// Add a document rewriter that is executed on /// the merged schema document. diff --git a/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs b/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs index 6b27b2f043b..74839b69838 100644 --- a/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs +++ b/src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs @@ -10,13 +10,13 @@ internal static class ThrowHelper { public static InvalidOperationException BufferedRequest_VariableDoesNotExist( string name) => - new InvalidOperationException(string.Format( + new(string.Format( ThrowHelper_BufferedRequest_VariableDoesNotExist, name)); public static InvalidOperationException BufferedRequest_OperationNotFound( DocumentNode document) => - new InvalidOperationException(string.Format( + new(string.Format( ThrowHelper_BufferedRequest_OperationNotFound, document)); @@ -24,7 +24,7 @@ internal static class ThrowHelper string variableName, FieldNode fieldSelection, Path path) => - new GraphQLException(ErrorBuilder.New() + new(ErrorBuilder.New() .SetMessage( StitchingResources.ArgumentScopedVariableResolver_InvalidArgumentName, variableName) @@ -37,7 +37,7 @@ internal static class ThrowHelper string variableName, FieldNode fieldSelection, Path path) => - new GraphQLException(ErrorBuilder.New() + new(ErrorBuilder.New() .SetMessage( StitchingResources.FieldScopedVariableResolver_InvalidFieldName, variableName) @@ -50,7 +50,7 @@ internal static class ThrowHelper string scopeName, FieldNode fieldSelection, Path path) => - new GraphQLException(ErrorBuilder.New() + new(ErrorBuilder.New() .SetMessage( StitchingResources.RootScopedVariableResolver_ScopeNotSupported, scopeName) @@ -61,7 +61,7 @@ internal static class ThrowHelper public static SchemaException PublishSchemaDefinitionDescriptor_ResourceNotFound( string key) => - new SchemaException( + new( SchemaErrorBuilder.New() .SetMessage( "The resource `{0}` was not found!", @@ -70,10 +70,19 @@ internal static class ThrowHelper public static SchemaException IntrospectionHelper_UnableToFetchSchemaDefinition( IReadOnlyList errors) => - new SchemaException( + new( SchemaErrorBuilder.New() .SetMessage("Unable to fetch schema definition.") .SetExtension("errors", errors) .Build()); + + public static SchemaException RequestExecutorBuilder_ResourceNotFound( + string key) => + new( + SchemaErrorBuilder.New() + .SetMessage( + "The resource `{0}` was not found!", + key) + .Build()); } } diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs index 9c65c103f26..2c21faa0a2b 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Configuration/HotChocolateStitchingRequestExecutorExtensionsTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using HotChocolate.Configuration; using HotChocolate.Execution; @@ -34,6 +35,45 @@ public async Task RewriteType() Assert.Equal("OriginalType1", lookup[("NewType1", "Schema1")]); Assert.Equal("OriginalType2", lookup[("NewType2", "Schema2")]); } + + [Fact] + public void AddTypeExtensionsFromResource_Builder_Is_Null() + { + // arrange + // act + void Configure() => + HotChocolateStitchingRequestExecutorExtensions + .AddTypeExtensionsFromResource(null!, GetType().Assembly, "abc"); + + // assert + Assert.Throws(Configure); + } + + [Fact] + public void AddTypeExtensionsFromResource_Assembly_Is_Null() + { + // arrange + // act + void Configure() => + new ServiceCollection().AddGraphQL() + .AddTypeExtensionsFromResource(null!, "abc"); + + // assert + Assert.Throws(Configure); + } + + [Fact] + public void AddTypeExtensionsFromResource_Key_Is_Null() + { + // arrange + // act + void Configure() => + new ServiceCollection().AddGraphQL() + .AddTypeExtensionsFromResource(GetType().Assembly, null!); + + // assert + Assert.Throws(Configure); + } } public class CustomQueryType : ObjectType diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj b/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj index b6852bda4ec..2dbc0a4eae7 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/HotChocolate.Stitching.Tests.csproj @@ -28,4 +28,11 @@ + + + + Always + + + diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs index f92a4cdef67..c27ea80779a 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/BaseTests.cs @@ -693,5 +693,56 @@ public async Task Add_Dummy_Directive() // assert Assert.NotNull(schema.GetDirectiveType("foo")); } + + [Fact] + public async Task Add_Dummy_Directive_From_Resource() + { + // arrange + IHttpClientFactory httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + // act + ISchema schema = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromResource( + GetType().Assembly, + "HotChocolate.Stitching.__resources__.DummyDirective.graphql") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildSchemaAsync(); + + // assert + Assert.NotNull(schema.GetDirectiveType("foo")); + } + + [Fact] + public async Task Add_Dummy_Directive_From_Resource_Key_Does_Not_Exist() + { + // arrange + IHttpClientFactory httpClientFactory = + Context.CreateDefaultRemoteSchemas(); + + // act + async Task Configure() => + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddRemoteSchema(Context.ContractSchema) + .AddRemoteSchema(Context.CustomerSchema) + .AddTypeExtensionsFromResource( + GetType().Assembly, + "HotChocolate.Stitching.__resources__.abc") + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .BuildSchemaAsync(); + + // assert + SchemaException exception = await Assert.ThrowsAsync(Configure); + Assert.Contains( + "The resource `HotChocolate.Stitching.__resources__.abc` was not found!", + exception.Message); + } } } diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql new file mode 100644 index 00000000000..11be1da2a6a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/__resources__/DummyDirective.graphql @@ -0,0 +1 @@ +directive @foo on FIELD_DEFINITION