diff --git a/src/HotChocolate/Core/src/Execution/Configuration/DefaultRequestExecutorOptionsMonitor.cs b/src/HotChocolate/Core/src/Execution/Configuration/DefaultRequestExecutorOptionsMonitor.cs index 4623a3fd3eb..78bbe0b27f6 100644 --- a/src/HotChocolate/Core/src/Execution/Configuration/DefaultRequestExecutorOptionsMonitor.cs +++ b/src/HotChocolate/Core/src/Execution/Configuration/DefaultRequestExecutorOptionsMonitor.cs @@ -59,7 +59,7 @@ private async ValueTask InitializeAsync(CancellationToken cancellationToken) { _disposables.Add(provider.OnChange(OnChange)); - IEnumerable allOptions = + IEnumerable allOptions = await provider.GetOptionsAsync(cancellationToken) .ConfigureAwait(false); diff --git a/src/HotChocolate/Core/src/Execution/Configuration/INamedRequestExecutorFactoryOptions.cs b/src/HotChocolate/Core/src/Execution/Configuration/INamedRequestExecutorFactoryOptions.cs new file mode 100644 index 00000000000..702e7f65b78 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Configuration/INamedRequestExecutorFactoryOptions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Options; + +namespace HotChocolate.Execution.Configuration +{ + /// + /// Represents something that configures the . + /// + public interface INamedRequestExecutorFactoryOptions + : IConfigureOptions + { + /// + /// The schema name to which this instance provides configurations to. + /// + NameString SchemaName { get; } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Configuration/IRequestExecutorOptionsProvider.cs b/src/HotChocolate/Core/src/Execution/Configuration/IRequestExecutorOptionsProvider.cs new file mode 100644 index 00000000000..d3e4baa33e6 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Configuration/IRequestExecutorOptionsProvider.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace HotChocolate.Execution.Configuration +{ + /// + /// Provides dynamic configurations. + /// + public interface IRequestExecutorOptionsProvider + { + /// + /// Gets named configuration options. + /// + /// + /// The . + /// + /// + /// Returns the configuration options of this provider. + /// + ValueTask> GetOptionsAsync( + CancellationToken cancellationToken); + + /// + /// Registers a listener to be called whenever a named + /// changes. + /// + /// + /// The action to be invoked when has changed. + /// + /// + /// An which should be disposed to stop listening for changes. + /// + IDisposable OnChange(Action listener); + } +} diff --git a/src/HotChocolate/Core/src/Execution/Configuration/NamedRequestExecutorFactoryOptions.cs b/src/HotChocolate/Core/src/Execution/Configuration/NamedRequestExecutorFactoryOptions.cs new file mode 100644 index 00000000000..03a93d2b5d3 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/Configuration/NamedRequestExecutorFactoryOptions.cs @@ -0,0 +1,45 @@ +using System; + +namespace HotChocolate.Execution.Configuration +{ + public sealed class NamedRequestExecutorFactoryOptions + : INamedRequestExecutorFactoryOptions + { + private readonly Action _configure; + + public NamedRequestExecutorFactoryOptions( + NameString schemaName, + Action configure) + { + SchemaName = schemaName.EnsureNotEmpty(nameof(schemaName)); + _configure = configure ?? throw new ArgumentNullException(nameof(configure)); + } + + /* + public NamedRequestExecutorFactoryOptions( + NameString schemaName, + RequestExecutorFactoryOptions options) + { + SchemaName = schemaName.EnsureNotEmpty(nameof(schemaName)); + _configure = o => + { + options.Pipeline + + + } + } + */ + + public NameString SchemaName { get; } + + public void Configure(RequestExecutorFactoryOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + _configure(options); + } + } +} diff --git a/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorFactoryOptions.cs b/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorFactoryOptions.cs index 09104e2bfa2..cdb45ddc064 100644 --- a/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorFactoryOptions.cs +++ b/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorFactoryOptions.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using HotChocolate.Execution.Options; -using Microsoft.Extensions.Options; namespace HotChocolate.Execution.Configuration { @@ -34,72 +31,4 @@ public class RequestExecutorFactoryOptions public IList OnRequestExecutorEvicted { get; } = new List(); } - - /// - /// Provides dynamic configurations. - /// - public interface IRequestExecutorOptionsProvider - { - /// - /// Gets named configuration options. - /// - /// - /// The . - /// - /// - /// Returns the configuration options of this provider. - /// - ValueTask> GetOptionsAsync( - CancellationToken cancellationToken); - - /// - /// Registers a listener to be called whenever a named - /// changes. - /// - /// - /// The action to be invoked when has changed. - /// - /// - /// An which should be disposed to stop listening for changes. - /// - IDisposable OnChange(Action listener); - } - - /// - /// Represents something that configures the . - /// - public interface INamedRequestExecutorFactoryOptions - : IConfigureOptions - { - /// - /// The schema name to which this instance provides configurations to. - /// - NameString SchemaName { get; } - } - - public sealed class NamedRequestExecutorFactoryOptions - : INamedRequestExecutorFactoryOptions - { - private readonly Action _configure; - - public NamedRequestExecutorFactoryOptions( - NameString schemaName, - Action configure) - { - SchemaName = schemaName.EnsureNotEmpty(nameof(schemaName)); - _configure = configure ?? throw new ArgumentNullException(nameof(configure)); - } - - public NameString SchemaName { get; } - - public void Configure(RequestExecutorFactoryOptions options) - { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - _configure(options); - } - } } diff --git a/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs b/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs index 2dbd803da18..c80c82abb41 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs @@ -52,7 +52,7 @@ public interface ITypeCompletionContext /// /// /// The type could not be resolved for the given . - /// + /// T GetType(ITypeReference typeRef) where T : IType; IEnumerable GetTypes() where T : IType; diff --git a/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitor.cs b/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitor.cs index 9577108149c..a2ec964a618 100644 --- a/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitor.cs @@ -52,8 +52,7 @@ public SchemaSyntaxVisitor(IBindingLookup bindingLookup) public string Description { get; private set; } - public IReadOnlyCollection Directives - { get; private set; } + public IReadOnlyCollection Directives { get; private set; } public IReadOnlyList Types => _types; @@ -61,7 +60,7 @@ public IReadOnlyCollection Directives ObjectTypeDefinitionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _objectTypeFactory.Create(_bindingLookup, node))); } @@ -69,7 +68,7 @@ public IReadOnlyCollection Directives ObjectTypeExtensionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _objectTypeExtensionFactory.Create(_bindingLookup, node))); } @@ -77,7 +76,7 @@ public IReadOnlyCollection Directives InterfaceTypeDefinitionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _interfaceTypeFactory.Create(_bindingLookup, node))); } @@ -85,7 +84,7 @@ public IReadOnlyCollection Directives InterfaceTypeExtensionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _interfaceTypeExtensionFactory.Create(_bindingLookup, node))); } @@ -93,7 +92,7 @@ public IReadOnlyCollection Directives UnionTypeDefinitionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _unionTypeFactory.Create(_bindingLookup, node))); } @@ -101,7 +100,7 @@ public IReadOnlyCollection Directives UnionTypeExtensionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _unionTypeExtensionFactory.Create(_bindingLookup, node))); } @@ -109,7 +108,7 @@ public IReadOnlyCollection Directives InputObjectTypeDefinitionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _inputObjectTypeFactory.Create(_bindingLookup, node))); } @@ -117,7 +116,7 @@ public IReadOnlyCollection Directives InputObjectTypeExtensionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _inputObjectTypeExtensionFactory.Create(_bindingLookup, node))); } @@ -125,7 +124,7 @@ public IReadOnlyCollection Directives EnumTypeDefinitionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _enumTypeFactory.Create(_bindingLookup, node))); } @@ -133,7 +132,7 @@ public IReadOnlyCollection Directives EnumTypeExtensionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _enumTypeExtensionFactory.Create(_bindingLookup, node))); } @@ -141,7 +140,7 @@ public IReadOnlyCollection Directives DirectiveDefinitionNode node, object context) { - _types.Add(SchemaTypeReference.Create( + _types.Add(TypeReference.Create( _directiveTypeFactory.Create(_bindingLookup, node))); } diff --git a/src/HotChocolate/Core/src/Types/Types/ObjectField.cs b/src/HotChocolate/Core/src/Types/Types/ObjectField.cs index 6869b008e45..fdc699863cc 100644 --- a/src/HotChocolate/Core/src/Types/Types/ObjectField.cs +++ b/src/HotChocolate/Core/src/Types/Types/ObjectField.cs @@ -122,7 +122,7 @@ internal ObjectField(ObjectFieldDefinition definition, bool sortArgumentsByName ObjectFieldDefinition definition) { var isIntrospectionField = IsIntrospectionField - || DeclaringType.IsIntrospectionType(); + || DeclaringType.IsIntrospectionType(); Resolver = definition.Resolver!; diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/ObjectTypeExtensionTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/ObjectTypeExtensionTests.cs index bb1da85d3ff..fd1575b9e5e 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/ObjectTypeExtensionTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/ObjectTypeExtensionTests.cs @@ -506,12 +506,31 @@ public void ObjectTypeExtension_RepeatableDirectiveOnArgument() // assert ObjectType type = schema.GetType("Foo"); - int count = type.Fields["name"].Arguments["a"] + var count = type.Fields["name"].Arguments["a"] .Directives["dummy_rep"] .Count(); Assert.Equal(2, count); } + [Fact] + public void ObjectTypeExtension_SetDirectiveOnArgument_Sdl_First() + { + // arrange + // act + ISchema schema = SchemaBuilder.New() + .AddQueryType() + .AddDocumentFromString( + @"extend type Foo { + name(a: String @dummy): String + }") + .AddDirectiveType() + .Create(); + + // assert + ObjectType type = schema.GetType("Foo"); + Assert.True(type.Fields["name"].Arguments["a"].Directives.Contains("dummy")); + } + public class FooType : ObjectType { diff --git a/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/Extensions/HotChocolateRedisPersistedQueriesServiceCollectionExtensions.cs b/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/Extensions/HotChocolateRedisPersistedQueriesServiceCollectionExtensions.cs index ec3eb966fb5..9f618ede3c4 100644 --- a/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/Extensions/HotChocolateRedisPersistedQueriesServiceCollectionExtensions.cs +++ b/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/Extensions/HotChocolateRedisPersistedQueriesServiceCollectionExtensions.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using StackExchange.Redis; using HotChocolate.Execution; -using HotChocolate.PersistedQueries.FileSystem; +using HotChocolate.PersistedQueries.Redis; namespace HotChocolate { diff --git a/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/RedisQueryStorage.cs b/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/RedisQueryStorage.cs index ef513332f22..1220781266f 100644 --- a/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/RedisQueryStorage.cs +++ b/src/HotChocolate/PersistedQueries/src/PersistedQueries.Redis/RedisQueryStorage.cs @@ -5,7 +5,7 @@ using HotChocolate.Language; using StackExchange.Redis; -namespace HotChocolate.PersistedQueries.FileSystem +namespace HotChocolate.PersistedQueries.Redis { /// /// An implementation of @@ -45,13 +45,7 @@ public RedisQueryStorage(IDatabase database) string queryId) { var buffer = (byte[]?)await _database.StringGetAsync(queryId).ConfigureAwait(false); - - if (buffer is null) - { - return null; - } - - return new QueryDocument(Utf8GraphQLParser.Parse(buffer)); + return buffer is null ? null : new QueryDocument(Utf8GraphQLParser.Parse(buffer)); } /// diff --git a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/IntegrationTests.cs b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/IntegrationTests.cs index def58f6e490..597cc3682fe 100644 --- a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/IntegrationTests.cs +++ b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/IntegrationTests.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using HotChocolate.Types; using HotChocolate.Execution; -using HotChocolate.PersistedQueries.FileSystem; using Snapshooter.Xunit; using Squadron; using StackExchange.Redis; diff --git a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/RedisQueryStorageTests.cs b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/RedisQueryStorageTests.cs index 91374972968..5518090302f 100644 --- a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/RedisQueryStorageTests.cs +++ b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/RedisQueryStorageTests.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using HotChocolate.Execution; using HotChocolate.Language; -using HotChocolate.PersistedQueries.FileSystem; using Snapshooter; using Snapshooter.Xunit; using Squadron; diff --git a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddReadOnlyRedisQueryStorage_Services.snap b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddReadOnlyRedisQueryStorage_Services.snap index 07a8e128bb2..93b3b81a103 100644 --- a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddReadOnlyRedisQueryStorage_Services.snap +++ b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddReadOnlyRedisQueryStorage_Services.snap @@ -4,7 +4,7 @@ "Value": null }, { - "Key": "HotChocolate.PersistedQueries.FileSystem.RedisQueryStorage", + "Key": "HotChocolate.PersistedQueries.Redis.RedisQueryStorage", "Value": null } ] diff --git a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddRedisQueryStorage_Services.snap b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddRedisQueryStorage_Services.snap index c82b4a0a651..9a2e0384122 100644 --- a/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddRedisQueryStorage_Services.snap +++ b/src/HotChocolate/PersistedQueries/test/PersistedQueries.Redis.Tests/__snapshots__/ServiceCollectionExtensionsTests.AddRedisQueryStorage_Services.snap @@ -8,7 +8,7 @@ "Value": null }, { - "Key": "HotChocolate.PersistedQueries.FileSystem.RedisQueryStorage", + "Key": "HotChocolate.PersistedQueries.Redis.RedisQueryStorage", "Value": null } ] diff --git a/src/HotChocolate/Stitching/HotChocolate.Stitching.sln b/src/HotChocolate/Stitching/HotChocolate.Stitching.sln index 5e09ffd3ac0..4ef9543855a 100644 --- a/src/HotChocolate/Stitching/HotChocolate.Stitching.sln +++ b/src/HotChocolate/Stitching/HotChocolate.Stitching.sln @@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.AspNetCore.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Stitching.Abstractions", "src\Stitching.Abstractions\HotChocolate.Stitching.Abstractions.csproj", "{E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stitching.Redis", "src\Stitching.Redis\Stitching.Redis.csproj", "{5492C256-DD42-45DC-8515-F1281F30E99E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -249,6 +251,18 @@ Global {E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}.Release|x64.Build.0 = Release|Any CPU {E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}.Release|x86.ActiveCfg = Release|Any CPU {E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB}.Release|x86.Build.0 = Release|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x64.ActiveCfg = Debug|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x64.Build.0 = Debug|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x86.ActiveCfg = Debug|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Debug|x86.Build.0 = Debug|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Release|Any CPU.Build.0 = Release|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x64.ActiveCfg = Release|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x64.Build.0 = Release|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x86.ActiveCfg = Release|Any CPU + {5492C256-DD42-45DC-8515-F1281F30E99E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -270,6 +284,7 @@ Global {C1CF3B06-4A02-46A6-98B2-448B5DD4E7D3} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8} {709EE9F8-BA48-4C5A-8BDA-96B96689A1FC} = {D7A7C1D4-6239-4B4C-A80C-E953334A83F8} {E25716BB-CAD4-4FAA-BFFB-9D1F56E3B6AB} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3} + {5492C256-DD42-45DC-8515-F1281F30E99E} = {D530CEBF-33A3-4BCE-9887-A50F5AE789A3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FB1557E1-2F94-4540-93E5-B47698838B72} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/Class1.cs b/src/HotChocolate/Stitching/src/Stitching.Redis/Class1.cs new file mode 100644 index 00000000000..0805fc00a32 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/Class1.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using HotChocolate.Execution.Configuration; +using HotChocolate.Execution.Options; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Redis +{ + public class RedisExecutorOptionsProvider : IRequestExecutorOptionsProvider + { + private NameString _schemaName; + + public RedisExecutorOptionsProvider(NameString schemaName) + { + _schemaName = schemaName; + } + + public async ValueTask> GetOptionsAsync( + CancellationToken cancellationToken) + { + IEnumerable schemaDefinitions = + await GetSchemaDefinitionsAsync(cancellationToken) + .ConfigureAwait(false); + + var list = new List(); + var serviceCollection = new ServiceCollection(); + IRequestExecutorBuilder builder = serviceCollection.AddGraphQL(); + + foreach (RemoteSchemaDefinition schemaDefinition in schemaDefinitions) + { + builder.AddRemoteSchema( + schemaDefinition.Name, + (sp, ct) => new ValueTask(schemaDefinition)); + + IServiceProvider services = serviceCollection.BuildServiceProvider(); + IRequestExecutorOptionsMonitor optionsMonitor = + services.GetRequiredService(); + + RequestExecutorFactoryOptions options = + await optionsMonitor.GetAsync(schemaDefinition.Name, cancellationToken) + .ConfigureAwait(false); + // list.Add(new NamedRequestExecutorFactoryOptions(schemaDefinition.Name, options)); + } + + return list; + } + + public IDisposable OnChange(Action listener) + { + throw new NotImplementedException(); + } + + + private async ValueTask> GetSchemaDefinitionsAsync( + CancellationToken cancellationToken) => throw new NotImplementedException(); + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching.Redis/Stitching.Redis.csproj b/src/HotChocolate/Stitching/src/Stitching.Redis/Stitching.Redis.csproj new file mode 100644 index 00000000000..7d591b1ede4 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching.Redis/Stitching.Redis.csproj @@ -0,0 +1,21 @@ + + + + HotChocolate.Stitching.Redis + HotChocolate.Stitching.Redis + HotChocolate.Stitching.Redis + Contains the Hot Chocolate GraphQL schema stitching layer. + enable + + + + + + + + + + + + + diff --git a/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs index d5c0557cd46..b18dd86a6ab 100644 --- a/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs +++ b/src/HotChocolate/Stitching/src/Stitching/DependencyInjection/HotChocolateStitchingRequestExecutorExtensions.cs @@ -16,6 +16,7 @@ using HotChocolate.Stitching.Merge.Rewriters; using HotChocolate.Stitching.Pipeline; using HotChocolate.Stitching.Requests; +using HotChocolate.Stitching.SchemaDefinitions; using HotChocolate.Stitching.Utilities; using HotChocolate.Utilities; using HotChocolate.Utilities.Introspection; @@ -273,48 +274,62 @@ await loadSchema(services, cancellationToken) schemaBuilder.AddTypeExtensions(doc); } + schemaBuilder.AddTypeRewriter( + new RemoveFieldRewriter( + new FieldReference( + autoProxy.Schema.QueryType.Name, + SchemaDefinitionFieldNames.SchemaDefinitionField), + schemaName)); + + schemaBuilder.AddDocumentRewriter( + new RemoveTypeRewriter( + SchemaDefinitionType.Names.SchemaDefinition, + schemaName)); + foreach (var schemaAction in extensionsRewriter.SchemaActions) { switch (schemaAction.Name.Value) { case DirectiveNames.RemoveRootTypes: - builder.IgnoreRootTypes(schemaName); + schemaBuilder.AddDocumentRewriter( + new RemoveRootTypeRewriter(schemaName)); break; case DirectiveNames.RemoveType: - builder.IgnoreType( - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RemoveType_TypeName), - schemaName); + schemaBuilder.AddDocumentRewriter( + new RemoveTypeRewriter( + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RemoveType_TypeName), + schemaName)); break; case DirectiveNames.RenameType: - builder.RenameType( - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RenameType_TypeName), - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RenameType_NewTypeName), - schemaName); + schemaBuilder.AddTypeRewriter( + new RenameTypeRewriter( + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameType_TypeName), + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameType_NewTypeName), + schemaName)); break; case DirectiveNames.RenameField: - builder.RenameField( - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RenameField_TypeName), - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RenameField_TypeName), - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RenameField_FieldName), - GetArgumentValue( - schemaAction, - DirectiveFieldNames.RenameField_NewFieldName), - schemaName); + schemaBuilder.AddTypeRewriter( + new RenameFieldRewriter( + new FieldReference( + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameField_TypeName), + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameField_FieldName)), + GetArgumentValue( + schemaAction, + DirectiveFieldNames.RenameField_NewFieldName), + schemaName)); break; } } @@ -656,7 +671,7 @@ await loadSchema(services, cancellationToken) schemaName?.EnsureNotEmpty(nameof(schemaName)); return builder.AddDocumentRewriter( - new RemoveRootTypeRewriter()); + new RemoveRootTypeRewriter(schemaName)); } /// diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs new file mode 100644 index 00000000000..ce9e5856964 --- /dev/null +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.MergeContext.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using HotChocolate.Language; + +namespace HotChocolate.Stitching.Merge +{ + public partial class AddSchemaExtensionRewriter + { + public class MergeContext + { + public MergeContext(DocumentNode schema, DocumentNode extensions) + { + Extensions = extensions.Definitions + .OfType() + .ToDictionary(t => t.Name.Value); + + Directives = schema.Definitions + .OfType() + .ToDictionary(t => t.Name.Value); + } + + public IDictionary Extensions { get; } + + public IDictionary Directives { get; } + } + } +} diff --git a/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs index 41768fe0a1a..5baffa4bca0 100644 --- a/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs +++ b/src/HotChocolate/Stitching/src/Stitching/Merge/AddSchemaExtensionRewriter.cs @@ -8,24 +8,24 @@ namespace HotChocolate.Stitching.Merge { - public class AddSchemaExtensionRewriter + public partial class AddSchemaExtensionRewriter : SchemaSyntaxRewriter { - private readonly Dictionary _gloabalDirectives; + private readonly Dictionary _globalDirectives; public AddSchemaExtensionRewriter() { - _gloabalDirectives = new Dictionary(); + _globalDirectives = new Dictionary(); } - public AddSchemaExtensionRewriter(IEnumerable gloabalDirectives) + public AddSchemaExtensionRewriter(IEnumerable globalDirectives) { - if (gloabalDirectives is null) + if (globalDirectives is null) { - throw new ArgumentNullException(nameof(gloabalDirectives)); + throw new ArgumentNullException(nameof(globalDirectives)); } - _gloabalDirectives = gloabalDirectives.ToDictionary(t => t.Name.Value); + _globalDirectives = globalDirectives.ToDictionary(t => t.Name.Value); } public DocumentNode AddExtensions( @@ -42,19 +42,15 @@ public AddSchemaExtensionRewriter(IEnumerable gloabalDi throw new ArgumentNullException(nameof(extensions)); } - var newTypes = extensions.Definitions - .OfType().ToList(); - var newDirectives = extensions.Definitions - .OfType().ToList(); + var newTypes = extensions.Definitions.OfType().ToList(); + var newDirectives = extensions.Definitions.OfType().ToList(); DocumentNode current = schema; if (newTypes.Count > 0 || newDirectives.Count > 0) { - current = RemoveDirectives(current, - newDirectives.Select(t => t.Name.Value)); - current = RemoveTypes(current, - newTypes.Select(t => t.Name.Value)); + current = RemoveDirectives(current, newDirectives.Select(t => t.Name.Value)); + current = RemoveTypes(current, newTypes.Select(t => t.Name.Value)); var definitions = schema.Definitions.ToList(); definitions.AddRange(newTypes); @@ -64,6 +60,20 @@ public AddSchemaExtensionRewriter(IEnumerable gloabalDi var context = new MergeContext(current, extensions); current = RewriteDocument(current, context); + + if (context.Extensions.Count > 0) + { + var definitions = current.Definitions.ToList(); + + foreach (string notProcessed in context.Extensions.Keys.Except( + current.Definitions.OfType().Select(t => t.Name.Value))) + { + definitions.Add(context.Extensions[notProcessed]); + } + + return current.WithDefinitions(definitions); + } + return current; } @@ -131,8 +141,7 @@ public AddSchemaExtensionRewriter(IEnumerable gloabalDi extension, string.Format( CultureInfo.InvariantCulture, - StitchingResources - .AddSchemaExtensionRewriter_TypeMismatch, + StitchingResources.AddSchemaExtensionRewriter_TypeMismatch, node.Name.Value, node.Kind, extension.Kind)); @@ -151,8 +160,7 @@ public AddSchemaExtensionRewriter(IEnumerable gloabalDi return typeDefinition; } - var types = - new OrderedDictionary(); + var types = new OrderedDictionary(); foreach (NamedTypeNode type in typeDefinition.Types) { @@ -488,8 +496,8 @@ protected override InputObjectTypeDefinitionNode foreach (DirectiveNode directive in typeExtension.Directives) { - if (!_gloabalDirectives.TryGetValue(directive.Name.Value, - out DirectiveDefinitionNode directiveDefinition) + if (!_globalDirectives.TryGetValue(directive.Name.Value, + out DirectiveDefinitionNode? directiveDefinition) && !context.Directives.TryGetValue(directive.Name.Value, out directiveDefinition)) { @@ -517,23 +525,5 @@ protected override InputObjectTypeDefinitionNode return withDirectives.Invoke(directives); } - - public class MergeContext - { - public MergeContext(DocumentNode schema, DocumentNode extensions) - { - Extensions = extensions.Definitions - .OfType() - .ToDictionary(t => t.Name.Value); - - Directives = schema.Definitions - .OfType() - .ToDictionary(t => t.Name.Value); - } - - public IDictionary Extensions { get; } - - public IDictionary Directives { get; } - } } } diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs index 5641b689d63..126da45ea4e 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/FederatedSchemaTests.cs @@ -1,20 +1,28 @@ +using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using HotChocolate.AspNetCore.Utilities; using HotChocolate.Execution; +using HotChocolate.Stitching.Schemas.Accounts; +using HotChocolate.Stitching.Schemas.Inventory; +using HotChocolate.Stitching.Schemas.Products; +using HotChocolate.Stitching.Schemas.Reviews; +using HotChocolate.Types; using Snapshooter.Xunit; using Xunit; -using System.Collections.Generic; -using HotChocolate.AspNetCore.Utilities; -using HotChocolate.Stitching.Schemas.Contracts; -using HotChocolate.Stitching.Schemas.Customers; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.TestHost; namespace HotChocolate.Stitching.Integration { public class FederatedSchemaTests : IClassFixture { + public const string Accounts = "accounts"; + public const string Inventory = "inventory"; + public const string Products = "products"; + public const string Reviews = "reviews"; + public FederatedSchemaTests(StitchingTestContext context) { Context = context; @@ -33,42 +41,133 @@ public async Task AutoMerge_Schema() await new ServiceCollection() .AddSingleton(httpClientFactory) .AddGraphQL() - .AddRemoteSchema(Context.ContractSchema) - .AddRemoteSchema(Context.CustomerSchema) + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(Accounts) + .AddRemoteSchema(Inventory) + .AddRemoteSchema(Products) + .AddRemoteSchema(Reviews) .BuildSchemaAsync(); // assert schema.Print().MatchSnapshot(); } - public TestServer CreateCustomerService() => + [Fact] + public async Task AutoMerge_Execute() + { + // arrange + IHttpClientFactory httpClientFactory = CreateDefaultRemoteSchemas(); + + IRequestExecutor executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query")) + .AddRemoteSchema(Accounts) + .AddRemoteSchema(Inventory) + .AddRemoteSchema(Products) + .AddRemoteSchema(Reviews) + .BuildRequestExecutorAsync(); + + // act + IExecutionResult result = await executor.ExecuteAsync( + @"{ + me { + id + name + reviews { + body + product { + upc + } + } + } + }"); + + // assert + result.ToJson().MatchSnapshot(); + } + + [Fact] + public async Task AutoMerge_AddLocal_Field_Execute() + { + // arrange + IHttpClientFactory httpClientFactory = CreateDefaultRemoteSchemas(); + + IRequestExecutor executor = + await new ServiceCollection() + .AddSingleton(httpClientFactory) + .AddGraphQL() + .AddQueryType(d => d.Name("Query").Field("local").Resolve("I am local.")) + .AddRemoteSchema(Accounts) + .AddRemoteSchema(Inventory) + .AddRemoteSchema(Products) + .AddRemoteSchema(Reviews) + .BuildRequestExecutorAsync(); + + // act + IExecutionResult result = await executor.ExecuteAsync( + @"{ + me { + id + name + reviews { + body + product { + upc + } + } + } + local + }"); + + // assert + result.ToJson().MatchSnapshot(); + } + + public TestServer CreateAccountsService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddGraphQLServer() + .AddAccountsSchema(), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateInventoryService() => + Context.ServerFactory.Create( + services => services + .AddRouting() + .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) + .AddGraphQLServer() + .AddInventorySchema(), + app => app + .UseWebSockets() + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); + + public TestServer CreateProductsService() => Context.ServerFactory.Create( services => services .AddRouting() .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() - .AddCustomerSchema() - .PublishSchemaDefinition(c => c - .SetName(Context.CustomerSchema) - .AddTypeExtensionsFromString( - @"extend type Customer { - contracts: [Contract!] - @delegate(path: ""contracts(customerId:$fields:id)"") - }")), + .AddProductsSchema(), app => app .UseWebSockets() .UseRouting() .UseEndpoints(endpoints => endpoints.MapGraphQL("/"))); - public TestServer CreateContractService() => + public TestServer CreateReviewsService() => Context.ServerFactory.Create( services => services .AddRouting() .AddHttpRequestSerializer(HttpResultSerialization.JsonArray) .AddGraphQLServer() - .AddContractSchema() - .PublishSchemaDefinition(c => c - .SetName(Context.ContractSchema)), + .AddReviewSchema(), app => app .UseWebSockets() .UseRouting() @@ -78,8 +177,10 @@ public IHttpClientFactory CreateDefaultRemoteSchemas() { var connections = new Dictionary { - { Context.CustomerSchema, CreateCustomerService().CreateClient() }, - { Context.ContractSchema, CreateContractService().CreateClient() } + { Accounts, CreateAccountsService().CreateClient() }, + { Inventory, CreateInventoryService().CreateClient() }, + { Products, CreateProductsService().CreateClient() }, + { Reviews, CreateReviewsService().CreateClient() }, }; return StitchingTestContext.CreateRemoteSchemas(connections); diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap new file mode 100644 index 00000000000..eff8ca1d1f6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_AddLocal_Field_Execute.snap @@ -0,0 +1,23 @@ +{ + "data": { + "me": { + "id": 1, + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "product": { + "upc": 1 + } + }, + { + "body": "Too expensive.", + "product": { + "upc": 2 + } + } + ] + }, + "local": "I am local." + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap new file mode 100644 index 00000000000..c4678d52031 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Execute.snap @@ -0,0 +1,22 @@ +{ + "data": { + "me": { + "id": 1, + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "product": { + "upc": 1 + } + }, + { + "body": "Too expensive.", + "product": { + "upc": 2 + } + } + ] + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap index 16cadf4c0a5..20711e5b241 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Integration/__snapshots__/FederatedSchemaTests.AutoMerge_Schema.snap @@ -1,155 +1,42 @@ schema { query: Query - mutation: Mutation } -interface Contract @source(name: "Contract", schema: "contract") { - id: ID! - customerId: ID! +type InventoryInfo @source(name: "InventoryInfo", schema: "inventory") { + upc: Int! + isInStock: Boolean! } -"The node interface is implemented by entities that have a global unique identifier." -interface Node @source(name: "Node", schema: "contract") @source(name: "Node", schema: "customer") { - id: ID! -} - -type Consultant implements Node @source(name: "Consultant", schema: "customer") { - id: ID! - name: String! - customers(first: Int after: String last: Int before: String): CustomerConnection -} - -type CreateCustomerPayload @source(name: "CreateCustomerPayload", schema: "customer") { - customer: Customer -} - -type Customer implements Node @source(name: "Customer", schema: "customer") { - id: ID! - name: String! - street: String! - consultant(customer: CustomerInput): Consultant - say(input: SayInput!): String - complexArg(arg: ComplexInputType): String - someInt: Int! - someGuid: Uuid! - kind: CustomerKind! - contracts: [Contract!] @delegate(path: "contracts(customerId:$fields:id)", schema: "customer") -} - -"A connection to a list of items." -type CustomerConnection @source(name: "CustomerConnection", schema: "customer") { - "Information to aid in pagination." - pageInfo: PageInfo! - "A list of edges." - edges: [CustomerEdge!] - "A flattened list of the nodes." - nodes: [Customer] -} - -"An edge in a connection." -type CustomerEdge @source(name: "CustomerEdge", schema: "customer") { - "A cursor for use in pagination." - cursor: String! - "The item at the end of the edge." - node: Customer -} - -type LifeInsuranceContract implements Node & Contract @source(name: "LifeInsuranceContract", schema: "contract") { - id: ID! - customerId: ID! - foo(bar: String): String - error: String - date_field: Date - date_time_field: DateTime - string_field: String - id_field: ID - byte_field: Byte - int_field: Int - long_field: Long - float_field(f: Float): Float - decimal_field: Decimal - premium: Float! -} - -type Mutation { - createCustomer(input: CreateCustomerInput): CreateCustomerPayload @delegate(schema: "customer") - createCustomers(inputs: [CreateCustomerInput]): [CreateCustomerPayload] @delegate(schema: "customer") -} - -"Information about pagination in a connection." -type PageInfo @source(name: "PageInfo", schema: "customer") { - "Indicates whether more edges exist following the set defined by the clients arguments." - hasNextPage: Boolean! - "Indicates whether more edges exist prior the set defined by the clients arguments." - hasPreviousPage: Boolean! - "When paginating backwards, the cursor to continue." - startCursor: String - "When paginating forwards, the cursor to continue." - endCursor: String +type Product @source(name: "Product", schema: "products") { + upc: Int! + name: String + price: Int! + weight: Int! + inStock: Boolean @delegate(path: "inventoryInfo(upc: $fields:upc).isInStock", schema: "inventory") + shippingEstimate: Int @delegate(path: "shippingEstimate(price: $fields:price weight: $fields:weight)", schema: "inventory") + reviews: [Review] @delegate(path: "reviewsByProduct(upc: $fields:upc)", schema: "reviews") } type Query { - _schemaDefinition(configuration: String!): _SchemaDefinition @delegate(schema: "contract") - node(id: ID!): Node @delegate(schema: "contract") - contract(contractId: ID!): Contract @delegate(schema: "contract") - contracts(customerId: ID!): [Contract!] @delegate(schema: "contract") - extendedScalar(d: DateTime): DateTime @delegate(schema: "contract") - int(i: Int!): Int! @delegate(schema: "contract") - guid(guid: Uuid!): Uuid! @delegate(schema: "contract") - customer__schemaDefinition(configuration: String!): _SchemaDefinition @delegate(schema: "customer", path: "_schemaDefinition(configuration: $arguments:configuration)") - customer_node(id: ID!): Node @delegate(schema: "customer", path: "node(id: $arguments:id)") - customer(id: ID!): Customer @delegate(schema: "customer") - customers(ids: [ID!]!): [Customer] @delegate(schema: "customer") - consultant(id: ID!): Consultant @delegate(schema: "customer") - customerOrConsultant(id: ID!): CustomerOrConsultant @delegate(schema: "customer") - customerByKind(kind: CustomerKind!): Customer @delegate(schema: "customer") - allCustomers: [Customer] @delegate(schema: "customer") -} - -type SomeOtherContract implements Node & Contract @source(name: "SomeOtherContract", schema: "contract") { - id: ID! - customerId: ID! - expiryDate: DateTime! + me: User! @delegate(path: "user(id: 1)", schema: "accounts") + topProducts(first: Int = 5): [Product] @delegate(schema: "products") } -type _SchemaDefinition @source(name: "_SchemaDefinition", schema: "contract") @source(name: "_SchemaDefinition", schema: "customer") { - name: String! - document: String! - extensionDocuments: [String!]! +type Review @source(name: "Review", schema: "reviews") { + id: Int! + authorId: Int! + upc: Int! + body: String + author: User @delegate(path: "user(id: $fields:authorId)", schema: "accounts") + product: Product @delegate(path: "product(upc: $fields:upc)", schema: "products") } -union CustomerOrConsultant @source(name: "CustomerOrConsultant", schema: "customer") = Customer | Consultant - -input ComplexInputType @source(name: "ComplexInputType", schema: "customer") { - value: String - deeper: ComplexInputType - valueArray: [String] - deeperArray: [ComplexInputType] -} - -input CreateCustomerInput @source(name: "CreateCustomerInput", schema: "customer") { +type User @source(name: "User", schema: "accounts") { + id: Int! name: String - street: String - consultantId: String -} - -input CustomerInput @source(name: "CustomerInput", schema: "customer") { - id: String - name: String - street: String - consultantId: String - someInt: Int! - someGuid: Uuid! - kind: CustomerKind! -} - -input SayInput @source(name: "SayInput", schema: "customer") { - words: [String] -} - -enum CustomerKind @source(name: "CustomerKind", schema: "customer") { - STANDARD - PREMIUM + birthdate: DateTime! + username: String + reviews: [Review] @delegate(path: "reviewsByAuthor(authorId: $fields:id)", schema: "reviews") } directive @delegate(path: String "The name of the schema to which this field shall be delegated to." schema: Name!) on FIELD_DEFINITION @@ -160,34 +47,14 @@ directive @source("The original name of the annotated type." name: Name! "The na "The `Boolean` scalar type represents `true` or `false`." scalar Boolean -"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." -scalar Byte - -"The `Date` scalar represents an ISO-8601 compliant date type." -scalar Date - "The `DateTime` scalar represents an ISO-8601 compliant date time type." scalar DateTime -"The built-in `Decimal` scalar type." -scalar Decimal - -"The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http:\/\/en.wikipedia.org\/wiki\/IEEE_floating_point)." -scalar Float - -"The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID." -scalar ID - "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." scalar Int -"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." -scalar Long - "The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types." scalar Name "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." scalar String - -scalar Uuid diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs index 60d6c17bbbb..4a7bef8f1e6 100644 --- a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/AddSchemaExtensionRewriterTests.cs @@ -21,7 +21,24 @@ public void ObjectType_AddScalarField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); + } + + [Fact] + public void ObjectType_AddScalarField_2() + { + // arrange + const string schema = "type Foo { bar: String }"; + const string extensions = "extend type Foo { baz: Int } extend type Baz { a: String }"; + + // act + var rewriter = new AddSchemaExtensionRewriter(); + DocumentNode merged = rewriter.AddExtensions( + Utf8GraphQLParser.Parse(schema), + Utf8GraphQLParser.Parse(extensions)); + + // assert + merged.ToString().MatchSnapshot(); } [Fact] @@ -39,7 +56,7 @@ public void ObjectType_AddObjectField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -57,7 +74,7 @@ public void ObjectType_AddDirectives() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -75,7 +92,7 @@ public void ObjectType_AddDirectivesToField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -93,7 +110,7 @@ public void ObjectType_DirectiveDeclaredInExtensionDoc() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -145,7 +162,7 @@ public void InterfaceType_AddScalarField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -163,7 +180,7 @@ public void InterfaceType_AddObjectField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -181,7 +198,7 @@ public void InterfaceType_AddDirectives() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -236,7 +253,7 @@ public void UnionType_AddType() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -256,7 +273,7 @@ public void UnionType_AddDirectives() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -312,7 +329,7 @@ public void InputObjectType_AddScalarField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -330,7 +347,7 @@ public void InputObjectType_AddObjectField() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] @@ -348,7 +365,7 @@ public void InputObjectType_AddDirectives() Utf8GraphQLParser.Parse(extensions)); // assert - SchemaSyntaxSerializer.Serialize(merged).MatchSnapshot(); + merged.ToString().MatchSnapshot(); } [Fact] diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap new file mode 100644 index 00000000000..ce27010e924 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Merge/__snapshots__/AddSchemaExtensionRewriterTests.ObjectType_AddScalarField_2.snap @@ -0,0 +1,8 @@ +type Foo { + bar: String + baz: Int +} + +extend type Baz { + a: String +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..cdc40fe7e56 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/AccountsSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,30 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Accounts +{ + public static class AccountsSchemaRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddAccountsSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddGraphQLServer() + .AddQueryType() + .PublishSchemaDefinition(c => c + .SetName("accounts") + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + me: User! @delegate(path: ""user(id: 1)"") + } + + extend type Review { + author: User @delegate(path: ""user(id: $fields:authorId)"") + }")); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs new file mode 100644 index 00000000000..d5af8ba8a0c --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/Query.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Accounts +{ + public class Query + { + public IEnumerable GetUsers([Service] UserRepository repository) => + repository.GetUsers(); + + public User GetUser(int id, [Service] UserRepository repository) => + repository.GetUser(id); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs new file mode 100644 index 00000000000..c9b6fa43f37 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/User.cs @@ -0,0 +1,23 @@ +using System; + +namespace HotChocolate.Stitching.Schemas.Accounts +{ + public class User + { + public User(int id, string name, DateTime birthdate, string username) + { + Id = id; + Name = name; + Birthdate = birthdate; + Username = username; + } + + public int Id { get; } + + public string Name { get; } + + public DateTime Birthdate { get; } + + public string Username { get; } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs new file mode 100644 index 00000000000..e116ccf481e --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Accounts/UserRepository.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Accounts +{ + public class UserRepository + { + private readonly Dictionary _users; + + public UserRepository() + { + _users = new User[] + { + new User(1, "Ada Lovelace", new DateTime(1815, 12, 10), "@ada"), + new User(2, "Alan Turing", new DateTime(1912, 06, 23), "@complete") + }.ToDictionary(t => t.Id); + } + + public User GetUser(int id) => _users[id]; + + public IEnumerable GetUsers() => _users.Values; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs new file mode 100644 index 00000000000..71c06b31a2f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfo.cs @@ -0,0 +1,15 @@ +namespace HotChocolate.Stitching.Schemas.Inventory +{ + public class InventoryInfo + { + public InventoryInfo(int upc, bool isInStock) + { + Upc = upc; + IsInStock = isInStock; + } + + public int Upc { get; } + + public bool IsInStock { get; } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs new file mode 100644 index 00000000000..29d4157171a --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventoryInfoRepository.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Inventory +{ + public class InventoryInfoRepository + { + private readonly Dictionary _infos; + + public InventoryInfoRepository() + { + _infos = new InventoryInfo[] + { + new InventoryInfo(1, true), + new InventoryInfo(2, false), + new InventoryInfo(3, true) + }.ToDictionary(t => t.Upc); + } + + public InventoryInfo GetInventoryInfo(int upc) => _infos[upc]; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..769402a1489 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/InventorySchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,29 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Inventory +{ + public static class InventorySchemaRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddInventorySchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType() + .PublishSchemaDefinition(c => c + .SetName("inventory") + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Product { + inStock: Boolean + @delegate(path: ""inventoryInfo(upc: $fields:upc).isInStock"") + shippingEstimate: Int + @delegate(path: ""shippingEstimate(price: $fields:price weight: $fields:weight)"") + } + ")); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs new file mode 100644 index 00000000000..c29395b6102 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Inventory/Query.cs @@ -0,0 +1,13 @@ +namespace HotChocolate.Stitching.Schemas.Inventory +{ + public class Query + { + public InventoryInfo GetInventoryInfo( + int upc, + [Service] InventoryInfoRepository repository) => + repository.GetInventoryInfo(upc); + + public double GetShippingEstimate(int price, int weight) => + price > 1000 ? 0 : weight * 0.5; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs new file mode 100644 index 00000000000..cc3f7f6ec2f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Product.cs @@ -0,0 +1,18 @@ +namespace HotChocolate.Stitching.Schemas.Products +{ + public class Product + { + public Product(int upc, string name, int price, int weight) + { + Upc = upc; + Name = name; + Price = price; + Weight = weight; + } + + public int Upc { get; } + public string Name { get; } + public int Price { get; } + public int Weight { get; } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs new file mode 100644 index 00000000000..2cd0af8ee22 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductRepository.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Products +{ + public class ProductRepository + { + private readonly Dictionary _products; + + public ProductRepository() + { + _products = new Product[] + { + new Product(1, "Table", 899, 100), + new Product(2, "Couch", 1299, 1000), + new Product(3, "Chair", 54, 50) + }.ToDictionary(t => t.Upc); + } + + public IEnumerable GetTopProducts(int first) => + _products.Values.OrderBy(t => t.Upc).Take(first); + + public Product GetProduct (int upc) => _products[upc]; + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..5e5a4f250f2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/ProductsSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,29 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Products +{ + public static class ProductsSchemaRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddProductsSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType() + .PublishSchemaDefinition(c => c + .SetName("products") + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type Query { + topProducts(first: Int = 5): [Product] @delegate + } + + extend type Review { + product: Product @delegate(path: ""product(upc: $fields:upc)"") + }")); + } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs new file mode 100644 index 00000000000..c78f7ebab2f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Products/Query.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Products +{ + public class Query + { + public IEnumerable GetTopProducts( + int first, + [Service] ProductRepository repository) => + repository.GetTopProducts(first); + + public Product GetProduct( + int upc, + [Service] ProductRepository repository) => + repository.GetProduct(upc); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs new file mode 100644 index 00000000000..18aeba675a2 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Author.cs @@ -0,0 +1,14 @@ +namespace HotChocolate.Stitching.Schemas.Reviews +{ + public class Author + { + public Author(int id, string name) + { + Id = id; + Name = name; + } + + public int Id { get; } + public string Name { get; } + } +} diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs new file mode 100644 index 00000000000..a468ebc180f --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Query.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace HotChocolate.Stitching.Schemas.Reviews +{ + public class Query + { + public IEnumerable GetReviews( + [Service] ReviewRepository repository) => + repository.GetReviews(); + + public IEnumerable GetReviewsByAuthor( + [Service] ReviewRepository repository, + int authorId) => + repository.GetReviewsByAuthorId(authorId); + + public IEnumerable GetReviewsByProduct( + [Service] ReviewRepository repository, + int upc) => + repository.GetReviewsByAuthorId(upc); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs new file mode 100644 index 00000000000..ec54ae809d6 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/Review.cs @@ -0,0 +1,18 @@ +namespace HotChocolate.Stitching.Schemas.Reviews +{ + public class Review + { + public Review(int id, int authorId, int upc, string body) + { + Id = id; + AuthorId = authorId; + Upc = upc; + Body = body; + } + + public int Id { get; } + public int AuthorId { get; } + public int Upc { get; } + public string Body { get; } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs new file mode 100644 index 00000000000..af02a250818 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewRepository.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HotChocolate.Stitching.Schemas.Reviews +{ + public class ReviewRepository + { + private readonly Dictionary _reviews; + private readonly Dictionary _authors; + + public ReviewRepository() + { + _reviews = new Review[] + { + new Review(1, 1, 1, "Love it!"), + new Review(2, 1, 2, "Too expensive."), + new Review(3, 2, 3, "Could be better."), + new Review(4, 2, 1, "Prefer something else.") + }.ToDictionary(t => t.Id); + + _authors = new Author[] + { + new Author(1, "@ada"), + new Author(2, "@complete") + }.ToDictionary(t => t.Id); + } + + public IEnumerable GetReviews() => + _reviews.Values.OrderBy(t => t.Id); + + public IEnumerable GetReviewsByProductId(int upc) => + _reviews.Values.OrderBy(t => t.Id).Where(t => t.Upc == upc); + + public IEnumerable GetReviewsByAuthorId(int authorId) => + _reviews.Values.OrderBy(t => t.Id).Where(t => t.AuthorId == authorId); + + public Review GetReview(int id) => _reviews[id]; + + public Author GetAuthor(int id) => _authors[id]; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..f31bfae3ba4 --- /dev/null +++ b/src/HotChocolate/Stitching/test/Stitching.Tests/Schemas/Reviews/ReviewsSchemaRequestExecutorBuilderExtensions.cs @@ -0,0 +1,31 @@ +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Stitching.Schemas.Reviews +{ + public static class ReviewSchemaRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddReviewSchema( + this IRequestExecutorBuilder builder) + { + builder.Services + .AddSingleton(); + + return builder + .AddQueryType() + .PublishSchemaDefinition(c => c + .SetName("reviews") + .IgnoreRootTypes() + .AddTypeExtensionsFromString( + @"extend type User { + reviews: [Review] + @delegate(path:""reviewsByAuthor(authorId: $fields:id)"") + } + + extend type Product { + reviews: [Review] + @delegate(path:""reviewsByProduct(upc: $fields:upc)"") + }")); + } + } +}