From 7b106aada09f2e8de30e4d6bcdfaca135fc9b24d Mon Sep 17 00:00:00 2001 From: PascalSenn Date: Fri, 25 Jan 2019 14:48:33 +0100 Subject: [PATCH] feat(Core): adds scoped data context (#538) --- .../Middleware/ScopedContextDataTests.cs | 46 +++++++++++++++++++ .../Core/Execution/ExecutionStrategyBase.cs | 4 +- .../Execution/Resolvers/DirectiveContext.cs | 6 +++ .../Execution/Resolvers/MiddlewareContext.cs | 6 +++ .../Execution/Resolvers/ResolverContext.cs | 6 +++ .../Core/Execution/Utilities/ResolverTask.cs | 12 ++++- .../FieldValueCompletionContext.cs | 4 +- src/Core/Types/Resolvers/IResolverContext.cs | 9 +++- 8 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 src/Core/Core.Tests/Execution/Middleware/ScopedContextDataTests.cs diff --git a/src/Core/Core.Tests/Execution/Middleware/ScopedContextDataTests.cs b/src/Core/Core.Tests/Execution/Middleware/ScopedContextDataTests.cs new file mode 100644 index 00000000000..3f8f4e60b62 --- /dev/null +++ b/src/Core/Core.Tests/Execution/Middleware/ScopedContextDataTests.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ChilliCream.Testing; +using Xunit; + +namespace HotChocolate.Execution +{ + public class ScopedContextDataTests + { + [Fact] + public async Task ScopedContextDataIsPassedAllongCorrectly() + { + // arrange + bool allDataIsPassedAlong = false; + + ISchema schema = Schema.Create( + "type Query { nested: Nested } type Nested { foo: String }", + c => c.Use(next => context => + { + if (context.ScopedContextData.ContainsKey("field")) + { + allDataIsPassedAlong = true; + context.Result = "123"; + } + else + { + context.ScopedContextData = context.ScopedContextData.Add("field", "abc"); + context.Result = new { foo = "123"}; + } + + return Task.CompletedTask; + })); + + IQueryExecutor executor = schema.MakeExecutable( + b => b.UseDefaultPipeline() + ); + + // act + IExecutionResult result = await executor.ExecuteAsync( + new QueryRequest("{ nested { foo } }")); + + // assert + Assert.True(allDataIsPassedAlong); + } + } +} diff --git a/src/Core/Core/Execution/ExecutionStrategyBase.cs b/src/Core/Core/Execution/ExecutionStrategyBase.cs index 7b5422edbc2..bf0f38d2e25 100644 --- a/src/Core/Core/Execution/ExecutionStrategyBase.cs +++ b/src/Core/Core/Execution/ExecutionStrategyBase.cs @@ -163,7 +163,9 @@ protected IEnumerable CreateRootResolverTasks( fieldSelection, Path.New(fieldSelection.ResponseName), source, - result); + result, + ImmutableDictionary.Empty + ); } } diff --git a/src/Core/Core/Execution/Resolvers/DirectiveContext.cs b/src/Core/Core/Execution/Resolvers/DirectiveContext.cs index 7fb22e8ae76..533b21dec5e 100644 --- a/src/Core/Core/Execution/Resolvers/DirectiveContext.cs +++ b/src/Core/Core/Execution/Resolvers/DirectiveContext.cs @@ -68,6 +68,12 @@ public object Result public IDictionary ContextData => _middlewareContext.ContextData; + public IImmutableDictionary ScopedContextData + { + get => _middlewareContext.ScopedContextData; + set => _middlewareContext.ScopedContextData = value; + } + public T Argument(NameString name) => _middlewareContext.Argument(name); diff --git a/src/Core/Core/Execution/Resolvers/MiddlewareContext.cs b/src/Core/Core/Execution/Resolvers/MiddlewareContext.cs index edddba2ac3a..bec4e50f9c2 100644 --- a/src/Core/Core/Execution/Resolvers/MiddlewareContext.cs +++ b/src/Core/Core/Execution/Resolvers/MiddlewareContext.cs @@ -69,6 +69,12 @@ public object Result public IDictionary ContextData => _resolverContext.ContextData; + public IImmutableDictionary ScopedContextData + { + get => _resolverContext.ScopedContextData; + set => _resolverContext.ScopedContextData = value; + } + public T Argument(NameString name) => _resolverContext.Argument(name); diff --git a/src/Core/Core/Execution/Resolvers/ResolverContext.cs b/src/Core/Core/Execution/Resolvers/ResolverContext.cs index 19c5adc2eb8..6e53559579f 100644 --- a/src/Core/Core/Execution/Resolvers/ResolverContext.cs +++ b/src/Core/Core/Execution/Resolvers/ResolverContext.cs @@ -71,6 +71,12 @@ public ResolverContext( public IDictionary ContextData => _executionContext.ContextData; + public IImmutableDictionary ScopedContextData + { + get => _resolverTask.ScopedContextData; + set => _resolverTask.ScopedContextData = value; + } + public T Argument(NameString name) { if (string.IsNullOrEmpty(name)) diff --git a/src/Core/Core/Execution/Utilities/ResolverTask.cs b/src/Core/Core/Execution/Utilities/ResolverTask.cs index d0cdbd22bad..b1668047935 100644 --- a/src/Core/Core/Execution/Utilities/ResolverTask.cs +++ b/src/Core/Core/Execution/Utilities/ResolverTask.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; @@ -18,7 +18,8 @@ public ResolverTask( FieldSelection fieldSelection, Path path, IImmutableStack source, - IDictionary result) + IDictionary result, + IImmutableDictionary scopedContextData) { _executionContext = executionContext; Source = source; @@ -27,6 +28,7 @@ public ResolverTask( FieldType = fieldSelection.Field.Type; Path = path; _result = result; + ScopedContextData = scopedContextData; ResolverContext = new ResolverContext( executionContext, this, @@ -53,6 +55,12 @@ public ResolverTask( public object ResolverResult { get; set; } public FieldDelegate FieldDelegate { get; } + + public IImmutableDictionary ScopedContextData + { + get; + set; + } public void IntegrateResult(object value) { diff --git a/src/Core/Core/Execution/ValueCompletion/FieldValueCompletionContext.cs b/src/Core/Core/Execution/ValueCompletion/FieldValueCompletionContext.cs index a6000124d0d..729ffff4c58 100644 --- a/src/Core/Core/Execution/ValueCompletion/FieldValueCompletionContext.cs +++ b/src/Core/Core/Execution/ValueCompletion/FieldValueCompletionContext.cs @@ -173,7 +173,9 @@ public void EnqueueForProcessing( _enqueueResolverTask(new ResolverTask( ExecutionContext, objectType, field, Path.Append(field.ResponseName), - Source.Push(Value), objectResult)); + Source.Push(Value), objectResult, + _resolverTask.ScopedContextData) + ); } } diff --git a/src/Core/Types/Resolvers/IResolverContext.cs b/src/Core/Types/Resolvers/IResolverContext.cs index 7f234d584c8..6849029a4df 100644 --- a/src/Core/Types/Resolvers/IResolverContext.cs +++ b/src/Core/Types/Resolvers/IResolverContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -61,6 +61,13 @@ public interface IResolverContext /// IDictionary ContextData { get; } + /// + /// The scoped context data dictionary can be used by middlewares and + /// resolvers to store and retrieve data during execution scoped to the + /// hierarchy + /// + IImmutableDictionary ScopedContextData { get; set; } + /// /// Notifies when the connection underlying this request is aborted /// and thus request operations should be cancelled.