diff --git a/.devops/azure-pipelines.release-hotchocolate.yml b/.devops/azure-pipelines.release-hotchocolate.yml index 81458c77815..0ac2fe415ec 100644 --- a/.devops/azure-pipelines.release-hotchocolate.yml +++ b/.devops/azure-pipelines.release-hotchocolate.yml @@ -1,3 +1,4 @@ +--- # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # # @@ -12,60 +13,59 @@ # nuke --generate-configuration AzurePipelines_test-pr-hotchocolate --host AzurePipelines # # -# ------------------------------------------------------------------------------ trigger: none stages: - stage: ubuntu_latest - displayName: 'ubuntu-latest' - dependsOn: [ ] + displayName: "ubuntu-latest" + dependsOn: [] pool: - vmImage: 'ubuntu-latest' + vmImage: "ubuntu-latest" jobs: - job: Test - displayName: 'Test' - dependsOn: [ ] + displayName: "Test" + dependsOn: [] strategy: parallel: 8 - steps: + steps: - task: CmdLine@2 inputs: - script: './build.cmd test --skip --test-partition $(System.JobPositionInPhase)' + script: "./build.cmd test --skip --test-partition $(System.JobPositionInPhase)" - task: PublishBuildArtifacts@1 inputs: artifactName: test-results - pathtoPublish: 'output/test-results' + pathtoPublish: "output/test-results" - job: Pack - displayName: 'Pack' - dependsOn: [ ] - steps: + displayName: "Pack" + dependsOn: [] + steps: - task: CmdLine@2 inputs: - script: './build.cmd pack --skip' + script: "./build.cmd pack --skip" - task: PublishBuildArtifacts@1 inputs: artifactName: packages - pathtoPublish: 'output/packages' + pathtoPublish: "output/packages" - job: Publish - displayName: 'Publish' - dependsOn: [ Test, Pack ] + displayName: "Publish" + dependsOn: [Test, Pack] steps: - task: DownloadBuildArtifacts@0 inputs: - artifactName: 'packages' - downloadPath: '$(Build.Repository.LocalPath)/output' + artifactName: "packages" + downloadPath: "$(Build.Repository.LocalPath)/output" - task: CmdLine@2 inputs: - script: './build.cmd publish --skip' + script: "./build.cmd publish --skip" - task: GitHubRelease@0 - displayName: 'GitHub release' + displayName: "GitHub release" inputs: - gitHubConnection: 'ChilliCream GitHub' + gitHubConnection: "ChilliCream GitHub" action: edit - tag: '$(GitHubVersion)' - title: '$(GitHubVersion)' - assets: '$(Build.Repository.LocalPath)/output/packages/*.*' + tag: "$(GitHubVersion)" + title: "$(GitHubVersion)" + assets: "$(Build.Repository.LocalPath)/output/packages/*.*" assetUploadMode: replace isPreRelease: true releaseNotesSource: input - releaseNotes: 'For more details click [here](https://github.com/ChilliCream/hotchocolate/blob/master/CHANGELOG.md) to get to our CHANGELOG.' + releaseNotes: "For more details click [here](https://github.com/ChilliCream/hotchocolate/blob/master/CHANGELOG.md) to get to our CHANGELOG." diff --git a/build.ps1 b/build.ps1 index ac154ae91f4..f9830771a0f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -55,6 +55,10 @@ else { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version "2.1.809" -NoPath } + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version "3.0.103" -NoPath } + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version "3.1.401" -NoPath } + # Install by channel or version if (!(Test-Path variable:DotNetVersion)) { ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } diff --git a/build.sh b/build.sh index 7cee9063cf2..2b4db617e74 100755 --- a/build.sh +++ b/build.sh @@ -48,6 +48,10 @@ else curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" chmod +x "$DOTNET_INSTALL_FILE" + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "2.1.809" --no-path + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "3.0.103" --no-path + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "3.1.401" --no-path + # Install by channel or version if [[ -z ${DOTNET_VERSION+x} ]]; then "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path diff --git a/global.json b/global.json index 0ebfb85f9ab..f8971bd5559 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "3.1.300" + "version": "5.0.100-preview.7.20366.6" } } diff --git a/src/GreenDonut/src/Core/Batch.cs b/src/GreenDonut/src/Core/Batch.cs index 770b8af0ade..29f22afce3d 100644 --- a/src/GreenDonut/src/Core/Batch.cs +++ b/src/GreenDonut/src/Core/Batch.cs @@ -2,36 +2,50 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; namespace GreenDonut { public class Batch where TKey : notnull { + private readonly object _sync = new object(); + private bool _hasDispatched = false; private Dictionary> _items = new Dictionary>(); - public bool HasDispatched { get; private set; } - public IReadOnlyList Keys => _items.Keys.ToArray(); public int Size => _items.Count; - public TaskCompletionSource CreateOrGet(TKey key) + public bool TryGetOrCreate( + TKey key, + [NotNullWhen(true)] out TaskCompletionSource? promise) { - ThrowIfDispatched(); - - if (_items.ContainsKey(key)) + if (!_hasDispatched) { - return _items[key]; - } + lock (_sync) + { + if (!_hasDispatched) + { + if (_items.ContainsKey(key)) + { + promise = _items[key]; + } + else + { + promise = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + _items.Add(key, promise); + } - var promise = new TaskCompletionSource( - TaskCreationOptions.RunContinuationsAsynchronously); - - _items.Add(key, promise); + return true; + } + } + } - return promise; + promise = null; + return false; } public TaskCompletionSource Get(TKey key) @@ -39,19 +53,27 @@ public TaskCompletionSource Get(TKey key) return _items[key]; } - public void StartDispatching() + public ValueTask StartDispatchingAsync(Func dispatch) { - ThrowIfDispatched(); + bool execute = false; - HasDispatched = true; - } + if (!_hasDispatched) + { + lock (_sync) + { + if (!_hasDispatched) + { + execute = _hasDispatched = true; + } + } + } - private void ThrowIfDispatched() - { - if (HasDispatched) + if (execute) { - throw new InvalidOperationException("This batch has already been dispatched."); + return dispatch(); } + + return default; } } } diff --git a/src/GreenDonut/src/Core/DataLoaderBase.cs b/src/GreenDonut/src/Core/DataLoaderBase.cs index 639709b9966..ca111745deb 100644 --- a/src/GreenDonut/src/Core/DataLoaderBase.cs +++ b/src/GreenDonut/src/Core/DataLoaderBase.cs @@ -174,7 +174,7 @@ public void Clear() /// A list of results which are in the exact same order as the provided /// keys. /// - protected abstract Task>> FetchAsync( + protected abstract ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken); @@ -189,7 +189,6 @@ public Task LoadAsync(TKey key, CancellationToken cancellationToken) lock (_sync) { object cacheKey = _cacheKeyResolver(key); - Batch batch = GetCurrentBatch(); if (_options.Caching && _cache.TryGetValue(cacheKey, out object? cachedValue)) { @@ -200,7 +199,7 @@ public Task LoadAsync(TKey key, CancellationToken cancellationToken) return cachedTask; } - TaskCompletionSource promise = batch.CreateOrGet(key); + TaskCompletionSource promise = GetOrCreatePromise(key); if (_options.Caching) { @@ -312,62 +311,48 @@ private void BatchOperationSucceeded( } } - private Task DispatchBatchAsync( + private ValueTask DispatchBatchAsync( Batch batch, CancellationToken cancellationToken) { - if (!batch.HasDispatched) + return batch.StartDispatchingAsync(async () => { - lock (_sync) - { - if (!batch.HasDispatched) - { - batch.StartDispatching(); + Activity? activity = DiagnosticEvents.StartBatching(batch.Keys); + IReadOnlyList> results = new Result[0]; - return DispatchBatchInternalAsync(batch, batch.Keys, cancellationToken); - } + try + { + results = await FetchAsync(batch.Keys, cancellationToken).ConfigureAwait(false); + BatchOperationSucceeded(batch, batch.Keys, results); + } + catch (Exception ex) + { + BatchOperationFailed(batch, batch.Keys, ex); } - } - return Task.CompletedTask; + DiagnosticEvents.StopBatching(activity, batch.Keys, + results.Select(result => result.Value).ToArray()); + }); } - private async Task DispatchBatchInternalAsync( - Batch batch, - IReadOnlyList keys, - CancellationToken cancellationToken) + private TaskCompletionSource GetOrCreatePromise(TKey key) { - Activity? activity = DiagnosticEvents.StartBatching(keys); - IReadOnlyList> results = new Result[0]; - - try - { - results = await FetchAsync(keys, cancellationToken).ConfigureAwait(false); - BatchOperationSucceeded(batch, keys, results); - } - catch (Exception ex) + if (_currentBatch is {} && + _currentBatch.Size < _maxBatchSize && + _currentBatch.TryGetOrCreate(key, out TaskCompletionSource? promise) && + promise is {}) { - BatchOperationFailed(batch, keys, ex); + return promise; } - DiagnosticEvents.StopBatching(activity, keys, - results.Select(result => result.Value).ToArray()); - } - - private Batch GetCurrentBatch() - { - if (_currentBatch == null || - _currentBatch.HasDispatched || - _currentBatch.Size == _maxBatchSize) - { - var newBatch = new Batch(); + var newBatch = new Batch(); - _batchScheduler.Schedule(() => - DispatchBatchAsync(newBatch, _disposeTokenSource.Token)); - _currentBatch = newBatch; - } + newBatch.TryGetOrCreate(key, out TaskCompletionSource? newPromise); + _batchScheduler.Schedule(() => + DispatchBatchAsync(newBatch, _disposeTokenSource.Token)); + _currentBatch = newBatch; - return _currentBatch; + return newPromise!; } private async Task> LoadInternalAsync( diff --git a/src/GreenDonut/src/Core/FetchDataDelegate.cs b/src/GreenDonut/src/Core/FetchDataDelegate.cs index 13198eccd82..db6d5b12ae2 100644 --- a/src/GreenDonut/src/Core/FetchDataDelegate.cs +++ b/src/GreenDonut/src/Core/FetchDataDelegate.cs @@ -17,7 +17,7 @@ namespace GreenDonut /// A list of results which are in the exact same order as the provided /// keys. /// - public delegate Task>> FetchDataDelegate( + public delegate ValueTask>> FetchDataDelegate( IReadOnlyList keys, CancellationToken cancellationToken) where TKey : notnull; diff --git a/src/GreenDonut/src/Core/GreenDonut.csproj b/src/GreenDonut/src/Core/GreenDonut.csproj index ec968b9b495..ea35630654d 100644 --- a/src/GreenDonut/src/Core/GreenDonut.csproj +++ b/src/GreenDonut/src/Core/GreenDonut.csproj @@ -9,6 +9,7 @@ + diff --git a/src/GreenDonut/src/Core/IBatchScheduler.cs b/src/GreenDonut/src/Core/IBatchScheduler.cs index 23768d4cdc5..0bf62f1ef19 100644 --- a/src/GreenDonut/src/Core/IBatchScheduler.cs +++ b/src/GreenDonut/src/Core/IBatchScheduler.cs @@ -1,9 +1,10 @@ using System; +using System.Threading.Tasks; namespace GreenDonut { public interface IBatchScheduler { - void Schedule(Action dispatch); + void Schedule(Func dispatch); } } diff --git a/src/GreenDonut/test/Core.Tests/DataLoader.cs b/src/GreenDonut/test/Core.Tests/DataLoader.cs index e648c07bb52..20e939781ff 100644 --- a/src/GreenDonut/test/Core.Tests/DataLoader.cs +++ b/src/GreenDonut/test/Core.Tests/DataLoader.cs @@ -24,7 +24,7 @@ public DataLoader( _fetch = fetch ?? throw new ArgumentNullException(nameof(fetch)); } - protected override Task>> FetchAsync( + protected override ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken) { diff --git a/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs b/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs index 1313ae02da4..20f4a390cba 100644 --- a/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs +++ b/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Threading.Tasks; namespace GreenDonut { @@ -7,20 +8,21 @@ public class ManualBatchScheduler : IBatchScheduler { private readonly object _sync = new object(); - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); + private readonly ConcurrentQueue> _queue = + new ConcurrentQueue>(); public void Dispatch() { lock(_sync) { - while (_queue.TryDequeue(out Action dispatch)) + while (_queue.TryDequeue(out Func dispatch)) { dispatch(); } } } - public void Schedule(Action dispatch) + public void Schedule(Func dispatch) { _queue.Enqueue(dispatch); } diff --git a/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs b/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs index e648c07bb52..20e939781ff 100644 --- a/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs +++ b/src/GreenDonut/test/DiagnosticSource.Tests/DataLoader.cs @@ -24,7 +24,7 @@ public DataLoader( _fetch = fetch ?? throw new ArgumentNullException(nameof(fetch)); } - protected override Task>> FetchAsync( + protected override ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken) { diff --git a/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs b/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs index 766d7b908da..81f727961d0 100644 --- a/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs +++ b/src/GreenDonut/test/DiagnosticSource.Tests/DiagnosticEventsTests.cs @@ -51,7 +51,7 @@ public async Task VerifyEvents() } } - private async Task>> FetchDataAsync( + private ValueTask>> FetchDataAsync( IReadOnlyList keys, CancellationToken cancellationToken) { @@ -65,7 +65,7 @@ private async Task>> FetchDataAsync( : Result.Resolve(null); } - return await Task.FromResult(results).ConfigureAwait(false); + return new ValueTask>>(results); } private async Task Catch(Func execute) diff --git a/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs b/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs index 1313ae02da4..20f4a390cba 100644 --- a/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs +++ b/src/GreenDonut/test/DiagnosticSource.Tests/ManualBatchScheduler.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Threading.Tasks; namespace GreenDonut { @@ -7,20 +8,21 @@ public class ManualBatchScheduler : IBatchScheduler { private readonly object _sync = new object(); - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); + private readonly ConcurrentQueue> _queue = + new ConcurrentQueue>(); public void Dispatch() { lock(_sync) { - while (_queue.TryDequeue(out Action dispatch)) + while (_queue.TryDequeue(out Func dispatch)) { dispatch(); } } } - public void Schedule(Action dispatch) + public void Schedule(Func dispatch) { _queue.Enqueue(dispatch); } diff --git a/src/HotChocolate/Core/HotChocolate.Core.sln b/src/HotChocolate/Core/HotChocolate.Core.sln index 4dcff98d402..d01fce63c29 100644 --- a/src/HotChocolate/Core/HotChocolate.Core.sln +++ b/src/HotChocolate/Core/HotChocolate.Core.sln @@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Subscriptions. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Subscriptions.InMemory.Tests", "test\Subscriptions.InMemory.Tests\HotChocolate.Subscriptions.InMemory.Tests.csproj", "{F72E72DE-F163-4BCC-A442-94DB331D3C35}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fetching.Tests", "test\Fetching.Tests\HotChocolate.Fetching.Tests.csproj", "{2A319702-6C2E-4184-8E71-43F87745B38D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -399,6 +401,18 @@ Global {F72E72DE-F163-4BCC-A442-94DB331D3C35}.Release|x64.Build.0 = Release|Any CPU {F72E72DE-F163-4BCC-A442-94DB331D3C35}.Release|x86.ActiveCfg = Release|Any CPU {F72E72DE-F163-4BCC-A442-94DB331D3C35}.Release|x86.Build.0 = Release|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x64.ActiveCfg = Debug|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x64.Build.0 = Debug|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x86.ActiveCfg = Debug|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Debug|x86.Build.0 = Debug|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|Any CPU.Build.0 = Release|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x64.ActiveCfg = Release|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x64.Build.0 = Release|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x86.ActiveCfg = Release|Any CPU + {2A319702-6C2E-4184-8E71-43F87745B38D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -431,6 +445,7 @@ Global {3ED68361-0D3D-45F5-8A83-444DD3D5C1BF} = {37B9D3B1-CA34-4720-9A0B-CFF1E64F52C2} {14648C7D-8958-4C94-B837-7F90F2F10081} = {7462D089-D350-44D6-8131-896D949A65B7} {F72E72DE-F163-4BCC-A442-94DB331D3C35} = {7462D089-D350-44D6-8131-896D949A65B7} + {2A319702-6C2E-4184-8E71-43F87745B38D} = {7462D089-D350-44D6-8131-896D949A65B7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E4D94C77-6657-4630-9D42-0A9AC5153A1B} diff --git a/src/HotChocolate/Core/src/Abstractions/Error.cs b/src/HotChocolate/Core/src/Abstractions/Error.cs index 291fb0fa4ba..882986678cc 100644 --- a/src/HotChocolate/Core/src/Abstractions/Error.cs +++ b/src/HotChocolate/Core/src/Abstractions/Error.cs @@ -3,7 +3,7 @@ using HotChocolate.Execution; using HotChocolate.Properties; -#nullable enable +#nullable enable namespace HotChocolate { @@ -33,9 +33,9 @@ public Error( Extensions = extensions; Exception = exception; - if (code is { } && Extensions is null) + if (code is not null && Extensions is null) { - Extensions = new OrderedDictionary { {_codePropertyName, code} }; + Extensions = new OrderedDictionary { { _codePropertyName, code } }; } } @@ -71,8 +71,8 @@ public IError WithCode(string? code) } var extensions = Extensions is null - ? new OrderedDictionary() {[_codePropertyName] = code} - : new OrderedDictionary(Extensions) {[_codePropertyName] = code}; + ? new OrderedDictionary() { [_codePropertyName] = code } + : new OrderedDictionary(Extensions) { [_codePropertyName] = code }; return new Error(Message, code, Path, Locations, extensions, Exception); } diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ITask.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTask.cs similarity index 70% rename from src/HotChocolate/Core/src/Execution/Utilities/ITask.cs rename to src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTask.cs index a69de236f23..1e7eef5e54e 100644 --- a/src/HotChocolate/Core/src/Execution/Utilities/ITask.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTask.cs @@ -1,10 +1,12 @@ -namespace HotChocolate.Execution.Utilities +namespace HotChocolate.Execution { /// /// Represents a task that shall be executed by the execution engine. /// - internal interface ITask + public interface IExecutionTask { + bool IsCompleted { get; } + /// /// Starts the execution of this task. /// diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskContext.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskContext.cs new file mode 100644 index 00000000000..1d0e7f4115f --- /dev/null +++ b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskContext.cs @@ -0,0 +1,17 @@ +using System; + +namespace HotChocolate.Execution +{ + public interface IExecutionTaskContext + { + void ReportError(IExecutionTask task, IError error); + + void ReportError(IExecutionTask task, Exception exception); + + IDisposable Track(IExecutionTask task); + + void Started(); + + void Completed(); + } +} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskDefinition.cs b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskDefinition.cs new file mode 100644 index 00000000000..50d90cc7401 --- /dev/null +++ b/src/HotChocolate/Core/src/Abstractions/Execution/IExecutionTaskDefinition.cs @@ -0,0 +1,7 @@ +namespace HotChocolate.Execution +{ + public interface IExecutionTaskDefinition + { + IExecutionTask Create(IExecutionTaskContext context); + } +} diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs b/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs index bc5105024ab..b0c5a5321f8 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/QueryResultBuilder.cs @@ -144,7 +144,7 @@ public IReadOnlyQueryResult Create() public static QueryResultBuilder New() => new QueryResultBuilder(); - public static QueryResultBuilder FromResult(IReadOnlyQueryResult result) + public static QueryResultBuilder FromResult(IQueryResult result) { var builder = new QueryResultBuilder(); builder._data = result.Data; diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs b/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs index 70ead95e208..985795736e5 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/SubscriptionResult.cs @@ -36,6 +36,32 @@ public SubscriptionResult( _session = session; } + public SubscriptionResult( + SubscriptionResult subscriptionResult, + IAsyncDisposable? session = null) + { + _resultStreamFactory = subscriptionResult._resultStreamFactory; + _errors = subscriptionResult._errors; + _extensions = subscriptionResult._extensions; + _contextData = subscriptionResult._contextData; + _session = session is null + ? (IAsyncDisposable)subscriptionResult + : new CombinedDispose(session, subscriptionResult); + } + + public SubscriptionResult( + SubscriptionResult subscriptionResult, + IDisposable? session = null) + { + _resultStreamFactory = subscriptionResult._resultStreamFactory; + _errors = subscriptionResult._errors; + _extensions = subscriptionResult._extensions; + _contextData = subscriptionResult._contextData; + _session = session is null + ? (IAsyncDisposable)subscriptionResult + : new CombinedDispose(session.Dispose, subscriptionResult); + } + public IReadOnlyList? Errors => _errors; public IReadOnlyDictionary? Extensions => _extensions; @@ -79,5 +105,40 @@ public async ValueTask DisposeAsync() } } + private class CombinedDispose : IAsyncDisposable + { + private readonly IAsyncDisposable? _aa; + private readonly Action? _ab; + private readonly IAsyncDisposable _b; + private bool _disposed = false; + + public CombinedDispose(IAsyncDisposable a, IAsyncDisposable b) + { + _aa = a; + _b = b; + } + + public CombinedDispose(Action a, IAsyncDisposable b) + { + _ab = a; + _b = b; + } + + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + if (_aa is not null) + { + await _aa.DisposeAsync().ConfigureAwait(false); + } + + _ab?.Invoke(); + + await _b.DisposeAsync().ConfigureAwait(false); + _disposed = true; + } + } + } } } diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs index 588794007e0..e09c7acad4b 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs @@ -9,6 +9,7 @@ using HotChocolate.Language; using HotChocolate.Utilities; using System.Linq; +using HotChocolate.DataLoader; namespace Microsoft.Extensions.DependencyInjection { @@ -97,5 +98,12 @@ internal static IServiceCollection TryAddDefaultBatchDispatcher( services.TryAddScoped(sp => sp.GetRequiredService()); return services; } + + internal static IServiceCollection TryAddDefaultDataLoaderRegistry( + this IServiceCollection services) + { + services.TryAddScoped(); + return services; + } } } diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs new file mode 100644 index 00000000000..6903d884e49 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Extensions.DependencyInjection.Extensions; +using GreenDonut; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class RequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddDataLoader( + this IRequestExecutorBuilder builder) + where T : class, IDataLoader + { + builder.Services.TryAddScoped(); + return builder; + } + + public static IRequestExecutorBuilder AddDataLoader( + this IRequestExecutorBuilder builder) + where TService : class, IDataLoader + where TImplementation : class, TService + { + builder.Services.TryAddScoped(); + return builder; + } + + public static IRequestExecutorBuilder AddDataLoader( + this IRequestExecutorBuilder builder, + Func factory) + where T : class, IDataLoader + { + builder.Services.TryAddScoped(factory); + return builder; + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs index 72aa3ce0b86..00bc6d582d4 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs @@ -32,6 +32,7 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services services.TryAddDefaultCaches(); services.TryAddDefaultDocumentHashProvider(); services.TryAddDefaultBatchDispatcher(); + services.TryAddDefaultDataLoaderRegistry(); // pools services.TryAddResultPool(); diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs index 9b90c1b1b34..d1ec117e80f 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/SchemaRequestExecutorBuilderExtensions.Resolvers.cs @@ -166,7 +166,7 @@ public static IRequestExecutorBuilder AddResolver( this IRequestExecutorBuilder builder, NameString typeName, NameString fieldName, - Func> resolver) + Func> resolver) { if (builder == null) { @@ -225,7 +225,7 @@ public static IRequestExecutorBuilder AddResolver( this IRequestExecutorBuilder builder, NameString typeName, NameString fieldName, - Func> resolver) + Func> resolver) { if (builder == null) { diff --git a/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs b/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs index 5027601af22..67773544c00 100644 --- a/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs +++ b/src/HotChocolate/Core/src/Execution/ExecutionContext.Pooling.cs @@ -8,12 +8,16 @@ namespace HotChocolate.Execution internal partial class ExecutionContext : IExecutionContext { + private readonly IExecutionTaskContext _taskContext; private readonly TaskBacklog _taskBacklog; private readonly TaskStatistics _taskStatistics; private CancellationTokenSource _completed = default!; - public ExecutionContext(ObjectPool resolverTaskPool) + public ExecutionContext( + IExecutionTaskContext taskContext, + ObjectPool resolverTaskPool) { + _taskContext = taskContext; _taskStatistics = new TaskStatistics(); _taskBacklog = new TaskBacklog(_taskStatistics, resolverTaskPool); TaskPool = resolverTaskPool; @@ -21,7 +25,9 @@ public ExecutionContext(ObjectPool resolverTaskPool) TaskStats.AllTasksCompleted += OnCompleted; } - public void Initialize(IBatchDispatcher batchDispatcher, CancellationToken requestAborted) + public void Initialize( + IBatchDispatcher batchDispatcher, + CancellationToken requestAborted) { _completed = new CancellationTokenSource(); requestAborted.Register(TryComplete); diff --git a/src/HotChocolate/Core/src/Execution/ExecutionContext.cs b/src/HotChocolate/Core/src/Execution/ExecutionContext.cs index 70d2072bf51..0984fc079e2 100644 --- a/src/HotChocolate/Core/src/Execution/ExecutionContext.cs +++ b/src/HotChocolate/Core/src/Execution/ExecutionContext.cs @@ -24,7 +24,12 @@ private void TryDispatchBatches() { if (TaskBacklog.IsEmpty && BatchDispatcher.HasTasks) { - BatchDispatcher.Dispatch(); + BatchDispatcher.Dispatch(Register); + } + + void Register(IExecutionTaskDefinition taskDefinition) + { + TaskBacklog.Register(taskDefinition.Create(_taskContext)); } } @@ -37,7 +42,7 @@ private void TaskStatisticsEventHandler( TryDispatchBatches(); private void OnCompleted( - object? source, + object? source, EventArgs args) => _taskBacklog.Complete(); } diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs index 85e61b8a583..569f44ef2d0 100644 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs +++ b/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDiagnosticEvents.cs @@ -102,6 +102,31 @@ public void ResolverError(IMiddlewareContext context, IError error) } } + public IActivityScope RunTask(IExecutionTask task) + { + if (_listeners.Length == 0) + { + return _empty; + } + + var scopes = new IActivityScope[_resolverListener.Length]; + + for (int i = 0; i < _resolverListener.Length; i++) + { + scopes[i] = _resolverListener[i].RunTask(task); + } + + return new AggregateActivityScope(scopes); + } + + public void TaskError(IExecutionTask task, IError error) + { + for (int i = 0; i < _listeners.Length; i++) + { + _listeners[i].TaskError(task, error); + } + } + public void AddedDocumentToCache(IRequestContext context) { for (int i = 0; i < _listeners.Length; i++) diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs index 6cf9d8cb1d4..c6f8ebf1b3f 100644 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs +++ b/src/HotChocolate/Core/src/Execution/Instrumentation/DiagnosticEventListener.cs @@ -38,6 +38,12 @@ public virtual void ResolverError(IMiddlewareContext context, IError error) { } + public virtual IActivityScope RunTask(IExecutionTask task) => EmptyScope; + + public virtual void TaskError(IExecutionTask task, IError error) + { + } + public virtual void AddedDocumentToCache(IRequestContext context) { } diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs index 48833c63ccc..8c8c3c7b642 100644 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs +++ b/src/HotChocolate/Core/src/Execution/Instrumentation/IDiagnosticEvents.cs @@ -22,6 +22,10 @@ public interface IDiagnosticEvents void ResolverError(IMiddlewareContext context, IError error); + IActivityScope RunTask(IExecutionTask task); + + void TaskError(IExecutionTask task, IError error); + void AddedDocumentToCache(IRequestContext context); void RetrievedDocumentFromCache(IRequestContext context); diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs index 87ec540c8ea..e19fae03328 100644 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs +++ b/src/HotChocolate/Core/src/Execution/Instrumentation/NoOpDiagnosticEvents.cs @@ -40,6 +40,11 @@ public void ResolverError( IError error) { } + public IActivityScope RunTask(IExecutionTask task) => this; + + public void TaskError(IExecutionTask task, IError error) + { } + public void AddedDocumentToCache( IRequestContext context) { } diff --git a/src/HotChocolate/Core/src/Execution/OperationContext.IExecutionTaskContext.cs b/src/HotChocolate/Core/src/Execution/OperationContext.IExecutionTaskContext.cs new file mode 100644 index 00000000000..97d90778139 --- /dev/null +++ b/src/HotChocolate/Core/src/Execution/OperationContext.IExecutionTaskContext.cs @@ -0,0 +1,29 @@ +using System; + +namespace HotChocolate.Execution +{ + internal sealed partial class OperationContext : IExecutionTaskContext + { + void IExecutionTaskContext.ReportError(IExecutionTask task, IError error) => + ReportError(task, error); + + void IExecutionTaskContext.ReportError(IExecutionTask task, Exception exception) => + ReportError(task, ErrorHandler.CreateUnexpectedError(exception).Build()); + + private void ReportError(IExecutionTask task, IError error) + { + error = ErrorHandler.Handle(error); + Result.AddError(error); + DiagnosticEvents.TaskError(task, error); + } + + void IExecutionTaskContext.Started() => + Execution.TaskStats.TaskStarted(); + + void IExecutionTaskContext.Completed() => + Execution.TaskStats.TaskCompleted(); + + IDisposable IExecutionTaskContext.Track(IExecutionTask task) => + DiagnosticEvents.RunTask(task); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs b/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs index cc2cb579c9f..6d47cf50ee2 100644 --- a/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs +++ b/src/HotChocolate/Core/src/Execution/OperationContext.Pooling.cs @@ -15,7 +15,7 @@ public OperationContext( ObjectPool resolverTaskPool, ResultPool resultPool) { - _executionContext = new ExecutionContext(resolverTaskPool); + _executionContext = new ExecutionContext(this, resolverTaskPool); _resultHelper = new ResultHelper(resultPool); } @@ -28,7 +28,9 @@ public void Initialize( IVariableValueCollection variables) { _requestContext = requestContext; - _executionContext.Initialize(batchDispatcher, requestContext.RequestAborted); + _executionContext.Initialize( + batchDispatcher, + requestContext.RequestAborted); Operation = operation; RootValue = rootValue; Variables = variables; diff --git a/src/HotChocolate/Core/src/Execution/RequestExecutor.cs b/src/HotChocolate/Core/src/Execution/RequestExecutor.cs index 4d9df4b2965..6b8bec0437b 100644 --- a/src/HotChocolate/Core/src/Execution/RequestExecutor.cs +++ b/src/HotChocolate/Core/src/Execution/RequestExecutor.cs @@ -6,6 +6,7 @@ using HotChocolate.Execution.Instrumentation; using HotChocolate.Execution.Utilities; using HotChocolate.Utilities; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Execution { @@ -22,7 +23,7 @@ internal sealed class RequestExecutor : IRequestExecutor public RequestExecutor( ISchema schema, IServiceProvider applicationServices, - IServiceProvider schemaServices, + IServiceProvider executorServices, IErrorHandler errorHandler, ITypeConverter converter, IActivator activator, @@ -33,8 +34,8 @@ public RequestExecutor( throw new ArgumentNullException(nameof(schema)); _services = applicationServices ?? throw new ArgumentNullException(nameof(applicationServices)); - Services = schemaServices ?? - throw new ArgumentNullException(nameof(schemaServices)); + Services = executorServices ?? + throw new ArgumentNullException(nameof(executorServices)); _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); _converter = converter ?? @@ -61,26 +62,42 @@ public async Task ExecuteAsync( throw new ArgumentNullException(nameof(request)); } - var context = new RequestContext( - Schema, - request.Services ?? _services, - _errorHandler, - _converter, - _activator, - _diagnosticEvents, - request) + IServiceScope? scope = request.Services is null ? _services.CreateScope() : null; + IServiceProvider services = scope is null ? request.Services! : scope.ServiceProvider; + + try { - RequestAborted = cancellationToken - }; + var context = new RequestContext( + Schema, + services, + _errorHandler, + _converter, + _activator, + _diagnosticEvents, + request) + { + RequestAborted = cancellationToken + }; + + await _requestDelegate(context).ConfigureAwait(false); + + if (context.Result is null) + { + throw new InvalidOperationException(); + } - await _requestDelegate(context).ConfigureAwait(false); + if(scope is not null && context.Result is SubscriptionResult result) + { + context.Result = new SubscriptionResult(result, scope); + scope = null; + } - if (context.Result is null) + return context.Result; + } + finally { - throw new InvalidOperationException(); + scope?.Dispose(); } - - return context.Result; } public Task ExecuteBatchAsync( diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs b/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs index 12f197de592..aa4e3f3403e 100644 --- a/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs +++ b/src/HotChocolate/Core/src/Execution/Utilities/ITaskBacklog.cs @@ -5,7 +5,7 @@ namespace HotChocolate.Execution.Utilities { /// - /// The task backlog of the execution engine stores + /// The task backlog of the execution engine stores /// without any guaranteed order. /// internal interface ITaskBacklog @@ -23,7 +23,7 @@ internal interface ITaskBacklog /// The task is not null when the method returnstrue /// /// Return true if there was a task on the backlog. - bool TryTake([NotNullWhen(true)] out ITask? task); + bool TryTake([NotNullWhen(true)] out IExecutionTask? task); /// /// Waits for either the to raise or @@ -39,5 +39,7 @@ internal interface ITaskBacklog /// Registers work with the task backlog. /// void Register(ResolverTaskDefinition taskDefinition); + + void Register(IExecutionTask task); } } diff --git a/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs b/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs index fc3447c9297..723f092ea10 100644 --- a/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs +++ b/src/HotChocolate/Core/src/Execution/Utilities/NoopBatchDispatcher.cs @@ -10,7 +10,7 @@ internal class NoopBatchDispatcher public event EventHandler? TaskEnqueued; - public void Dispatch() + public void Dispatch(Action enqueue) { } diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs b/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs index 2cf7e751f27..98df5078479 100644 --- a/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs +++ b/src/HotChocolate/Core/src/Execution/Utilities/ResolverExecutionHelper.cs @@ -34,7 +34,7 @@ private static async Task ExecuteResolvers( !executionContext.IsCompleted) { while (!cancellationToken.IsCancellationRequested && - executionContext.TaskBacklog.TryTake(out ITask? task)) + executionContext.TaskBacklog.TryTake(out IExecutionTask? task)) { task.BeginExecute(); } diff --git a/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs b/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs index 946ecfed26b..e6c85c5a094 100644 --- a/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs +++ b/src/HotChocolate/Core/src/Execution/Utilities/ResolverTask.cs @@ -7,7 +7,7 @@ namespace HotChocolate.Execution.Utilities { - internal sealed partial class ResolverTask : ITask + internal sealed partial class ResolverTask : IExecutionTask { private readonly MiddlewareContext _context = new MiddlewareContext(); private ValueTask _task; diff --git a/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs b/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs index a710816a399..788a41fb7e4 100644 --- a/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs +++ b/src/HotChocolate/Core/src/Execution/Utilities/TaskBacklog.cs @@ -11,8 +11,8 @@ internal class TaskBacklog : ITaskBacklog { private readonly ObjectPool _resolverTaskPool; private readonly ITaskStatistics _stats; - private UnsortedChannel _channel = - new UnsortedChannel(true); + private UnsortedChannel _channel = + new UnsortedChannel(true); internal TaskBacklog( ITaskStatistics stats, @@ -26,7 +26,7 @@ internal TaskBacklog( public bool IsEmpty => _channel.IsEmpty; /// - public bool TryTake([NotNullWhen(true)] out ITask? task) => + public bool TryTake([NotNullWhen(true)] out IExecutionTask? task) => _channel.Reader.TryRead(out task); /// @@ -47,15 +47,20 @@ public void Register(ResolverTaskDefinition taskDefinition) taskDefinition.Path, taskDefinition.ScopedContextData); + Register(resolverTask); + } + + public void Register(IExecutionTask task) + { _stats.TaskCreated(); - _channel.Writer.TryWrite(resolverTask); + _channel.Writer.TryWrite(task); } public void Complete() => _channel.Writer.Complete(); public void Reset() { - _channel = new UnsortedChannel(true); + _channel = new UnsortedChannel(true); } } } diff --git a/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs b/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs index 629fac77b76..a76e4774f64 100644 --- a/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs +++ b/src/HotChocolate/Core/src/Fetching/AutoBatchScheduler.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using GreenDonut; namespace HotChocolate.Fetching @@ -7,6 +8,8 @@ public class AutoBatchScheduler : IBatchScheduler , IAutoBatchDispatcher { - public void Schedule(Action dispatch) => dispatch(); + public void Schedule(Func dispatch) => dispatch(); + + public static AutoBatchScheduler Default { get; } = new AutoBatchScheduler(); } } diff --git a/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs b/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs index bf3423bcfec..0356b2aed3e 100644 --- a/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs +++ b/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs @@ -1,9 +1,9 @@ -using System.Security.Cryptography; using System; using System.Collections.Concurrent; -using System.Threading; +using System.Collections.Generic; using System.Threading.Tasks; using GreenDonut; +using HotChocolate.Execution; namespace HotChocolate.Fetching { @@ -11,24 +11,100 @@ public class BatchScheduler : IBatchScheduler , IBatchDispatcher { - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); + private readonly ConcurrentQueue> _queue = + new ConcurrentQueue>(); public bool HasTasks => _queue.Count > 0; public event EventHandler? TaskEnqueued; - public void Dispatch() + public void Dispatch(Action enqueue) { - while (_queue.TryDequeue(out Action? dispatch)) + lock (_queue) { - dispatch(); + if (_queue.Count > 0) + { + var tasks = new List>(); + + while (_queue.TryDequeue(out Func? dispatch)) + { + tasks.Add(dispatch); + } + + enqueue(new BatchExecutionTaskDefinition(tasks)); + } } } - public void Schedule(Action dispatch) + public void Schedule(Func dispatch) { _queue.Enqueue(dispatch); TaskEnqueued?.Invoke(this, EventArgs.Empty); } + + private class BatchExecutionTaskDefinition + : IExecutionTaskDefinition + { + private readonly IReadOnlyList> _tasks; + + public BatchExecutionTaskDefinition(IReadOnlyList> tasks) + { + _tasks = tasks; + } + + public IExecutionTask Create(IExecutionTaskContext context) + { + return new BatchExecutionTask(context, _tasks); + } + } + + private class BatchExecutionTask + : IExecutionTask + { + private readonly IExecutionTaskContext _context; + private readonly IReadOnlyList> _tasks; + private ValueTask _task; + + public BatchExecutionTask( + IExecutionTaskContext context, + IReadOnlyList> tasks) + { + _context = context; + _tasks = tasks; + } + + public void BeginExecute() + { + _context.Started(); + _task = ExecuteAsync(); + } + + public bool IsCompleted => _task.IsCompleted; + + private async ValueTask ExecuteAsync() + { + try + { + using (_context.Track(this)) + { + foreach (Func task in _tasks) + { + try + { + await task.Invoke().ConfigureAwait(false); + } + catch (Exception ex) + { + _context.ReportError(this, ex); + } + } + } + } + finally + { + _context.Completed(); + } + } + } } } diff --git a/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs b/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs index 5ac134c13b6..14676723432 100644 --- a/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs +++ b/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs @@ -1,13 +1,14 @@ using System; +using HotChocolate.Execution; namespace HotChocolate.Fetching { public interface IBatchDispatcher { - bool HasTasks { get; } - event EventHandler? TaskEnqueued; - void Dispatch(); + bool HasTasks { get; } + + void Dispatch(Action enqueue); } } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs index 400f77e6195..1a708fce515 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/BatchDataLoader.cs @@ -1,19 +1,23 @@ -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using GreenDonut; +#nullable enable + namespace HotChocolate.DataLoader { public abstract class BatchDataLoader : DataLoaderBase + where TKey : notnull { - protected BatchDataLoader(IBatchScheduler batchScheduler) - : base(batchScheduler) + protected BatchDataLoader( + IBatchScheduler batchScheduler, + DataLoaderOptions? options = null) + : base(batchScheduler, options) { } - protected sealed override async Task>> FetchAsync( + protected sealed override async ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken) { diff --git a/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs index 7fcbfb0b83a..62728012e3d 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/CacheDataLoader.cs @@ -5,20 +5,21 @@ using GreenDonut; using HotChocolate.Fetching; +#nullable enable + namespace HotChocolate.DataLoader { public abstract class CacheDataLoader : DataLoaderBase + where TKey : notnull { - protected CacheDataLoader(IAutoBatchDispatcher batchScheduler, FetchCache fetch) - : base(batchScheduler) - { } - - protected CacheDataLoader(IAutoBatchDispatcher batchScheduler, int cacheSize) - : base(batchScheduler, new DataLoaderOptions { CacheSize = cacheSize }) + protected CacheDataLoader(int cacheSize) + : base( + AutoBatchScheduler.Default, + new DataLoaderOptions { CacheSize = cacheSize, MaxBatchSize = 1 }) { } - protected sealed override async Task>> FetchAsync( + protected sealed override async ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken) { @@ -40,6 +41,8 @@ protected sealed override async Task>> FetchAsync( return items; } - protected abstract Task LoadSingleAsync(TKey key, CancellationToken cancellationToken); + protected abstract Task LoadSingleAsync( + TKey key, + CancellationToken cancellationToken); } } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderBatchOperation.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderBatchOperation.cs deleted file mode 100644 index b812636f5fd..00000000000 --- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderBatchOperation.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using GreenDonut; -using HotChocolate.Execution; - -namespace HotChocolate.DataLoader -{ - public sealed class DataLoaderBatchOperation - : IBatchOperation - , IObserver - { - private readonly object _sync = new object(); - - private ImmutableHashSet _touched = - ImmutableHashSet.Empty; - - public int BufferSize => _touched.Count; - - public event EventHandler BufferedRequests; - - public async Task InvokeAsync(CancellationToken cancellationToken) - { - /* - foreach (IDataLoader dataLoader in GetTouchedDataLoaders()) - { - if (dataLoader.BufferedRequests > 0) - { - await dataLoader.DispatchAsync(cancellationToken) - .ConfigureAwait(false); - } - } - */ - throw new NotImplementedException(); - } - - private void RequestBuffered(IDataLoader sender, EventArgs eventArgs) - { - if (!_touched.Contains(sender)) - { - lock (_sync) - { - _touched = _touched.Add(sender); - } - } - - BufferedRequests(this, EventArgs.Empty); - } - - private ImmutableHashSet GetTouchedDataLoaders() - { - lock (_sync) - { - ImmutableHashSet touched = _touched; - _touched = ImmutableHashSet.Empty; - return touched; - } - } - - public void OnNext(IDataLoader value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (!_touched.Contains(value)) - { - lock (_sync) - { - if (!_touched.Contains(value)) - { - _touched = _touched.Add(value); - // value.RequestBuffered += RequestBuffered; - throw new NotImplementedException(); - } - } - } - - BufferedRequests(this, EventArgs.Empty); - } - - public void OnCompleted() - { - } - - public void OnError(Exception error) - { - } - } -} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs index 9b68177a92d..09d79b217fa 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderDefaults.cs @@ -3,7 +3,5 @@ internal static class DataLoaderDefaults { public const int CacheSize = 100; - public const int MinCacheSize = 1; - public const int MaxBatchSize = 0; } } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistry.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistry.cs deleted file mode 100644 index 9417c4d690f..00000000000 --- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistry.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Immutable; -using GreenDonut; -using HotChocolate.Properties; - -namespace HotChocolate.DataLoader -{ - public sealed class DataLoaderRegistry - : IDataLoaderRegistry - , IDisposable - { - private readonly object _sync = new object(); - private readonly ConcurrentDictionary> _factories = - new ConcurrentDictionary>(); - private readonly ConcurrentDictionary _instances = - new ConcurrentDictionary(); - private ImmutableHashSet> _observers = - ImmutableHashSet>.Empty; - private readonly IServiceProvider _services; - - public DataLoaderRegistry(IServiceProvider services) - { - _services = services - ?? throw new ArgumentNullException(nameof(services)); - } - - public IDisposable Subscribe(IObserver observer) - { - if (observer == null) - { - throw new ArgumentNullException(nameof(observer)); - } - - lock (_sync) - { - _observers = _observers.Add(observer); - } - - return new ObserverSession(() => - { - lock (_sync) - { - _observers = _observers.Remove(observer); - } - }); - } - - public bool Register(string key, Func factory) - where T : IDataLoader - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - return _factories.TryAdd(key, () => factory(_services)); - } - - public bool TryGet(string key, out T dataLoader) - where T : IDataLoader - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (!_instances.TryGetValue(key, out IDataLoader loader) - && _factories.TryGetValue(key, out Func factory)) - { - lock (factory) - { - if (!_instances.TryGetValue(key, out loader)) - { - loader = _instances.GetOrAdd(key, k => factory()); - NotifyObservers(loader); - } - } - } - - if (loader is T l) - { - dataLoader = l; - return true; - } - - dataLoader = default(T); - return false; - } - - private void NotifyObservers(IDataLoader dataLoader) - { - foreach (IObserver observer in _observers) - { - observer.OnNext(dataLoader); - } - } - - public void Dispose() - { - foreach (IObserver observer in _observers) - { - observer.OnCompleted(); - } - - foreach (IDataLoader dataLoader in _instances.Values) - { - if (dataLoader is IDisposable d) - { - d.Dispose(); - } - } - - _observers = _observers.Clear(); - _instances.Clear(); - } - - private class ObserverSession - : IDisposable - { - private readonly Action _unregister; - - public ObserverSession(Action unregister) - { - _unregister = unregister; - } - - public void Dispose() - { - _unregister(); - } - } - } -} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistryExtensions.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistryExtensions.cs deleted file mode 100644 index d75bb19a580..00000000000 --- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderRegistryExtensions.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using GreenDonut; -using HotChocolate.Properties; -using HotChocolate.Utilities; - -namespace HotChocolate.DataLoader -{ - public static class DataLoaderRegistryExtensions - { - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchBatchFactory factory) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - new FetchBatchDataLoader( - factory(services))); - */ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchBatch fetch) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - new FetchBatchDataLoader(fetch));*/ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchGroupeFactory factory) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - new FetchGroupedDataLoader( - factory(services)));*/ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchGroup fetch) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - new FetchGroupedDataLoader(fetch)); - */ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchCacheFactory factory) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - new FetchSingleDataLoader( - factory(services))); - */ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchCache fetch) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - new FetchSingleDataLoader(fetch)); - */ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchOnceFactory factory) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - throw new NotImplementedException(); - -/* - return registry.Register(key, services => - { - FetchOnce fetch = factory(services); - return new FetchSingleDataLoader( - k => fetch(), DataLoaderDefaults.MinCacheSize); - });*/ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key, - FetchOnce fetch) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - throw new NotImplementedException(); - - /* - return registry.Register(key, services => - { - return new FetchSingleDataLoader( - k => fetch(), DataLoaderDefaults.MinCacheSize); - }); - */ - } - - public static bool Register( - this IDataLoaderRegistry registry, - string key) - where T : class, IDataLoader - { - CreateServiceDelegate createInstance = ActivatorHelper.CompileFactory(); - return registry.Register(key, s => createInstance(s)); - } - - public static bool Register( - this IDataLoaderRegistry registry) - where T : class, IDataLoader - { - CreateServiceDelegate createInstance = ActivatorHelper.CompileFactory(); - return registry.Register(typeof(T).FullName, s => createInstance(s)); - } - } -} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs index 41f06b86e37..842c2064ebb 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/DataLoaderResolverContextExtensions.cs @@ -1,66 +1,54 @@ using System; -using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using GreenDonut; using HotChocolate.DataLoader; using HotChocolate.Properties; using HotChocolate.Utilities; +using static HotChocolate.Properties.TypeResources; + +#nullable enable namespace HotChocolate.Resolvers { public static class DataLoaderResolverContextExtensions { - private static IDataLoader BatchDataLoaderFactory( - this IResolverContext context, - string key, - FetchBatchFactory factory) - { - if (TryGetDataLoader(context, key, - out IDataLoader dataLoader, - out IDataLoaderRegistry registry)) - { - return dataLoader; - } - - return GetOrCreate>( - key, registry, r => r.Register(key, factory)); - } - public static IDataLoader BatchDataLoader( this IResolverContext context, - string key, - FetchBatch fetch) + FetchBatch fetch, + string? key = null) + where TKey : notnull { - if (context == null) + if (context is null) { throw new ArgumentNullException(nameof(context)); } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) + if (fetch is null) { throw new ArgumentNullException(nameof(fetch)); } - return BatchDataLoaderFactory(context, key, services => fetch); + IServiceProvider services = context.Services; + IDataLoaderRegistry reg = services.GetRequiredService(); + Func> createDataLoader = + () => new FetchBatchDataLoader( + services.GetRequiredService(), + fetch); + + return key is null + ? reg.GetOrRegister(createDataLoader) + : reg.GetOrRegister(key, createDataLoader); } + [Obsolete] public static IDataLoader BatchDataLoader( this IResolverContext context, string key, - FetchBatchCt fetch) + FetchBatch fetch) + where TKey : notnull { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - if (string.IsNullOrEmpty(key)) { throw new ArgumentException( @@ -68,68 +56,44 @@ public static IDataLoader BatchDataLoader( nameof(key)); } - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - return BatchDataLoader( - context, - key, - keys => fetch(keys, context.RequestAborted)); - } - - private static IDataLoader GroupDataLoaderFactory( - this IResolverContext context, - string key, - FetchGroupeFactory factory) - { - if (TryGetDataLoader(context, key, - out IDataLoader dataLoader, - out IDataLoaderRegistry registry)) - { - return dataLoader; - } - - return GetOrCreate>( - key, registry, r => r.Register(key, factory)); + return BatchDataLoader(context, fetch, key); } public static IDataLoader GroupDataLoader( this IResolverContext context, - string key, - FetchGroup fetch) + FetchGroup fetch, + string? key = null) + where TKey : notnull { - if (context == null) + if (context is null) { throw new ArgumentNullException(nameof(context)); } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) + if (fetch is null) { throw new ArgumentNullException(nameof(fetch)); } - return GroupDataLoaderFactory(context, key, services => fetch); + IServiceProvider services = context.Services; + IDataLoaderRegistry reg = services.GetRequiredService(); + Func> createDataLoader = + () => new FetchGroupedDataLoader( + services.GetRequiredService(), + fetch); + + return key is null + ? reg.GetOrRegister(createDataLoader) + : reg.GetOrRegister(key, createDataLoader); } + [Obsolete] public static IDataLoader GroupDataLoader( this IResolverContext context, string key, - FetchGroupCt fetch) + FetchGroup fetch) + where TKey : notnull { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - if (string.IsNullOrEmpty(key)) { throw new ArgumentException( @@ -137,68 +101,46 @@ public static IDataLoader GroupDataLoader( nameof(key)); } - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - return GroupDataLoader( - context, - key, - keys => fetch(keys, context.RequestAborted)); - } - - private static IDataLoader CacheDataLoaderFactory( - this IResolverContext context, - string key, - FetchCacheFactory factory) - { - if (TryGetDataLoader(context, key, - out IDataLoader dataLoader, - out IDataLoaderRegistry registry)) - { - return dataLoader; - } - - return GetOrCreate>( - key, registry, r => r.Register(key, factory)); + return GroupDataLoader(context, fetch, key); } public static IDataLoader CacheDataLoader( this IResolverContext context, - string key, - FetchCache fetch) + FetchCacheCt fetch, + string? key = null, + int cacheSize = DataLoaderDefaults.CacheSize) + where TKey : notnull { - if (context == null) + if (context is null) { throw new ArgumentNullException(nameof(context)); } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) + if (fetch is null) { throw new ArgumentNullException(nameof(fetch)); } - return CacheDataLoaderFactory(context, key, services => fetch); + IServiceProvider services = context.Services; + IDataLoaderRegistry reg = services.GetRequiredService(); + Func> createDataLoader = + () => new FetchCacheDataLoader( + fetch, + cacheSize); + + return key is null + ? reg.GetOrRegister(createDataLoader) + : reg.GetOrRegister(key, createDataLoader); } + [Obsolete] public static IDataLoader CacheDataLoader( this IResolverContext context, string key, - FetchCacheCt fetch) + FetchCacheCt fetch, + int cacheSize = DataLoaderDefaults.CacheSize) + where TKey : notnull { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - if (string.IsNullOrEmpty(key)) { throw new ArgumentException( @@ -206,68 +148,38 @@ public static IDataLoader CacheDataLoader( nameof(key)); } - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - return CacheDataLoader( - context, - key, - keys => fetch(keys, context.RequestAborted)); - } - - private static Func> FetchOnceFactory( - this IResolverContext context, - string key, - FetchOnceFactory factory) - { - if (!TryGetDataLoader(context, key, - out IDataLoader dataLoader, - out IDataLoaderRegistry registry)) - { - dataLoader = GetOrCreate>( - key, registry, r => r.Register(key, factory)); - } - - return () => dataLoader.LoadAsync("none"); + return CacheDataLoader(context, fetch, key, cacheSize); } public static Task FetchOnceAsync( this IResolverContext context, - string key, - FetchOnce fetch) + Func> fetch, + string? key = null) { - if (context == null) + if (context is null) { throw new ArgumentNullException(nameof(context)); } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (fetch == null) + if (fetch is null) { throw new ArgumentNullException(nameof(fetch)); } - return FetchOnceFactory(context, key, services => fetch)(); + return CacheDataLoader( + context, + (k, ct) => fetch(ct), + key, + cacheSize: 1) + .LoadAsync("default", context.RequestAborted); } + [Obsolete] public static Task FetchOnceAsync( this IResolverContext context, string key, - FetchOnceCt fetch) + Func> fetch) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - if (string.IsNullOrEmpty(key)) { throw new ArgumentException( @@ -275,108 +187,68 @@ public static Task FetchOnceAsync( nameof(key)); } - if (fetch == null) - { - throw new ArgumentNullException(nameof(fetch)); - } - - return FetchOnceAsync( - context, - key, - () => fetch(context.RequestAborted)); + return FetchOnceAsync(context, fetch, key); } - public static T DataLoader( - this IResolverContext context, - string key) - where T : class, IDataLoader + public static T DataLoader(this IResolverContext context, string key) + where T : notnull, IDataLoader { - if (context == null) + if (context is null) { throw new ArgumentNullException(nameof(context)); } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - TypeResources.DataLoaderRegistry_KeyNullOrEmpty, - nameof(key)); - } - - if (TryGetDataLoader(context, key, - out T dataLoader, - out IDataLoaderRegistry registry)) + if (key is null) { - return dataLoader; + throw new ArgumentNullException(nameof(key)); } - return GetOrCreate - ( - key, - registry, - r => r.Register(key, services => - { - var instance = (T)services.GetService(typeof(T)); - return instance is null - ? ActivatorHelper.CompileFactory().Invoke(services) - : instance; - }) - ); + IServiceProvider services = context.Services; + IDataLoaderRegistry reg = services.GetRequiredService(); + return reg.GetOrRegister(key, () => CreateDataLoader(services)); } - public static T DataLoader( - this IResolverContext context) - where T : class, IDataLoader => - DataLoader(context, typeof(T).FullName); - - private static bool TryGetDataLoader( - IResolverContext context, - string key, - out T dataLoader, - out IDataLoaderRegistry registry) - where T : IDataLoader + public static T DataLoader(this IResolverContext context) + where T : notnull, IDataLoader { - registry = null; - - foreach (IDataLoaderRegistry current in - context.Service>()) + if (context is null) { - registry = current; - - if (current.TryGet(key, out dataLoader)) - { - return true; - } + throw new ArgumentNullException(nameof(context)); } - dataLoader = default; - return false; + IServiceProvider services = context.Services; + IDataLoaderRegistry reg = services.GetRequiredService(); + return reg.GetOrRegister(() => CreateDataLoader(services)); } - private static T GetOrCreate( - string key, - IDataLoaderRegistry registry, - Action register) + private static T CreateDataLoader(IServiceProvider services) where T : IDataLoader { - if (registry == null) - { - throw new InvalidOperationException(TypeResources - .DataLoaderResolverContextExtensions_RegistryIsNull); - } + T registeredDataLoader = services.GetService(); - if (!registry.TryGet(key, out T dataLoader)) + if (registeredDataLoader is null) { - register(registry); + if (typeof(T).IsInterface || typeof(T).IsAbstract) + { + throw new RegisterDataLoaderException( + string.Format( + DataLoaderResolverContextExtensions_CreateDataLoader_AbstractType, + typeof(T).FullName ?? typeof(T).Name)); + } - if (!registry.TryGet(key, out dataLoader)) + var factory = new ServiceFactory { Services = services }; + if (factory.CreateInstance(typeof(T)) is T dataLoader) { - throw new InvalidOperationException(TypeResources - .DataLoaderResolverContextExtensions_UnableToRegister); + return dataLoader; } + + throw new RegisterDataLoaderException( + string.Format( + DataLoaderResolverContextExtensions_CreateDataLoader_UnableToCreate, + typeof(T).FullName ?? typeof(T).Name)); } - return dataLoader; + return registeredDataLoader; } } } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/DefaultDataLoaderRegistry.cs b/src/HotChocolate/Core/src/Types/DataLoader/DefaultDataLoaderRegistry.cs new file mode 100644 index 00000000000..91b9ae08dcb --- /dev/null +++ b/src/HotChocolate/Core/src/Types/DataLoader/DefaultDataLoaderRegistry.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using GreenDonut; +using HotChocolate.Properties; + +#nullable enable + +namespace HotChocolate.DataLoader +{ + public class DefaultDataLoaderRegistry : IDataLoaderRegistry + { + private readonly ConcurrentDictionary _dataLoaders = + new ConcurrentDictionary(); + + public T GetOrRegister( + string key, + Func createDataLoader) + where T : IDataLoader + { + if (_dataLoaders.GetOrAdd(key, s => createDataLoader()) is T dataLoader) + { + return dataLoader; + } + + throw new RegisterDataLoaderException( + string.Format( + TypeResources.DefaultDataLoaderRegistry_GetOrRegister, + key, + typeof(T).FullName)); + } + + public T GetOrRegister( + Func createDataLoader) + where T : IDataLoader => + GetOrRegister(typeof(T).FullName ?? typeof(T).Name, createDataLoader); + + public void Dispose() + { + foreach (IDisposable disposable in _dataLoaders.Values.OfType()) + { + disposable.Dispose(); + } + } + } +} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchBatch.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchBatch.cs new file mode 100644 index 00000000000..aa38cbe2a63 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/DataLoader/FetchBatch.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace HotChocolate.DataLoader +{ + public delegate Task> FetchBatch( + IReadOnlyList keys, + CancellationToken cancellationToken) + where TKey : notnull; +} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchBatchDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchBatchDataLoader.cs index dfc65c359ee..3e398191700 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/FetchBatchDataLoader.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/FetchBatchDataLoader.cs @@ -4,14 +4,19 @@ using System.Threading.Tasks; using GreenDonut; +#nullable enable + namespace HotChocolate.DataLoader { internal sealed class FetchBatchDataLoader : BatchDataLoader + where TKey : notnull { private readonly FetchBatch _fetch; - public FetchBatchDataLoader(IBatchScheduler batchScheduler, FetchBatch fetch) + public FetchBatchDataLoader( + IBatchScheduler batchScheduler, + FetchBatch fetch) : base(batchScheduler) { _fetch = fetch ?? throw new ArgumentNullException(nameof(fetch)); @@ -19,6 +24,7 @@ public FetchBatchDataLoader(IBatchScheduler batchScheduler, FetchBatch> LoadBatchAsync( IReadOnlyList keys, - CancellationToken cancellationToken) => _fetch(keys); + CancellationToken cancellationToken) => + _fetch(keys, cancellationToken); } } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchCache.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchCache.cs new file mode 100644 index 00000000000..bb9076c4d3a --- /dev/null +++ b/src/HotChocolate/Core/src/Types/DataLoader/FetchCache.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace HotChocolate.DataLoader +{ + public delegate Task FetchCacheCt( + TKey key, + CancellationToken cancellationToken) + where TKey : notnull; +} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchCacheDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchCacheDataLoader.cs new file mode 100644 index 00000000000..a2e8ce668f9 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/DataLoader/FetchCacheDataLoader.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace HotChocolate.DataLoader +{ + internal sealed class FetchCacheDataLoader + : CacheDataLoader + where TKey : notnull + { + private readonly FetchCacheCt _fetch; + + public FetchCacheDataLoader( + FetchCacheCt fetch, + int cacheSize) + : base(cacheSize) + { + _fetch = fetch; + } + + protected override Task LoadSingleAsync( + TKey key, + CancellationToken cancellationToken) => + _fetch(key, cancellationToken); + } +} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchGroup.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchGroup.cs new file mode 100644 index 00000000000..1572d494ab9 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/DataLoader/FetchGroup.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GreenDonut; + +#nullable enable + +namespace HotChocolate.DataLoader +{ + public delegate Task> FetchGroup( + IReadOnlyList keys, + CancellationToken cancellationToken) + where TKey : notnull; +} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchGroupedDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchGroupedDataLoader.cs index 7e1d7a59e70..4d0c8bb9ef7 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/FetchGroupedDataLoader.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/FetchGroupedDataLoader.cs @@ -5,16 +5,19 @@ using System.Threading.Tasks; using GreenDonut; +#nullable enable + namespace HotChocolate.DataLoader { internal sealed class FetchGroupedDataLoader : GroupedDataLoader + where TKey : notnull { private readonly FetchGroup _fetch; public FetchGroupedDataLoader( IBatchScheduler batchScheduler, - FetchGroup fetch) + FetchGroup fetch) : base(batchScheduler) { _fetch = fetch ?? throw new ArgumentNullException(nameof(fetch)); @@ -22,9 +25,7 @@ public FetchGroupedDataLoader( protected override Task> LoadGroupedBatchAsync( IReadOnlyList keys, - CancellationToken cancellationToken) - { - return _fetch(keys); - } + CancellationToken cancellationToken) => + _fetch(keys, cancellationToken); } } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/FetchSingleDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/FetchSingleDataLoader.cs deleted file mode 100644 index 64564e2db0e..00000000000 --- a/src/HotChocolate/Core/src/Types/DataLoader/FetchSingleDataLoader.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using HotChocolate.Fetching; - -namespace HotChocolate.DataLoader -{ - internal sealed class FetchSingleDataLoader - : CacheDataLoader - { - private readonly FetchCache _fetch; - - public FetchSingleDataLoader( - IAutoBatchDispatcher batchScheduler, - FetchCache fetch) - : this(batchScheduler, fetch, DataLoaderDefaults.CacheSize) - { - } - - public FetchSingleDataLoader( - IAutoBatchDispatcher batchScheduler, - FetchCache fetch, - int cacheSize) - : base(batchScheduler, cacheSize) - { - _fetch = fetch ?? throw new ArgumentNullException(nameof(fetch)); - } - - protected override Task LoadSingleAsync( - TKey key, - CancellationToken cancellationToken) => - _fetch(key); - } -} diff --git a/src/HotChocolate/Core/src/Types/DataLoader/GroupedDataLoader.cs b/src/HotChocolate/Core/src/Types/DataLoader/GroupedDataLoader.cs index a404f126257..d83b5c6a4fb 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/GroupedDataLoader.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/GroupedDataLoader.cs @@ -4,16 +4,21 @@ using System.Threading.Tasks; using GreenDonut; +#nullable enable + namespace HotChocolate.DataLoader { public abstract class GroupedDataLoader : DataLoaderBase + where TKey : notnull { - protected GroupedDataLoader(IBatchScheduler batchScheduler) - : base(batchScheduler) + protected GroupedDataLoader( + IBatchScheduler batchScheduler, + DataLoaderOptions? options = null) + : base(batchScheduler, options) { } - protected sealed override async Task>> FetchAsync( + protected sealed override async ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken) { diff --git a/src/HotChocolate/Core/src/Types/DataLoader/IDataLoaderRegistry.cs b/src/HotChocolate/Core/src/Types/DataLoader/IDataLoaderRegistry.cs index 55168e6ff77..75581f74b6b 100644 --- a/src/HotChocolate/Core/src/Types/DataLoader/IDataLoaderRegistry.cs +++ b/src/HotChocolate/Core/src/Types/DataLoader/IDataLoaderRegistry.cs @@ -1,107 +1,23 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using GreenDonut; +#nullable enable + namespace HotChocolate.DataLoader { - public delegate FetchBatch FetchBatchFactory( - IServiceProvider services); - - public delegate Task> FetchBatch( - IReadOnlyList keys); - - public delegate Task> FetchBatchCt( - IReadOnlyList keys, - CancellationToken cancellationToken); - - public delegate FetchGroup FetchGroupeFactory( - IServiceProvider services); - - public delegate Task> FetchGroup( - IReadOnlyList keys); - - public delegate Task> FetchGroupCt( - IReadOnlyList keys, - CancellationToken cancellationToken); - - public delegate FetchCache FetchCacheFactory( - IServiceProvider services); - - public delegate Task FetchCache(TKey key); - - public delegate Task FetchCacheCt( - TKey key, - CancellationToken cancellationToken); - - public delegate FetchOnce FetchOnceFactory( - IServiceProvider services); - - public delegate Task FetchOnce(); - - public delegate Task FetchOnceCt( - CancellationToken cancellationToken); - /// - /// The DataLoader-registry holds the instances of DataLoders + /// The DataLoader-registry holds the instances of DataLoaders /// that are used by the execution engine. /// - public interface IDataLoaderRegistry - : IObservable + public interface IDataLoaderRegistry : IDisposable { - /// - /// Registers a new DataLoader with this registry. - /// - /// - /// The key with which this DataLoader can be resolved. - /// - /// - /// The factory that can create a instance of the DataLoader - /// when it is needed. - /// - /// - /// The DataLoader type. - /// - /// - /// Returns true if a DataLoader was successfully - /// registered for the specified ; - /// otherwise, false will be returned. - /// - bool Register(string key, Func factory) + T GetOrRegister( + string key, + Func createDataLoader) where T : IDataLoader; - /// - /// Tries to retrieve a DataLoader with the specified - /// . - /// - /// - /// The key with which this DataLoader can be resolved. - /// - /// - /// The retrieved DataLoader instance or null - /// if there is no DataLoader registered for the specified - /// . - /// - /// - /// The DataLoader type. - /// - /// - /// Returns true if a DataLoader was resolved; - /// otherwise, false will be returned. - /// - bool TryGet(string key, out T dataLoader) + T GetOrRegister( + Func createDataLoader) where T : IDataLoader; } - - /* - ctx.BatchDataloader( - "foo", - keys => repository.Get) - - ctx.DataLoader() - - - */ } diff --git a/src/HotChocolate/Core/src/Types/DataLoader/RegisterDataLoaderException.cs b/src/HotChocolate/Core/src/Types/DataLoader/RegisterDataLoaderException.cs new file mode 100644 index 00000000000..d5a449759ba --- /dev/null +++ b/src/HotChocolate/Core/src/Types/DataLoader/RegisterDataLoaderException.cs @@ -0,0 +1,13 @@ +#nullable enable + +namespace HotChocolate.DataLoader +{ + public class RegisterDataLoaderException + : GraphQLException + { + public RegisterDataLoaderException(string message) + : base(message) + { + } + } +} diff --git a/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs b/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs index 3baf53528ac..93e143ff9d0 100644 --- a/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs +++ b/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,46 +11,32 @@ namespace HotChocolate.Properties { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class TypeResources { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal TypeResources() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HotChocolate.Properties.TypeResources", typeof(TypeResources).Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.Properties.TypeResources", typeof(TypeResources).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -60,1430 +45,969 @@ internal TypeResources() { } } - /// - /// Looks up a localized string similar to The argument `{0}` has no type. Specify the type with `.Argument("{0}", a.Type<MyType>())` to fix this issue.. - /// - internal static string Argument_TypeIsNull { - get { - return ResourceManager.GetString("Argument_TypeIsNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The argument type has to be an input-type.. - /// internal static string ArgumentDescriptor_InputTypeViolation { get { return ResourceManager.GetString("ArgumentDescriptor_InputTypeViolation", resourceCulture); } } - /// - /// Looks up a localized string similar to Argument `{0}` of non-null type `{1}` must not be null.. - /// internal static string ArgumentValueBuilder_NonNull { get { return ResourceManager.GetString("ArgumentValueBuilder_NonNull", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Boolean` scalar type represents `true` or `false`.. - /// internal static string BooleanType_Description { get { return ResourceManager.GetString("BooleanType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255.. - /// internal static string ByteType_Description { get { return ResourceManager.GetString("ByteType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified IComplexTypeFieldBindingBuilder-implementation is not supported.. - /// internal static string ComplexTypeBindingBuilder_FieldBuilderNotSupported { get { return ResourceManager.GetString("ComplexTypeBindingBuilder_FieldBuilderNotSupported", resourceCulture); } } - /// - /// Looks up a localized string similar to The field binding builder is not completed and cannot be added.. - /// internal static string ComplexTypeBindingBuilder_FieldNotComplete { get { return ResourceManager.GetString("ComplexTypeBindingBuilder_FieldNotComplete", resourceCulture); } } - /// - /// Looks up a localized string similar to The DataLoader key cannot be null or empty.. - /// internal static string DataLoaderRegistry_KeyNullOrEmpty { get { return ResourceManager.GetString("DataLoaderRegistry_KeyNullOrEmpty", resourceCulture); } } - /// - /// Looks up a localized string similar to No DataLoader registry was registerd with your dependency injection.. - /// internal static string DataLoaderResolverContextExtensions_RegistryIsNull { get { return ResourceManager.GetString("DataLoaderResolverContextExtensions_RegistryIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to register a DataLoader with your DataLoader registry.. - /// internal static string DataLoaderResolverContextExtensions_UnableToRegister { get { return ResourceManager.GetString("DataLoaderResolverContextExtensions_UnableToRegister", resourceCulture); } } - /// - /// Looks up a localized string similar to The `DateTime` scalar represents an ISO-8601 compliant date time type.. - /// internal static string DateTimeType_Description { get { return ResourceManager.GetString("DateTimeType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Date` scalar represents an ISO-8601 compliant date type.. - /// internal static string DateType_Description { get { return ResourceManager.GetString("DateType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The built-in `Decimal` scalar type.. - /// internal static string DecimalType_Description { get { return ResourceManager.GetString("DecimalType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified member has to be a method or a property.. - /// internal static string DefaultTypeInspector_MemberInvalid { get { return ResourceManager.GetString("DefaultTypeInspector_MemberInvalid", resourceCulture); } } - /// - /// Looks up a localized string similar to Only type system objects are allowed as schema type.. - /// internal static string DependencyDescriptorBase_OnlyTsoIsAllowed { get { return ResourceManager.GetString("DependencyDescriptorBase_OnlyTsoIsAllowed", resourceCulture); } } - /// - /// Looks up a localized string similar to A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. - /// - ///In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.. - /// - internal static string Directive_Description { - get { - return ResourceManager.GetString("Directive_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use `locations`.. - /// - internal static string Directive_UseLocation { - get { - return ResourceManager.GetString("Directive_UseLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified directive `@{0}` is unique and cannot be added twice.. - /// internal static string DirectiveCollection_DirectiveIsUnique { get { return ResourceManager.GetString("DirectiveCollection_DirectiveIsUnique", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified directive `@{0}` is not allowed on the current location `{1}`.. - /// internal static string DirectiveCollection_LocationNotAllowed { get { return ResourceManager.GetString("DirectiveCollection_LocationNotAllowed", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an argument definition. - /// internal static string DirectiveLocation_ArgumentDefinition { get { return ResourceManager.GetString("DirectiveLocation_ArgumentDefinition", resourceCulture); } } - /// - /// Looks up a localized string similar to A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.. - /// internal static string DirectiveLocation_Description { get { return ResourceManager.GetString("DirectiveLocation_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an enum definition.. - /// internal static string DirectiveLocation_Enum { get { return ResourceManager.GetString("DirectiveLocation_Enum", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an enum value definition.. - /// internal static string DirectiveLocation_EnumValue { get { return ResourceManager.GetString("DirectiveLocation_EnumValue", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a field.. - /// internal static string DirectiveLocation_Field { get { return ResourceManager.GetString("DirectiveLocation_Field", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a field definition.. - /// internal static string DirectiveLocation_FieldDefinition { get { return ResourceManager.GetString("DirectiveLocation_FieldDefinition", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a fragment definition.. - /// internal static string DirectiveLocation_FragmentDefinition { get { return ResourceManager.GetString("DirectiveLocation_FragmentDefinition", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a fragment spread.. - /// internal static string DirectiveLocation_FragmentSpread { get { return ResourceManager.GetString("DirectiveLocation_FragmentSpread", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an inline fragment.. - /// internal static string DirectiveLocation_InlineFragment { get { return ResourceManager.GetString("DirectiveLocation_InlineFragment", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an input object field definition.. - /// internal static string DirectiveLocation_InputFieldDefinition { get { return ResourceManager.GetString("DirectiveLocation_InputFieldDefinition", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an input object type definition.. - /// internal static string DirectiveLocation_InputObject { get { return ResourceManager.GetString("DirectiveLocation_InputObject", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an interface definition.. - /// internal static string DirectiveLocation_Interface { get { return ResourceManager.GetString("DirectiveLocation_Interface", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a mutation operation.. - /// internal static string DirectiveLocation_Mutation { get { return ResourceManager.GetString("DirectiveLocation_Mutation", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to an object type definition.. - /// internal static string DirectiveLocation_Object { get { return ResourceManager.GetString("DirectiveLocation_Object", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a query operation.. - /// internal static string DirectiveLocation_Query { get { return ResourceManager.GetString("DirectiveLocation_Query", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a scalar definition.. - /// internal static string DirectiveLocation_Scalar { get { return ResourceManager.GetString("DirectiveLocation_Scalar", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a schema definition.. - /// internal static string DirectiveLocation_Schema { get { return ResourceManager.GetString("DirectiveLocation_Schema", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a subscription operation.. - /// internal static string DirectiveLocation_Subscription { get { return ResourceManager.GetString("DirectiveLocation_Subscription", resourceCulture); } } - /// - /// Looks up a localized string similar to Location adjacent to a union definition.. - /// internal static string DirectiveLocation_Union { get { return ResourceManager.GetString("DirectiveLocation_Union", resourceCulture); } } - /// - /// Looks up a localized string similar to The `{0}` directive does not declare any location on which it is valid.. - /// + internal static string DirectiveTypeDescriptor_OnlyProperties { + get { + return ResourceManager.GetString("DirectiveTypeDescriptor_OnlyProperties", resourceCulture); + } + } + + internal static string DirectiveTypeFactory_LocationNotSupported { + get { + return ResourceManager.GetString("DirectiveTypeFactory_LocationNotSupported", resourceCulture); + } + } + internal static string DirectiveType_NoLocations { get { return ResourceManager.GetString("DirectiveType_NoLocations", resourceCulture); } } - /// - /// Looks up a localized string similar to Replace Middleware with `Use`.. - /// internal static string DirectiveType_ReplaceWithUse { get { return ResourceManager.GetString("DirectiveType_ReplaceWithUse", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to convert the argument value to the specified type.. - /// internal static string DirectiveType_UnableToConvert { get { return ResourceManager.GetString("DirectiveType_UnableToConvert", resourceCulture); } } - /// - /// Looks up a localized string similar to Only property expressions are allowed to describe a directive type argument.. - /// - internal static string DirectiveTypeDescriptor_OnlyProperties { + internal static string Directive_Description { get { - return ResourceManager.GetString("DirectiveTypeDescriptor_OnlyProperties", resourceCulture); + return ResourceManager.GetString("Directive_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified location `{0}` is not supported.. - /// - internal static string DirectiveTypeFactory_LocationNotSupported { + internal static string Directive_UseLocation { get { - return ResourceManager.GetString("DirectiveTypeFactory_LocationNotSupported", resourceCulture); + return ResourceManager.GetString("Directive_UseLocation", resourceCulture); } } - /// - /// Looks up a localized string similar to The cursor cannot be null or empty.. - /// internal static string Edge_CursorIsNull { get { return ResourceManager.GetString("Edge_CursorIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to The enum type `{0}` has no values.. - /// - internal static string EnumType_NoValues { - get { - return ResourceManager.GetString("EnumType_NoValues", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The enum type extension can only be merged with an enum type.. - /// internal static string EnumTypeExtension_CannotMerge { get { return ResourceManager.GetString("EnumTypeExtension_CannotMerge", resourceCulture); } } - /// - /// Looks up a localized string similar to The enum value `{0}` of the enum type extension is not assignabe with the target enum type.. - /// internal static string EnumTypeExtension_ValueTypeInvalid { get { return ResourceManager.GetString("EnumTypeExtension_ValueTypeInvalid", resourceCulture); } } - /// - /// Looks up a localized string similar to One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.. - /// - internal static string EnumValue_Description { + internal static string EnumType_NoValues { get { - return ResourceManager.GetString("EnumValue_Description", resourceCulture); + return ResourceManager.GetString("EnumType_NoValues", resourceCulture); } } - /// - /// Looks up a localized string similar to The inner value of enum value cannot be null or empty.. - /// - internal static string EnumValue_ValueIsNull { + internal static string EnumValue_Description { get { - return ResourceManager.GetString("EnumValue_ValueIsNull", resourceCulture); + return ResourceManager.GetString("EnumValue_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.. - /// - internal static string Field_Description { + internal static string EnumValue_ValueIsNull { get { - return ResourceManager.GetString("Field_Description", resourceCulture); + return ResourceManager.GetString("EnumValue_ValueIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to Could not parse the native value of input field `{0}.{1}`.. - /// internal static string FieldInitHelper_InvalidDefaultValue { get { return ResourceManager.GetString("FieldInitHelper_InvalidDefaultValue", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} `{1}` has no fields declared.. - /// internal static string FieldInitHelper_NoFields { get { return ResourceManager.GetString("FieldInitHelper_NoFields", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).. - /// - internal static string FloatType_Description { - get { - return ResourceManager.GetString("FloatType_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to decode the id string.. - /// - internal static string IdSerializer_UnableToDecode { + internal static string Field_Description { get { - return ResourceManager.GetString("IdSerializer_UnableToDecode", resourceCulture); + return ResourceManager.GetString("Field_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to encode data.. - /// - internal static string IdSerializer_UnableToEncode { + internal static string FloatType_Description { get { - return ResourceManager.GetString("IdSerializer_UnableToEncode", resourceCulture); + return ResourceManager.GetString("FloatType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to 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.. - /// internal static string IdType_Description { get { return ResourceManager.GetString("IdType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to set the input field value.. - /// internal static string InputField_CannotSetValue { get { return ResourceManager.GetString("InputField_CannotSetValue", resourceCulture); } } - /// - /// Looks up a localized string similar to The input object type can only parse object value literals.. - /// - internal static string InputObjectType_CannotParseLiteral { + internal static string InputObjectTypeExtension_CannotMerge { get { - return ResourceManager.GetString("InputObjectType_CannotParseLiteral", resourceCulture); + return ResourceManager.GetString("InputObjectTypeExtension_CannotMerge", resourceCulture); } } - /// - /// Looks up a localized string similar to The input object `{0}` does not have any fields.. - /// - internal static string InputObjectType_NoFields { + internal static string InputObjectType_CannotParseLiteral { get { - return ResourceManager.GetString("InputObjectType_NoFields", resourceCulture); + return ResourceManager.GetString("InputObjectType_CannotParseLiteral", resourceCulture); } } - /// - /// Looks up a localized string similar to The input object type extension can only be merged with an input object type.. - /// - internal static string InputObjectTypeExtension_CannotMerge { + internal static string InputObjectType_NoFields { get { - return ResourceManager.GetString("InputObjectTypeExtension_CannotMerge", resourceCulture); + return ResourceManager.GetString("InputObjectType_NoFields", resourceCulture); } } - /// - /// Looks up a localized string similar to The input value of type `{0}` must not be null.. - /// internal static string InputTypeNonNullCheck_ValueIsNull { get { return ResourceManager.GetString("InputTypeNonNullCheck_ValueIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to A GraphQL-formatted string representing the default value for this input value.. - /// internal static string InputValue_DefaultValue { get { return ResourceManager.GetString("InputValue_DefaultValue", resourceCulture); } } - /// - /// Looks up a localized string similar to Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.. - /// internal static string InputValue_Description { get { return ResourceManager.GetString("InputValue_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The arguments of the interface field {0} from interface {1} and {2} do not match and are implemented by object type {3}.. - /// internal static string InterfaceImplRule_ArgumentsDontMatch { get { return ResourceManager.GetString("InterfaceImplRule_ArgumentsDontMatch", resourceCulture); } } - /// - /// Looks up a localized string similar to Object type {0} does not implement all arguments of field {1} from interface {2}.. - /// internal static string InterfaceImplRule_ArgumentsNotImpl { get { return ResourceManager.GetString("InterfaceImplRule_ArgumentsNotImpl", resourceCulture); } } - /// - /// Looks up a localized string similar to Object type {0} does not implement the field {1} from interface {2}.. - /// internal static string InterfaceImplRule_FieldNotImpl { get { return ResourceManager.GetString("InterfaceImplRule_FieldNotImpl", resourceCulture); } } - /// - /// Looks up a localized string similar to The return type of the interface field {0} from interface {1} and {2} do not match and are implemented by object type {3}.. - /// internal static string InterfaceImplRule_FieldTypeInvalid { get { return ResourceManager.GetString("InterfaceImplRule_FieldTypeInvalid", resourceCulture); } } - /// - /// Looks up a localized string similar to The return type of the interface field {0} does not match the field declared by object type {1}.. - /// internal static string InterfaceImplRule_ReturnTypeInvalid { get { return ResourceManager.GetString("InterfaceImplRule_ReturnTypeInvalid", resourceCulture); } } - /// - /// Looks up a localized string similar to The interface base class cannot be used as interface implementation declaration.. - /// - internal static string InterfaceTypeDescriptor_InterfaceBaseClass { - get { - return ResourceManager.GetString("InterfaceTypeDescriptor_InterfaceBaseClass", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The interface type extension can only be merged with an interface type.. - /// internal static string InterfaceTypeExtension_CannotMerge { get { return ResourceManager.GetString("InterfaceTypeExtension_CannotMerge", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.. - /// internal static string IntType_Description { get { return ResourceManager.GetString("IntType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1.. - /// internal static string LongType_Description { get { return ResourceManager.GetString("LongType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The multiplier path scalar represents a valid GraphQL multiplier path string.. - /// internal static string MultiplierPathType_Description { get { return ResourceManager.GetString("MultiplierPathType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The multiplier path scalar represents a valid GraphQL multiplier path string.. - /// - internal static string Name_Cannot_BeEmpty { - get { - return ResourceManager.GetString("Name_Cannot_BeEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name scalar represents a valid GraphQL name as specified in the spec and can be used to refer to fields or types.. - /// internal static string NameType_Description { get { return ResourceManager.GetString("NameType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified type is not an input type.. - /// - internal static string NonNullType_NotAnInputType { - get { - return ResourceManager.GetString("NonNullType_NotAnInputType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The inner type of non-null type must be a nullable type.. - /// - internal static string NonNullType_TypeIsNunNullType { - get { - return ResourceManager.GetString("NonNullType_TypeIsNunNullType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A non null type cannot parse null value literals.. - /// - internal static string NonNullType_ValueIsNull { + internal static string Name_Cannot_BeEmpty { get { - return ResourceManager.GetString("NonNullType_ValueIsNull", resourceCulture); + return ResourceManager.GetString("Name_Cannot_BeEmpty", resourceCulture); } } - /// - /// Looks up a localized string similar to The field-type must be an output-type.. - /// internal static string ObjectFieldDescriptorBase_FieldType { get { return ResourceManager.GetString("ObjectFieldDescriptorBase_FieldType", resourceCulture); } } - /// - /// Looks up a localized string similar to The interface base class cannot be used as interface implementation declaration.. - /// internal static string ObjectTypeDescriptor_InterfaceBaseClass { get { return ResourceManager.GetString("ObjectTypeDescriptor_InterfaceBaseClass", resourceCulture); } } - /// - /// Looks up a localized string similar to A field-expression must be a property-expression or a method-call-expression.. - /// - internal static string ObjectTypeDescriptor_MustBePropertyOrMethod { + internal static string InterfaceTypeDescriptor_InterfaceBaseClass { get { - return ResourceManager.GetString("ObjectTypeDescriptor_MustBePropertyOrMethod", resourceCulture); + return ResourceManager.GetString("InterfaceTypeDescriptor_InterfaceBaseClass", resourceCulture); } } - /// - /// Looks up a localized string similar to Schema types cannot be used as resolver types.. - /// - internal static string ObjectTypeDescriptor_Resolver_SchemaType { + internal static string ObjectTypeDescriptor_MustBePropertyOrMethod { get { - return ResourceManager.GetString("ObjectTypeDescriptor_Resolver_SchemaType", resourceCulture); + return ResourceManager.GetString("ObjectTypeDescriptor_MustBePropertyOrMethod", resourceCulture); } } - /// - /// Looks up a localized string similar to The object type extension can only be merged with an object type.. - /// - internal static string ObjectTypeExtension_CannotMerge { + internal static string ObjectTypeDescriptor_Resolver_SchemaType { get { - return ResourceManager.GetString("ObjectTypeExtension_CannotMerge", resourceCulture); + return ResourceManager.GetString("ObjectTypeDescriptor_Resolver_SchemaType", resourceCulture); } } - /// - /// Looks up a localized string similar to The member expression must specify a property or method that is public and that belongs to the type {0}. - /// internal static string Reflection_MemberMust_BeMethodOrProperty { get { return ResourceManager.GetString("Reflection_MemberMust_BeMethodOrProperty", resourceCulture); } } - /// - /// Looks up a localized string similar to A directive type mustn't be one of the base classes `DirectiveType` or `DirectiveType<T>` but must be a type inheriting from `DirectiveType` or `DirectiveType<T>`.. - /// internal static string ResolverCompiler_UnknownParameterType { get { return ResourceManager.GetString("ResolverCompiler_UnknownParameterType", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified IResolverFieldBindingBuilder-implementation is not supported.. - /// internal static string ResolverTypeBindingBuilder_FieldBuilderNotSupported { get { return ResourceManager.GetString("ResolverTypeBindingBuilder_FieldBuilderNotSupported", resourceCulture); } } - /// - /// Looks up a localized string similar to The field binding builder is not completed and cannot be added.. - /// internal static string ResolverTypeBindingBuilder_FieldNotComplete { get { return ResourceManager.GetString("ResolverTypeBindingBuilder_FieldNotComplete", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} cannot deserialize the given value.. - /// internal static string Scalar_Cannot_Deserialize { get { return ResourceManager.GetString("Scalar_Cannot_Deserialize", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} cannot parse the given literal of type `{1}`.. - /// internal static string Scalar_Cannot_ParseLiteral { get { return ResourceManager.GetString("Scalar_Cannot_ParseLiteral", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} cannot parse the given value of type `{1}`.. - /// internal static string Scalar_Cannot_ParseValue { get { return ResourceManager.GetString("Scalar_Cannot_ParseValue", resourceCulture); } } - /// - /// Looks up a localized string similar to {0} cannot serialize the given value.. - /// internal static string Scalar_Cannot_Serialize { get { return ResourceManager.GetString("Scalar_Cannot_Serialize", resourceCulture); } } - /// - /// Looks up a localized string similar to A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.. - /// - internal static string Schema_Description { - get { - return ResourceManager.GetString("Schema_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A list of all directives supported by this server.. - /// - internal static string Schema_Directives { - get { - return ResourceManager.GetString("Schema_Directives", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If this server supports mutation, the type that mutation operations will be rooted at.. - /// - internal static string Schema_MutationType { - get { - return ResourceManager.GetString("Schema_MutationType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type that query operations will be rooted at.. - /// - internal static string Schema_QueryType { + internal static string SchemaBuilderExtensions_DirectiveTypeIsBaseType { get { - return ResourceManager.GetString("Schema_QueryType", resourceCulture); + return ResourceManager.GetString("SchemaBuilderExtensions_DirectiveTypeIsBaseType", resourceCulture); } } - /// - /// Looks up a localized string similar to If this server support subscription, the type that subscription operations will be rooted at.. - /// - internal static string Schema_SubscriptionType { + internal static string SchemaBuilderExtensions_MustBeDirectiveType { get { - return ResourceManager.GetString("Schema_SubscriptionType", resourceCulture); + return ResourceManager.GetString("SchemaBuilderExtensions_MustBeDirectiveType", resourceCulture); } } - /// - /// Looks up a localized string similar to A list of all types supported by this server.. - /// - internal static string Schema_Types { + internal static string SchemaBuilderExtensions_SchemaIsEmpty { get { - return ResourceManager.GetString("Schema_Types", resourceCulture); + return ResourceManager.GetString("SchemaBuilderExtensions_SchemaIsEmpty", resourceCulture); } } - /// - /// Looks up a localized string similar to There is no handler registered that can handle the specified schema binding.. - /// internal static string SchemaBuilder_Binding_CannotBeHandled { get { return ResourceManager.GetString("SchemaBuilder_Binding_CannotBeHandled", resourceCulture); } } - /// - /// Looks up a localized string similar to The schema binding is not valid.. - /// internal static string SchemaBuilder_Binding_Invalid { get { return ResourceManager.GetString("SchemaBuilder_Binding_Invalid", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified convention type is not supported.. - /// - internal static string SchemaBuilder_Convention_NotSuppported { - get { - return ResourceManager.GetString("SchemaBuilder_Convention_NotSuppported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified interceptor type is not supported.. - /// - internal static string SchemaBuilder_Interceptor_NotSuppported { - get { - return ResourceManager.GetString("SchemaBuilder_Interceptor_NotSuppported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The given schema has to inherit from TypeSystemObjectBase in order to be initializable.. - /// internal static string SchemaBuilder_ISchemaNotTso { get { return ResourceManager.GetString("SchemaBuilder_ISchemaNotTso", resourceCulture); } } - /// - /// Looks up a localized string similar to schemaType must be a schema type.. - /// - internal static string SchemaBuilder_MustBeSchemaType { - get { - return ResourceManager.GetString("SchemaBuilder_MustBeSchemaType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The schema builder was unable to identify the query type of the schema. Either specify which type is the query type or set the schema builder to non-strict validation mode.. - /// internal static string SchemaBuilder_NoQueryType { get { return ResourceManager.GetString("SchemaBuilder_NoQueryType", resourceCulture); } } - /// - /// Looks up a localized string similar to A root type must be a class.. - /// internal static string SchemaBuilder_RootType_MustBeClass { get { return ResourceManager.GetString("SchemaBuilder_RootType_MustBeClass", resourceCulture); } } - /// - /// Looks up a localized string similar to A root type must be an object type.. - /// internal static string SchemaBuilder_RootType_MustBeObjectType { get { return ResourceManager.GetString("SchemaBuilder_RootType_MustBeObjectType", resourceCulture); } } - /// - /// Looks up a localized string similar to Non-generic schema types are not allowed.. - /// internal static string SchemaBuilder_RootType_NonGenericType { get { return ResourceManager.GetString("SchemaBuilder_RootType_NonGenericType", resourceCulture); } } - /// - /// Looks up a localized string similar to The given schema has to inherit from `Schema` in order to be initializable.. - /// internal static string SchemaBuilder_SchemaTypeInvalid { get { return ResourceManager.GetString("SchemaBuilder_SchemaTypeInvalid", resourceCulture); } } - /// - /// Looks up a localized string similar to A directive type mustn't be one of the base classes `DirectiveType` or `DirectiveType<T>` but must be a type inheriting from `DirectiveType` or `DirectiveType<T>`.. - /// - internal static string SchemaBuilderExtensions_DirectiveTypeIsBaseType { + internal static string SchemaErrorBuilder_MessageIsNull { get { - return ResourceManager.GetString("SchemaBuilderExtensions_DirectiveTypeIsBaseType", resourceCulture); + return ResourceManager.GetString("SchemaErrorBuilder_MessageIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to A directive type must inherit from `DirectiveType` or `DirectiveType<T>`.. - /// - internal static string SchemaBuilderExtensions_MustBeDirectiveType { + internal static string SchemaField_Description { get { - return ResourceManager.GetString("SchemaBuilderExtensions_MustBeDirectiveType", resourceCulture); + return ResourceManager.GetString("SchemaField_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The schema string cannot be null or empty.. - /// - internal static string SchemaBuilderExtensions_SchemaIsEmpty { + internal static string SchemaSyntaxVisitor_UnknownOperationType { get { - return ResourceManager.GetString("SchemaBuilderExtensions_SchemaIsEmpty", resourceCulture); + return ResourceManager.GetString("SchemaSyntaxVisitor_UnknownOperationType", resourceCulture); } } - /// - /// Looks up a localized string similar to The error message mustn't be null or empty.. - /// - internal static string SchemaErrorBuilder_MessageIsNull { + internal static string Schema_Description { get { - return ResourceManager.GetString("SchemaErrorBuilder_MessageIsNull", resourceCulture); + return ResourceManager.GetString("Schema_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Access the current type schema of this server.. - /// - internal static string SchemaField_Description { + internal static string Schema_Directives { get { - return ResourceManager.GetString("SchemaField_Description", resourceCulture); + return ResourceManager.GetString("Schema_Directives", resourceCulture); } } - /// - /// Looks up a localized string similar to Unknown operation type.. - /// - internal static string SchemaSyntaxVisitor_UnknownOperationType { + internal static string Schema_MutationType { get { - return ResourceManager.GetString("SchemaSyntaxVisitor_UnknownOperationType", resourceCulture); + return ResourceManager.GetString("Schema_MutationType", resourceCulture); } } - /// - /// Looks up a localized string similar to The `Short` scalar type represents non-fractional signed whole 16-bit numeric values. Short can represent values between -(2^15) and 2^15 - 1.. - /// - internal static string ShortType_Description { + internal static string Schema_QueryType { get { - return ResourceManager.GetString("ShortType_Description", resourceCulture); + return ResourceManager.GetString("Schema_QueryType", resourceCulture); } } - /// - /// Looks up a localized string similar to The `{0}` cannot be null or empty.. - /// - internal static string String_Argument_NullOrEmpty { + internal static string Schema_SubscriptionType { get { - return ResourceManager.GetString("String_Argument_NullOrEmpty", resourceCulture); + return ResourceManager.GetString("Schema_SubscriptionType", resourceCulture); } } - /// - /// Looks up a localized string similar to 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.. - /// - internal static string StringType_Description { + internal static string Schema_Types { get { - return ResourceManager.GetString("StringType_Description", resourceCulture); + return ResourceManager.GetString("Schema_Types", resourceCulture); } } - /// - /// Looks up a localized string similar to The `TimeSpan` scalar represents an ISO-8601 compliant duration type.. - /// - internal static string TimeSpanType_Description { + internal static string ShortType_Description { get { - return ResourceManager.GetString("TimeSpanType_Description", resourceCulture); + return ResourceManager.GetString("ShortType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. - /// - ///Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose othe [rest of string was truncated]";. - /// - internal static string Type_Description { + internal static string StringType_Description { get { - return ResourceManager.GetString("Type_Description", resourceCulture); + return ResourceManager.GetString("StringType_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The configuration delegate mustn't be null.. - /// - internal static string TypeConfiguration_ConfigureIsNull { + internal static string String_Argument_NullOrEmpty { get { - return ResourceManager.GetString("TypeConfiguration_ConfigureIsNull", resourceCulture); + return ResourceManager.GetString("String_Argument_NullOrEmpty", resourceCulture); } } - /// - /// Looks up a localized string similar to Definition mustn't be null.. - /// - internal static string TypeConfiguration_DefinitionIsNull { + internal static string TypeConfiguration_ConfigureIsNull { get { - return ResourceManager.GetString("TypeConfiguration_DefinitionIsNull", resourceCulture); + return ResourceManager.GetString("TypeConfiguration_ConfigureIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to convert type from `{0}` to `{1}`. - /// - internal static string TypeConvertion_ConvertNotSupported { + internal static string TypeConfiguration_DefinitionIsNull { get { - return ResourceManager.GetString("TypeConvertion_ConvertNotSupported", resourceCulture); + return ResourceManager.GetString("TypeConfiguration_DefinitionIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified type is not a schema type.. - /// internal static string TypeDependency_MustBeSchemaType { get { return ResourceManager.GetString("TypeDependency_MustBeSchemaType", resourceCulture); } } - /// - /// Looks up a localized string similar to The type structure is invalid.. - /// internal static string TypeExtensions_InvalidStructure { get { return ResourceManager.GetString("TypeExtensions_InvalidStructure", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified type kind is not supported.. - /// internal static string TypeExtensions_KindIsNotSupported { get { return ResourceManager.GetString("TypeExtensions_KindIsNotSupported", resourceCulture); } } - /// - /// Looks up a localized string similar to The specified type is not a valid list type.. - /// internal static string TypeExtensions_NoListType { get { return ResourceManager.GetString("TypeExtensions_NoListType", resourceCulture); } } - /// - /// Looks up a localized string similar to The given type is not a {0}.. - /// internal static string TypeExtensions_TypeIsNotOfT { get { return ResourceManager.GetString("TypeExtensions_TypeIsNotOfT", resourceCulture); } } - /// - /// Looks up a localized string similar to Request the type information of a single type.. - /// internal static string TypeField_Description { get { return ResourceManager.GetString("TypeField_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to resolve dependencies {1} for type `{0}`.. - /// internal static string TypeInitializer_CannotResolveDependency { get { return ResourceManager.GetString("TypeInitializer_CannotResolveDependency", resourceCulture); } } - /// - /// Looks up a localized string similar to The name `{0}` was already registered by another type.. - /// internal static string TypeInitializer_CompleteName_Duplicate { get { return ResourceManager.GetString("TypeInitializer_CompleteName_Duplicate", resourceCulture); } } - /// - /// Looks up a localized string similar to The kind of the extension does not match the kind of the type `{0}`.. - /// internal static string TypeInitializer_Merge_KindDoesNotMatch { get { return ResourceManager.GetString("TypeInitializer_Merge_KindDoesNotMatch", resourceCulture); } } - /// - /// Looks up a localized string similar to An enum describing what kind of type a given `__Type` is.. - /// internal static string TypeKind_Description { get { return ResourceManager.GetString("TypeKind_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is an enum. `enumValues` is a valid field.. - /// internal static string TypeKind_Enum { get { return ResourceManager.GetString("TypeKind_Enum", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is an input object. `inputFields` is a valid field.. - /// internal static string TypeKind_InputObject { get { return ResourceManager.GetString("TypeKind_InputObject", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.. - /// internal static string TypeKind_Interface { get { return ResourceManager.GetString("TypeKind_Interface", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is a list. `ofType` is a valid field.. - /// internal static string TypeKind_List { get { return ResourceManager.GetString("TypeKind_List", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is a non-null. `ofType` is a valid field.. - /// internal static string TypeKind_NonNull { get { return ResourceManager.GetString("TypeKind_NonNull", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is an object. `fields` and `interfaces` are valid fields.. - /// internal static string TypeKind_Object { get { return ResourceManager.GetString("TypeKind_Object", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is a scalar.. - /// internal static string TypeKind_Scalar { get { return ResourceManager.GetString("TypeKind_Scalar", resourceCulture); } } - /// - /// Looks up a localized string similar to Indicates this type is a union. `possibleTypes` is a valid field.. - /// internal static string TypeKind_Union { get { return ResourceManager.GetString("TypeKind_Union", resourceCulture); } } - /// - /// Looks up a localized string similar to The name of the current Object type at runtime.. - /// internal static string TypeNameField_Description { get { return ResourceManager.GetString("TypeNameField_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Invalid type structure.. - /// internal static string TypeNameHelper_InvalidTypeStructure { get { return ResourceManager.GetString("TypeNameHelper_InvalidTypeStructure", resourceCulture); } } - /// - /// Looks up a localized string similar to Only type system objects are allowed as dependency.. - /// internal static string TypeNameHelper_OnlyTsosAreAllowed { get { return ResourceManager.GetString("TypeNameHelper_OnlyTsosAreAllowed", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to infer or resolve a schema type from the type reference `{0}`.. - /// - internal static string TypeRegistrar_TypesInconsistent { + internal static string TypeResourceHelper_TypeNameEmptyOrNull { get { - return ResourceManager.GetString("TypeRegistrar_TypesInconsistent", resourceCulture); + return ResourceManager.GetString("TypeResourceHelper_TypeNameEmptyOrNull", resourceCulture); } } - /// - /// Looks up a localized string similar to The typeName mustn't be null or empty.. - /// - internal static string TypeResourceHelper_TypeNameEmptyOrNull { + internal static string Type_Description { get { - return ResourceManager.GetString("TypeResourceHelper_TypeNameEmptyOrNull", resourceCulture); + return ResourceManager.GetString("Type_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to The description becomes immutable once it was assigned.. - /// - internal static string TypeSystemObject_DescriptionImmutable { + internal static string UnionTypeExtension_CannotMerge { get { - return ResourceManager.GetString("TypeSystemObject_DescriptionImmutable", resourceCulture); + return ResourceManager.GetString("UnionTypeExtension_CannotMerge", resourceCulture); } } - /// - /// Looks up a localized string similar to The name becomes immutable once it was assigned.. - /// - internal static string TypeSystemObject_NameImmutable { + internal static string VariableValueBuilder_InputType { get { - return ResourceManager.GetString("TypeSystemObject_NameImmutable", resourceCulture); + return ResourceManager.GetString("VariableValueBuilder_InputType", resourceCulture); + } + } + + internal static string VariableValueBuilder_InvalidValue { + get { + return ResourceManager.GetString("VariableValueBuilder_InvalidValue", resourceCulture); + } + } + + internal static string VariableValueBuilder_NodeKind { + get { + return ResourceManager.GetString("VariableValueBuilder_NodeKind", resourceCulture); + } + } + + internal static string VariableValueBuilder_NonNull { + get { + return ResourceManager.GetString("VariableValueBuilder_NonNull", resourceCulture); + } + } + + internal static string VariableValueBuilder_NonNull_In_Graph { + get { + return ResourceManager.GetString("VariableValueBuilder_NonNull_In_Graph", resourceCulture); + } + } + + internal static string VariableValueBuilder_VarNameEmpty { + get { + return ResourceManager.GetString("VariableValueBuilder_VarNameEmpty", resourceCulture); + } + } + + internal static string Argument_TypeIsNull { + get { + return ResourceManager.GetString("Argument_TypeIsNull", resourceCulture); + } + } + + internal static string NonNullType_NotAnInputType { + get { + return ResourceManager.GetString("NonNullType_NotAnInputType", resourceCulture); + } + } + + internal static string NonNullType_TypeIsNunNullType { + get { + return ResourceManager.GetString("NonNullType_TypeIsNunNullType", resourceCulture); + } + } + + internal static string NonNullType_ValueIsNull { + get { + return ResourceManager.GetString("NonNullType_ValueIsNull", resourceCulture); + } + } + + internal static string ObjectTypeExtension_CannotMerge { + get { + return ResourceManager.GetString("ObjectTypeExtension_CannotMerge", resourceCulture); } } - /// - /// Looks up a localized string similar to The type definition is null which means that the type was initialized incorrectly.. - /// internal static string TypeSystemObjectBase_DefinitionIsNull { get { return ResourceManager.GetString("TypeSystemObjectBase_DefinitionIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to The type name was not completed correctly and is still empty. Type names are not allowed to remain empty after name completion was executed. - ///Type: `{0}`. - /// internal static string TypeSystemObjectBase_NameIsNull { get { return ResourceManager.GetString("TypeSystemObjectBase_NameIsNull", resourceCulture); } } - /// - /// Looks up a localized string similar to A Union type must define one or more unique member types.. - /// + internal static string TypeSystemObject_DescriptionImmutable { + get { + return ResourceManager.GetString("TypeSystemObject_DescriptionImmutable", resourceCulture); + } + } + + internal static string TypeSystemObject_NameImmutable { + get { + return ResourceManager.GetString("TypeSystemObject_NameImmutable", resourceCulture); + } + } + internal static string UnionType_MustHaveTypes { get { return ResourceManager.GetString("UnionType_MustHaveTypes", resourceCulture); } } - /// - /// Looks up a localized string similar to Unable to resolve the specified type reference.. - /// internal static string UnionType_UnableToResolveType { get { return ResourceManager.GetString("UnionType_UnableToResolveType", resourceCulture); } } - /// - /// Looks up a localized string similar to The union type extension can only be merged with an union type.. - /// - internal static string UnionTypeExtension_CannotMerge { + internal static string SchemaBuilder_MustBeSchemaType { get { - return ResourceManager.GetString("UnionTypeExtension_CannotMerge", resourceCulture); + return ResourceManager.GetString("SchemaBuilder_MustBeSchemaType", resourceCulture); } } - /// - /// Looks up a localized string similar to Variable `{0}` of type `{1}` must be an input type.. - /// - internal static string VariableValueBuilder_InputType { + internal static string TypeRegistrar_TypesInconsistent { get { - return ResourceManager.GetString("VariableValueBuilder_InputType", resourceCulture); + return ResourceManager.GetString("TypeRegistrar_TypesInconsistent", resourceCulture); } } - /// - /// Looks up a localized string similar to Variable `{0}` got invalid value.. - /// - internal static string VariableValueBuilder_InvalidValue { + internal static string TypeConvertion_ConvertNotSupported { get { - return ResourceManager.GetString("VariableValueBuilder_InvalidValue", resourceCulture); + return ResourceManager.GetString("TypeConvertion_ConvertNotSupported", resourceCulture); } } - /// - /// Looks up a localized string similar to The type node kind is not supported.. - /// - internal static string VariableValueBuilder_NodeKind { + internal static string SchemaBuilder_Interceptor_NotSuppported { get { - return ResourceManager.GetString("VariableValueBuilder_NodeKind", resourceCulture); + return ResourceManager.GetString("SchemaBuilder_Interceptor_NotSuppported", resourceCulture); } } - /// - /// Looks up a localized string similar to Variable `{0}` of type `{1}` must not be null.. - /// - internal static string VariableValueBuilder_NonNull { + internal static string IdSerializer_UnableToEncode { get { - return ResourceManager.GetString("VariableValueBuilder_NonNull", resourceCulture); + return ResourceManager.GetString("IdSerializer_UnableToEncode", resourceCulture); } } - /// - /// Looks up a localized string similar to Detected non-null violation in variable `{0}`.. - /// - internal static string VariableValueBuilder_NonNull_In_Graph { + internal static string IdSerializer_UnableToDecode { get { - return ResourceManager.GetString("VariableValueBuilder_NonNull_In_Graph", resourceCulture); + return ResourceManager.GetString("IdSerializer_UnableToDecode", resourceCulture); } } - /// - /// Looks up a localized string similar to Variable name mustn't be null or empty.. - /// - internal static string VariableValueBuilder_VarNameEmpty { + internal static string SchemaBuilder_Convention_NotSuppported { get { - return ResourceManager.GetString("VariableValueBuilder_VarNameEmpty", resourceCulture); + return ResourceManager.GetString("SchemaBuilder_Convention_NotSuppported", resourceCulture); + } + } + + internal static string TimeSpanType_Description { + get { + return ResourceManager.GetString("TimeSpanType_Description", resourceCulture); + } + } + + internal static string DefaultDataLoaderRegistry_GetOrRegister { + get { + return ResourceManager.GetString("DefaultDataLoaderRegistry_GetOrRegister", resourceCulture); + } + } + + internal static string DataLoaderResolverContextExtensions_CreateDataLoader_AbstractType { + get { + return ResourceManager.GetString("DataLoaderResolverContextExtensions_CreateDataLoader_AbstractType", resourceCulture); + } + } + + internal static string DataLoaderResolverContextExtensions_CreateDataLoader_UnableToCreate { + get { + return ResourceManager.GetString("DataLoaderResolverContextExtensions_CreateDataLoader_UnableToCreate", resourceCulture); } } } diff --git a/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx b/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx index ef88251d8e9..c5e94db6c58 100644 --- a/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx +++ b/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx @@ -596,4 +596,13 @@ Type: `{0}` The `TimeSpan` scalar represents an ISO-8601 compliant duration type. + + The DataLoader `{0}` was not of the requested type `{1}`. + + + The DataLoader `{0}` needs to be register with the dependency injection provider. + + + Unable to create DataLoader `{0}`. + diff --git a/src/HotChocolate/Core/src/Types/Resolvers/Expressions/Parameters/GetDataLoaderCompiler.cs b/src/HotChocolate/Core/src/Types/Resolvers/Expressions/Parameters/GetDataLoaderCompiler.cs index ac09c83dc4b..2641e50ac8a 100644 --- a/src/HotChocolate/Core/src/Types/Resolvers/Expressions/Parameters/GetDataLoaderCompiler.cs +++ b/src/HotChocolate/Core/src/Types/Resolvers/Expressions/Parameters/GetDataLoaderCompiler.cs @@ -3,6 +3,8 @@ using System.Reflection; using HotChocolate.Resolvers.CodeGeneration; +#nullable enable + namespace HotChocolate.Resolvers.Expressions.Parameters { internal sealed class GetDataLoaderCompiler diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/DataLoaderTests.cs b/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/DataLoaderTests.cs deleted file mode 100644 index bfa2efc48ac..00000000000 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/DataLoaderTests.cs +++ /dev/null @@ -1,392 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Snapshooter.Xunit; -using Xunit; -using GreenDonut; -using HotChocolate.Execution; -using HotChocolate.Resolvers; - -namespace HotChocolate.Integration.DataLoader -{ - public class DataLoaderTests - { - [Fact] - public async Task FetchOnceDataLoader() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create( - @"type Query { fetchItem: String }", - c => - { - c.BindResolver(async ctx => - { - return await ctx.FetchOnceAsync( - "fetchItems", - () => Task.FromResult("fooBar")); - - }).To("Query", "fetchItem"); - }); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - IExecutionResult result = await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ fetchItem }") - .SetServices(scope.ServiceProvider) - .Create()); - - // assert - result.MatchSnapshot(); - } - - [Fact] - public async Task FetchSingleDataLoader() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create( - @"type Query { fetchItem: String }", - c => - { - c.BindResolver(async ctx => - { - IDataLoader dataLoader = - ctx.CacheDataLoader( - "fetchItems", - key => Task.FromResult(key)); - return await dataLoader.LoadAsync("fooBar"); - }).To("Query", "fetchItem"); - }); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - IExecutionResult result = await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ fetchItem }") - .SetServices(scope.ServiceProvider) - .Create()); - - // assert - result.MatchSnapshot(); - } - - [Fact] - public async Task FetchDataLoader() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create( - @"type Query { fetchItem: String }", - c => - { - c.BindResolver(async ctx => - { - IDataLoader dataLoader = - ctx.BatchDataLoader( - "fetchItems", - keys => - Task.FromResult< - IReadOnlyDictionary>( - keys.ToDictionary(t => t))); - return await dataLoader.LoadAsync("fooBar"); - }).To("Query", "fetchItem"); - }); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - IExecutionResult result = await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ fetchItem }") - .SetServices(scope.ServiceProvider) - .Create()); - - // assert - result.MatchSnapshot(); - } - - [Fact] - public async Task FetchGroupDataLoader() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create( - @"type Query { fetchItem: String }", - c => - { - c.BindResolver(async ctx => - { - IDataLoader dataLoader = - ctx.GroupDataLoader( - "fetchItems", - keys => - Task.FromResult(keys.ToLookup(t => t))); - return await dataLoader.LoadAsync("fooBar"); - }).To("Query", "fetchItem"); - }); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - IExecutionResult result = await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ fetchItem }") - .SetServices(scope.ServiceProvider) - .Create()); - - // assert - result.MatchSnapshot(); - } - - [Fact] - public async Task ClassDataLoader() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create(c => c.RegisterQueryType()); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - var results = new List(); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withDataLoader(key: ""a"") - b: withDataLoader(key: ""b"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withDataLoader(key: ""a"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - c: withDataLoader(key: ""c"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ loads }") - .SetServices(scope.ServiceProvider) - .Create())); - - // assert - Assert.Collection(results, - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors)); - results.MatchSnapshot(); - } - - [Fact] - public async Task ClassDataLoaderWithKey() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create(c => c.RegisterQueryType()); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - var results = new List(); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withDataLoader2(key: ""a"") - b: withDataLoader2(key: ""b"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withDataLoader2(key: ""a"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - c: withDataLoader2(key: ""c"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ loads loads2 }") - .SetServices(scope.ServiceProvider) - .Create())); - - // assert - Assert.Collection(results, - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors)); - results.MatchSnapshot(); - } - - [Fact] - public async Task StackedDataLoader() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoaderRegistry() - .BuildServiceProvider(); - - var schema = Schema.Create(c => c.RegisterQueryType()); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - var results = new List(); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withStackedDataLoader(key: ""a"") - b: withStackedDataLoader(key: ""b"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withStackedDataLoader(key: ""a"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - c: withStackedDataLoader(key: ""c"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - // assert - Assert.Collection(results, - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors)); - results.MatchSnapshot(); - } - - [Fact] - public async Task ClassDataLoader_Resolve_From_DependencyInjection() - { - // arrange - IServiceProvider serviceProvider = new ServiceCollection() - .AddDataLoader() - .BuildServiceProvider(); - - var schema = Schema.Create(c => c.RegisterQueryType()); - - IQueryExecutor executor = schema.MakeExecutable(); - IServiceScope scope = serviceProvider.CreateScope(); - - // act - var results = new List(); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: dataLoaderWithInterface(key: ""a"") - b: dataLoaderWithInterface(key: ""b"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: dataLoaderWithInterface(key: ""a"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - c: dataLoaderWithInterface(key: ""c"") - }") - .SetServices(scope.ServiceProvider) - .Create())); - - results.Add(await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery("{ loads loads2 loads3 }") - .SetServices(scope.ServiceProvider) - .Create())); - - // assert - Assert.Collection(results, - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors), - t => Assert.Null(t.Errors)); - results.MatchSnapshot(); - } - } -} diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchDataLoader.snap b/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchDataLoader.snap deleted file mode 100644 index 55869891a17..00000000000 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchDataLoader.snap +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Data": { - "fetchItem": "fooBar" - }, - "Errors": null, - "Extensions": null, - "ContextData": null -} diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchGroupDataLoader.snap b/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchGroupDataLoader.snap deleted file mode 100644 index 55869891a17..00000000000 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchGroupDataLoader.snap +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Data": { - "fetchItem": "fooBar" - }, - "Errors": null, - "Extensions": null, - "ContextData": null -} diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchOnceDataLoader.snap b/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchOnceDataLoader.snap deleted file mode 100644 index 55869891a17..00000000000 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchOnceDataLoader.snap +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Data": { - "fetchItem": "fooBar" - }, - "Errors": null, - "Extensions": null, - "ContextData": null -} diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchSingleDataLoader.snap b/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchSingleDataLoader.snap deleted file mode 100644 index 55869891a17..00000000000 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchSingleDataLoader.snap +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Data": { - "fetchItem": "fooBar" - }, - "Errors": null, - "Extensions": null, - "ContextData": null -} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs new file mode 100644 index 00000000000..f0df29b5ec9 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using GreenDonut; +using HotChocolate.DataLoader; +using HotChocolate.Execution; +using HotChocolate.Resolvers; +using HotChocolate.Tests; +using Snapshooter.Xunit; +using Xunit; +using static HotChocolate.Tests.TestHelper; +using Snapshooter; + +namespace HotChocolate.Integration.DataLoader +{ + public class DataLoaderTests + { + [Fact] + public async Task FetchOnceDataLoader() + { + Snapshot.FullName(); + await ExpectValid( + "{ fetchItem }", + configure: b => b + .AddGraphQL() + .AddDocumentFromString("type Query { fetchItem: String }") + .AddResolver( + "Query", "fetchItem", + async ctx => await ctx.FetchOnceAsync(ct => Task.FromResult("fooBar"))) + ) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task FetchSingleDataLoader() + { + Snapshot.FullName(); + await ExpectValid( + "{ fetchItem }", + configure: b => b + .AddGraphQL() + .AddDocumentFromString("type Query { fetchItem: String }") + .AddResolver( + "Query", "fetchItem", + async ctx => await ctx.CacheDataLoader( + (key, ct) => Task.FromResult(key)) + .LoadAsync("fooBar")) + ) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task FetchDataLoader() + { + Snapshot.FullName(); + await ExpectValid( + "{ fetchItem }", + configure: b => b + .AddGraphQL() + .AddDocumentFromString("type Query { fetchItem: String }") + .AddResolver( + "Query", "fetchItem", + async ctx => await ctx.BatchDataLoader( + (keys, ct) => Task.FromResult>( + keys.ToDictionary(t => t))) + .LoadAsync("fooBar")) + ) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task FetchGroupDataLoader() + { + Snapshot.FullName(); + await ExpectValid( + "{ fetchItem }", + configure: b => b + .AddGraphQL() + .AddDocumentFromString("type Query { fetchItem: String }") + .AddResolver( + "Query", "fetchItem", + async ctx => await ctx.GroupDataLoader( + (keys, ct) => Task.FromResult( + keys.ToLookup(t => t))) + .LoadAsync("fooBar")) + ) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task ClassDataLoader() + { + // arrange + IRequestExecutor executor = await CreateExecutorAsync(c => c + .AddQueryType() + .AddDataLoader() + .UseRequest(next => async context => + { + await next(context); + + var dataLoader = + context.Services + .GetRequiredService() + .GetOrRegister(() => throw new Exception()); + + context.Result = QueryResultBuilder + .FromResult((IQueryResult)context.Result) + .AddExtension("loads", dataLoader.Loads) + .Create(); + }) + .UseDefaultPipeline()); + + // act + var results = new List(); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: withDataLoader(key: ""a"") + b: withDataLoader(key: ""b"") + bar { + c: withDataLoader(key: ""c"") + } + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: withDataLoader(key: ""a"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + c: withDataLoader(key: ""c"") + }") + .Create())); + + // assert + results.MatchSnapshot(); + } + + [Fact] + public async Task ClassDataLoaderWithKey() + { + // arrange + IRequestExecutor executor = await CreateExecutorAsync(c => c + .AddQueryType() + .AddDataLoader() + .UseRequest(next => async context => + { + await next(context); + + var dataLoader = + context.Services + .GetRequiredService() + .GetOrRegister("fooBar", () => throw new Exception()); + + context.Result = QueryResultBuilder + .FromResult((IQueryResult)context.Result) + .AddExtension("loads", dataLoader.Loads) + .Create(); + }) + .UseDefaultPipeline()); + + // act + var results = new List(); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: withDataLoader2(key: ""a"") + b: withDataLoader2(key: ""b"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: withDataLoader2(key: ""a"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + c: withDataLoader2(key: ""c"") + }") + .Create())); + + // assert + results.MatchSnapshot(); + } + + [Fact] + public async Task StackedDataLoader() + { + // arrange + IRequestExecutor executor = await CreateExecutorAsync(c => c + .AddQueryType() + .AddDataLoader()); + + // act + var results = new List(); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: withStackedDataLoader(key: ""a"") + b: withStackedDataLoader(key: ""b"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: withStackedDataLoader(key: ""a"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + c: withStackedDataLoader(key: ""c"") + }") + .Create())); + + // assert + results.MatchSnapshot(); + } + + [Fact] + public async Task ClassDataLoader_Resolve_From_DependencyInjection() + { + // arrange + IRequestExecutor executor = await CreateExecutorAsync(c => c + .AddQueryType() + .AddDataLoader() + .UseRequest(next => async context => + { + await next(context); + + var dataLoader = + (TestDataLoader)context.Services.GetRequiredService(); + + context.Result = QueryResultBuilder + .FromResult((IQueryResult)context.Result) + .AddExtension("loads", dataLoader.Loads) + .Create(); + }) + .UseDefaultPipeline()); + + // act + var results = new List(); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: dataLoaderWithInterface(key: ""a"") + b: dataLoaderWithInterface(key: ""b"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + a: dataLoaderWithInterface(key: ""a"") + }") + .Create())); + + results.Add(await executor.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + c: dataLoaderWithInterface(key: ""c"") + }") + .Create())); + + // assert + results.MatchSnapshot(); + } + } +} diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/ITestDataLoader.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/ITestDataLoader.cs similarity index 100% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/ITestDataLoader.cs rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/ITestDataLoader.cs diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/MyCustomContext.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/MyCustomContext.cs similarity index 100% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/MyCustomContext.cs rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/MyCustomContext.cs diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/Query.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs similarity index 57% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/Query.cs rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs index a4a610edfcf..861bd85bd7c 100644 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/Query.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs @@ -10,16 +10,18 @@ public class Query public Task GetWithDataLoader( string key, FieldNode fieldSelection, - [DataLoader]TestDataLoader testDataLoader, + [DataLoader] TestDataLoader testDataLoader, CancellationToken cancellationToken) { return testDataLoader.LoadAsync(key, cancellationToken); } + public Bar Bar => new Bar(); + public Task GetWithDataLoader2( string key, FieldNode fieldSelection, - [DataLoader("fooBar")]TestDataLoader testDataLoader, + [DataLoader("fooBar")] TestDataLoader testDataLoader, CancellationToken cancellationToken) { return testDataLoader.LoadAsync(key, cancellationToken); @@ -37,7 +39,7 @@ public Task GetDataLoaderWithInterface( public async Task GetWithStackedDataLoader( string key, FieldNode fieldSelection, - [DataLoader("fooBar")]TestDataLoader testDataLoader, + [DataLoader("fooBar")] TestDataLoader testDataLoader, CancellationToken cancellationToken) { @@ -48,43 +50,17 @@ public async Task GetWithStackedDataLoader( s += await testDataLoader.LoadAsync(key + "e", cancellationToken); return s; } + } - public List GetLoads([ - DataLoader]TestDataLoader testDataLoader) - { - var list = new List(); - - foreach (IReadOnlyList request in testDataLoader.Loads) - { - list.Add(string.Join(", ", request)); - } - - return list; - } - - public List GetLoads2( - [DataLoader("fooBar")]TestDataLoader testDataLoader) - { - var list = new List(); - - foreach (IReadOnlyList request in testDataLoader.Loads) - { - list.Add(string.Join(", ", request)); - } - - return list; - } - - public List GetLoads3(ITestDataLoader testDataLoader) + public class Bar + { + public Task GetWithDataLoader( + string key, + FieldNode fieldSelection, + [DataLoader] TestDataLoader testDataLoader, + CancellationToken cancellationToken) { - var list = new List(); - - foreach (IReadOnlyList request in ((TestDataLoader)testDataLoader).Loads) - { - list.Add(string.Join(", ", request)); - } - - return list; + return testDataLoader.LoadAsync(key, cancellationToken); } } } diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/TestDataLoader.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/TestDataLoader.cs similarity index 72% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/TestDataLoader.cs rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/TestDataLoader.cs index 6d44f2f3c80..c4e037490a1 100644 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/TestDataLoader.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/TestDataLoader.cs @@ -10,20 +10,20 @@ public class TestDataLoader : DataLoaderBase , ITestDataLoader { - public TestDataLoader() - : base(null) + public TestDataLoader(IBatchScheduler batchScheduler) + : base(batchScheduler) { } public List> Loads { get; } = new List>(); - protected override Task>> FetchAsync( + protected override ValueTask>> FetchAsync( IReadOnlyList keys, CancellationToken cancellationToken) { Loads.Add(keys.OrderBy(t => t).ToArray()); - return Task.FromResult>>( + return new ValueTask>>( keys.Select(t => (Result)t).ToArray()); } } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader.snap new file mode 100644 index 00000000000..3e2e938b8bf --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader.snap @@ -0,0 +1,50 @@ +[ + { + "Data": { + "a": "a", + "b": "b", + "bar": { + "c": "c" + } + }, + "Errors": null, + "Extensions": { + "loads": [ + [ + "a", + "b", + "c" + ] + ] + }, + "ContextData": null + }, + { + "Data": { + "a": "a" + }, + "Errors": null, + "Extensions": { + "loads": [ + [ + "a" + ] + ] + }, + "ContextData": null + }, + { + "Data": { + "c": "c" + }, + "Errors": null, + "Extensions": { + "loads": [ + [ + "c" + ] + ] + }, + "ContextData": null + } +] diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoaderWithKey.snap similarity index 57% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader.snap rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoaderWithKey.snap index aaa8cdf787d..ce03a704184 100644 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader.snap +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoaderWithKey.snap @@ -5,7 +5,14 @@ "b": "b" }, "Errors": null, - "Extensions": null, + "Extensions": { + "loads": [ + [ + "a", + "b" + ] + ] + }, "ContextData": null }, { @@ -13,7 +20,13 @@ "a": "a" }, "Errors": null, - "Extensions": null, + "Extensions": { + "loads": [ + [ + "a" + ] + ] + }, "ContextData": null }, { @@ -21,18 +34,13 @@ "c": "c" }, "Errors": null, - "Extensions": null, - "ContextData": null - }, - { - "Data": { + "Extensions": { "loads": [ - "a, b", - "c" + [ + "c" + ] ] }, - "Errors": null, - "Extensions": null, "ContextData": null } ] diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection.snap similarity index 54% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection.snap rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection.snap index 25a350d11a2..ce03a704184 100644 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection.snap +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection.snap @@ -5,7 +5,14 @@ "b": "b" }, "Errors": null, - "Extensions": null, + "Extensions": { + "loads": [ + [ + "a", + "b" + ] + ] + }, "ContextData": null }, { @@ -13,7 +20,13 @@ "a": "a" }, "Errors": null, - "Extensions": null, + "Extensions": { + "loads": [ + [ + "a" + ] + ] + }, "ContextData": null }, { @@ -21,20 +34,13 @@ "c": "c" }, "Errors": null, - "Extensions": null, - "ContextData": null - }, - { - "Data": { - "loads": [], - "loads2": [], - "loads3": [ - "a, b", - "c" + "Extensions": { + "loads": [ + [ + "c" + ] ] }, - "Errors": null, - "Extensions": null, "ContextData": null } ] diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoaderWithKey.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection_NETCOREAPP2_1.snap similarity index 54% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoaderWithKey.snap rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection_NETCOREAPP2_1.snap index dbf23906f4d..ce03a704184 100644 --- a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoaderWithKey.snap +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.ClassDataLoader_Resolve_From_DependencyInjection_NETCOREAPP2_1.snap @@ -5,7 +5,14 @@ "b": "b" }, "Errors": null, - "Extensions": null, + "Extensions": { + "loads": [ + [ + "a", + "b" + ] + ] + }, "ContextData": null }, { @@ -13,7 +20,13 @@ "a": "a" }, "Errors": null, - "Extensions": null, + "Extensions": { + "loads": [ + [ + "a" + ] + ] + }, "ContextData": null }, { @@ -21,19 +34,13 @@ "c": "c" }, "Errors": null, - "Extensions": null, - "ContextData": null - }, - { - "Data": { - "loads": [], - "loads2": [ - "a, b", - "c" + "Extensions": { + "loads": [ + [ + "c" + ] ] }, - "Errors": null, - "Extensions": null, "ContextData": null } ] diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchDataLoader.snap new file mode 100644 index 00000000000..38fbfcca920 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchDataLoader.snap @@ -0,0 +1,5 @@ +{ + "data": { + "fetchItem": "fooBar" + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchGroupDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchGroupDataLoader.snap new file mode 100644 index 00000000000..38fbfcca920 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchGroupDataLoader.snap @@ -0,0 +1,5 @@ +{ + "data": { + "fetchItem": "fooBar" + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchOnceDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchOnceDataLoader.snap new file mode 100644 index 00000000000..38fbfcca920 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchOnceDataLoader.snap @@ -0,0 +1,5 @@ +{ + "data": { + "fetchItem": "fooBar" + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchSingleDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchSingleDataLoader.snap new file mode 100644 index 00000000000..38fbfcca920 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.FetchSingleDataLoader.snap @@ -0,0 +1,5 @@ +{ + "data": { + "fetchItem": "fooBar" + } +} diff --git a/src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.StackedDataLoader.snap b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.StackedDataLoader.snap similarity index 100% rename from src/HotChocolate/Core/test/Core.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.StackedDataLoader.snap rename to src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/__snapshots__/DataLoaderTests.StackedDataLoader.snap diff --git a/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited.snap b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited.snap new file mode 100644 index 00000000000..455eeecf475 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited.snap @@ -0,0 +1,16 @@ +query foo($v: Boolean) { + ... on Query { + hero(episode: EMPIRE) { + ... on Human { + name @include(if: $v) + id + height @include(if: $v) + } + ... on Droid { + name @include(if: $v) + id + height @include(if: $v) + } + } + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited_2.snap b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited_2.snap new file mode 100644 index 00000000000..2683f5d8d1d --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited_2.snap @@ -0,0 +1,16 @@ +query foo($v: Boolean, $q: Boolean) { + ... on Query { + hero(episode: EMPIRE) { + ... on Human { + name @include(if: $q) + id + height @include(if: $v) + } + ... on Droid { + name @include(if: $q) + id + height @include(if: $v) + } + } + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited_3.snap b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited_3.snap new file mode 100644 index 00000000000..65e23a43199 --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Object_Field_Visibility_Is_Correctly_Inherited_3.snap @@ -0,0 +1,12 @@ +query foo($v: Boolean, $q: Boolean) { + ... on Query { + hero(episode: EMPIRE) @include(if: $v) { + ... on Human { + name @include(if: $q) + } + ... on Droid { + name @include(if: $q) + } + } + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Prepare_Fragment_Definition.snap b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Prepare_Fragment_Definition.snap new file mode 100644 index 00000000000..bc679f1ab1d --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Prepare_Fragment_Definition.snap @@ -0,0 +1,14 @@ +{ + ... on Query { + hero(episode: EMPIRE) { + ... on Human { + name + homePlanet + } + ... on Droid { + name + primaryFunction + } + } + } +} diff --git a/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Prepare_Inline_Fragment.snap b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Prepare_Inline_Fragment.snap new file mode 100644 index 00000000000..bc679f1ab1d --- /dev/null +++ b/src/HotChocolate/Core/test/Execution.Tests/Utilities/__snapshots__/OperationCompilerTests.Prepare_Inline_Fragment.snap @@ -0,0 +1,14 @@ +{ + ... on Query { + hero(episode: EMPIRE) { + ... on Human { + name + homePlanet + } + ... on Droid { + name + primaryFunction + } + } + } +} diff --git a/src/HotChocolate/Core/test/Fetching.Tests/AutoBatchSchedulerTests.cs b/src/HotChocolate/Core/test/Fetching.Tests/AutoBatchSchedulerTests.cs index 5f90577a1f6..a1e83e6a073 100644 --- a/src/HotChocolate/Core/test/Fetching.Tests/AutoBatchSchedulerTests.cs +++ b/src/HotChocolate/Core/test/Fetching.Tests/AutoBatchSchedulerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using HotChocolate.Fetching; using Xunit; @@ -12,9 +13,10 @@ public void Schedule_OneAction_DispatchesImmediately() // arrange var hasBeenDispatched = false; var scheduler = new AutoBatchScheduler(); - Action dispatch = () => + Func dispatch = () => { hasBeenDispatched = true; + return default; }; // act diff --git a/src/HotChocolate/Core/test/Fetching.Tests/BatchSchedulerTests.cs b/src/HotChocolate/Core/test/Fetching.Tests/BatchSchedulerTests.cs index 0235bee24e8..39725edd377 100644 --- a/src/HotChocolate/Core/test/Fetching.Tests/BatchSchedulerTests.cs +++ b/src/HotChocolate/Core/test/Fetching.Tests/BatchSchedulerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using HotChocolate.Fetching; using Snapshooter.Xunit; using Xunit; @@ -11,20 +12,17 @@ public class BatchSchedulerTests public void Dispatch_OneAction_ShouldDispatchOneAction() { // arrange - var hasBeenDispatched = false; var scheduler = new BatchScheduler(); - Action dispatch = () => - { - hasBeenDispatched = true; - }; + Func dispatch = () => default; scheduler.Schedule(dispatch); + Assert.True(scheduler.HasTasks); // act - scheduler.Dispatch(); + scheduler.Dispatch(d => { }); // assert - Assert.True(hasBeenDispatched); + Assert.False(scheduler.HasTasks); } [Fact] @@ -42,7 +40,7 @@ public void Schedule_OneAction_HasTasksShouldReturnTrue() { // arrange var scheduler = new BatchScheduler(); - Action dispatch = () => { }; + Func dispatch = () => default; // act scheduler.Schedule(dispatch); @@ -57,7 +55,7 @@ public void Schedule_OneAction_ShouldRaiseTaskEnqueued() // arrange var hasBeenRaised = false; var scheduler = new BatchScheduler(); - Action dispatch = () => { }; + Func dispatch = () => default; scheduler.TaskEnqueued += (s, e) => { diff --git a/src/HotChocolate/Core/test/Types.Tests/DataLoader/DataLoaderResolverContextExtensions.cs b/src/HotChocolate/Core/test/Types.Tests/DataLoader/DataLoaderResolverContextExtensions.cs index 211fd8ea9ea..f9ee0023445 100644 --- a/src/HotChocolate/Core/test/Types.Tests/DataLoader/DataLoaderResolverContextExtensions.cs +++ b/src/HotChocolate/Core/test/Types.Tests/DataLoader/DataLoaderResolverContextExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using HotChocolate.DataLoader; using Moq; @@ -14,272 +15,206 @@ public class DataLoaderResolverContextExtensionsTests public void BatchDataLoader_1_ContextNull_ArgNullException() { // arrange - // assert - Action a = () => DataLoaderResolverContextExtensions - .BatchDataLoader( - null, - "abc", - new FetchBatch(keys => Task - .FromResult>( - null))); - // act - Assert.Throws(a); - } - - [Fact] - public void BatchDataLoader_1_KeyNull_ArgNullException() - { - // arrange - var resolverContext = new Mock(); - - // assert Action a = () => DataLoaderResolverContextExtensions .BatchDataLoader( - resolverContext.Object, null, - new FetchBatch(keys => Task + new FetchBatch((keys, ct) => Task .FromResult>( - null))); - - // act - Assert.Throws(a); - } - - [Fact] - public void BatchDataLoader_1_FetchNull_ArgNullException() - { - // arrange - var resolverContext = new Mock(); + null)), + key: "abc"); // assert - Action a = () => DataLoaderResolverContextExtensions - .BatchDataLoader( - resolverContext.Object, - "123", - default(FetchBatch)); - - // act Assert.Throws(a); } [Fact] + [Obsolete] public void BatchDataLoader_2_ContextNull_ArgNullException() { // arrange - // assert + // act Action a = () => DataLoaderResolverContextExtensions .BatchDataLoader( null, "abc", - new FetchBatchCt((keys, ct) => Task + new FetchBatch((keys, ct) => Task .FromResult>( null))); - // act + // assert Assert.Throws(a); } [Fact] + [Obsolete] public void BatchDataLoader_2_KeyNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .BatchDataLoader( resolverContext.Object, null, - new FetchBatchCt((keys, ct) => Task + new FetchBatch((keys, ct) => Task .FromResult>( null))); - // act + // assert Assert.Throws(a); } [Fact] - public void BatchDataLoader_2_FetchNull_ArgNullException() + public void BatchDataLoader_1_FetchNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .BatchDataLoader( resolverContext.Object, - "123", - default(FetchBatchCt)); + default(FetchBatch), + key: "123"); - // act + // assert Assert.Throws(a); } [Fact] - public void GroupDataLoader_1_ContextNull_ArgNullException() + [Obsolete] + public void BatchDataLoader_2_FetchNull_ArgNullException() { // arrange - var lookup = new Mock>(); + var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions - .GroupDataLoader( - null, - "abc", - new FetchGroup(keys => - Task.FromResult(lookup.Object))); + .BatchDataLoader( + resolverContext.Object, + "123", + default(FetchBatch)); - // act + // assert Assert.Throws(a); } [Fact] - public void GroupDataLoader_1_KeyNull_ArgNullException() + public void GroupDataLoader_1_ContextNull_ArgNullException() { // arrange - var resolverContext = new Mock(); var lookup = new Mock>(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .GroupDataLoader( - resolverContext.Object, null, - new FetchGroup(keys => - Task.FromResult(lookup.Object))); - - // act - Assert.Throws(a); - } - - [Fact] - public void GroupDataLoader_1_FetchNull_ArgNullException() - { - // arrange - var resolverContext = new Mock(); + new FetchGroup((keys, ct) => + Task.FromResult(lookup.Object)), + key: "abc"); // assert - Action a = () => DataLoaderResolverContextExtensions - .GroupDataLoader( - resolverContext.Object, - "123", - default(FetchGroup)); - - // act Assert.Throws(a); } [Fact] + [Obsolete] public void GroupDataLoader_2_ContextNull_ArgNullException() { // arrange var lookup = new Mock>(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .GroupDataLoader( null, "abc", - new FetchGroupCt((keys, ct) => + new FetchGroup((keys, ct) => Task.FromResult(lookup.Object))); - // act + // assert Assert.Throws(a); } [Fact] + [Obsolete] public void GroupDataLoader_2_KeyNull_ArgNullException() { // arrange var resolverContext = new Mock(); var lookup = new Mock>(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .GroupDataLoader( resolverContext.Object, null, - new FetchGroupCt((keys, ct) => + new FetchGroup((keys, ct) => Task.FromResult(lookup.Object))); - // act + // assert Assert.Throws(a); } [Fact] - public void GroupDataLoader_2_FetchNull_ArgNullException() + public void GroupDataLoader_1_FetchNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .GroupDataLoader( resolverContext.Object, - "123", - default(FetchGroupCt)); + default(FetchGroup), + key: "123"); - // act - Assert.Throws(a); - } - - [Fact] - public void CacheDataLoader_1_ContextNull_ArgNullException() - { - // arrange // assert - Action a = () => DataLoaderResolverContextExtensions - .CacheDataLoader( - null, - "abc", - new FetchCache(keys => - Task.FromResult(string.Empty))); - - // act Assert.Throws(a); } [Fact] - public void CacheDataLoader_1_KeyNull_ArgNullException() + [Obsolete] + public void GroupDataLoader_2_FetchNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions - .CacheDataLoader( + .GroupDataLoader( resolverContext.Object, - null, - new FetchCache(keys => - Task.FromResult(string.Empty))); + "123", + default(FetchGroup)); - // act - Assert.Throws(a); + // assert + Assert.Throws(a); } [Fact] - public void CacheDataLoader_1_FetchNull_ArgNullException() + public void CacheDataLoader_1_ContextNull_ArgNullException() { // arrange - var resolverContext = new Mock(); - - // assert + // act Action a = () => DataLoaderResolverContextExtensions .CacheDataLoader( - resolverContext.Object, - "123", - default(FetchCache)); + null, + new FetchCacheCt((keys, ct) => + Task.FromResult(string.Empty)), + key: "abc"); - // act + // assert Assert.Throws(a); } [Fact] + [Obsolete] public void CacheDataLoader_2_ContextNull_ArgNullException() { // arrange - // assert + // act Action a = () => DataLoaderResolverContextExtensions .CacheDataLoader( null, @@ -287,17 +222,18 @@ public void CacheDataLoader_2_ContextNull_ArgNullException() new FetchCacheCt((keys, ct) => Task.FromResult(string.Empty))); - // act + // assert Assert.Throws(a); } [Fact] + [Obsolete] public void CacheDataLoader_2_KeyNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .CacheDataLoader( resolverContext.Object, @@ -305,124 +241,129 @@ public void CacheDataLoader_2_KeyNull_ArgNullException() new FetchCacheCt((keys, ct) => Task.FromResult(string.Empty))); - // act + // assert Assert.Throws(a); } [Fact] - public void CacheDataLoader_2_FetchNull_ArgNullException() + public void CacheDataLoader_1_FetchNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .CacheDataLoader( resolverContext.Object, - "123", - default(FetchCacheCt)); + default(FetchCacheCt), + key: "123"); - // act - Assert.Throws(a); - } - - [Fact] - public void FetchOnceAsync_1_ContextNull_ArgNullException() - { - // arrange // assert - Action a = () => DataLoaderResolverContextExtensions - .FetchOnceAsync( - null, - "abc", - new FetchOnce(() => Task.FromResult(string.Empty))); - - // act Assert.Throws(a); } [Fact] - public void FetchOnceAsync_1_KeyNull_ArgNullException() + [Obsolete] + public void CacheDataLoader_2_FetchNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions - .FetchOnceAsync( + .CacheDataLoader( resolverContext.Object, - null, - new FetchOnce(() => Task.FromResult(string.Empty))); + "123", + default(FetchCacheCt)); - // act - Assert.Throws(a); + // assert + Assert.Throws(a); } [Fact] - public void FetchOnceAsync_1_FetchNull_ArgNullException() + public void FetchOnceAsync_1_ContextNull_ArgNullException() { // arrange - var resolverContext = new Mock(); - // assert Action a = () => DataLoaderResolverContextExtensions .FetchOnceAsync( - resolverContext.Object, - "123", - default(FetchOnce)); + null, + new Func>(ct => + Task.FromResult(string.Empty)), + key: "abc"); // act Assert.Throws(a); } [Fact] + [Obsolete] public void FetchOnceAsync_2_ContextNull_ArgNullException() { // arrange - // assert + // act Action a = () => DataLoaderResolverContextExtensions .FetchOnceAsync( null, "abc", - new FetchOnceCt(ct => + new Func>(ct => Task.FromResult(string.Empty))); - // act + // assert Assert.Throws(a); } [Fact] + [Obsolete] public void FetchOnceAsync_2_KeyNull_ArgNullException() { // arrange var resolverContext = new Mock(); - // assert + // act Action a = () => DataLoaderResolverContextExtensions .FetchOnceAsync( resolverContext.Object, null, - new FetchOnceCt(ct => + new Func>(ct => Task.FromResult(string.Empty))); - // act + // assert Assert.Throws(a); } [Fact] - public void FetchOnceAsync_2_FetchNull_ArgNullException() + public void FetchOnceAsync_1_FetchNull_ArgNullException() { // arrange var resolverContext = new Mock(); + // act + Action a = () => DataLoaderResolverContextExtensions + .FetchOnceAsync( + resolverContext.Object, + default(Func>), + key: "123"); + // assert + Assert.Throws(a); + } + + [Fact] + [Obsolete] + public void FetchOnceAsync_2_FetchNull_ArgNullException() + { + // arrange + var resolverContext = new Mock(); + + // act Action a = () => DataLoaderResolverContextExtensions .FetchOnceAsync( resolverContext.Object, "123", - default(FetchOnceCt)); + default(Func>)); - // act + // assert Assert.Throws(a); } } diff --git a/src/HotChocolate/Directory.Build.props b/src/HotChocolate/Directory.Build.props index b787a2e15ed..e7e33284c58 100644 --- a/src/HotChocolate/Directory.Build.props +++ b/src/HotChocolate/Directory.Build.props @@ -2,7 +2,7 @@ - 8.0 + preview 0.0.0