Skip to content

Commit

Permalink
Introduce IResolveFieldContext.Parent (#2295)
Browse files Browse the repository at this point in the history
  • Loading branch information
sungam3r committed Feb 21, 2021
1 parent 8d6754e commit c277b10
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs2/site/docs/migrations/migration4.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,4 @@ lock (field)
* `ExecutionNode.PropagateNull` must be called before `ExecutionNode.ToValue`; see reference implementation
* `IDocumentValidator.ValidateAsync` does not take `originalQuery` parameter; use `Document.OriginalQuery` instead
* `IDocumentValidator.ValidateAsync` now returns `(IValidationResult validationResult, Variables variables)` tuple instead of single `IValidationResult` before
* `IResolveFieldContext.FieldName` and `IResolveFieldContext.ReturnType` properties have been removed, use `IResolveFieldContext.FieldAst.Name` and `IResolveFieldContext.FieldDefinition.ResolvedType` instead
3 changes: 3 additions & 0 deletions src/GraphQL.ApiTests/GraphQL.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ namespace GraphQL
GraphQL.Language.AST.Fragments Fragments { get; }
GraphQL.Instrumentation.Metrics Metrics { get; }
GraphQL.Language.AST.Operation Operation { get; }
GraphQL.IResolveFieldContext Parent { get; }
GraphQL.Types.IObjectGraphType ParentType { get; }
System.Collections.Generic.IEnumerable<object> Path { get; }
System.IServiceProvider RequestServices { get; }
Expand Down Expand Up @@ -289,6 +290,7 @@ namespace GraphQL
public GraphQL.Language.AST.Fragments Fragments { get; }
public GraphQL.Instrumentation.Metrics Metrics { get; }
public GraphQL.Language.AST.Operation Operation { get; }
public GraphQL.IResolveFieldContext Parent { get; }
public GraphQL.Types.IObjectGraphType ParentType { get; }
public System.Collections.Generic.IEnumerable<object> Path { get; }
public System.IServiceProvider RequestServices { get; }
Expand All @@ -315,6 +317,7 @@ namespace GraphQL
public GraphQL.Language.AST.Fragments Fragments { get; set; }
public GraphQL.Instrumentation.Metrics Metrics { get; set; }
public GraphQL.Language.AST.Operation Operation { get; set; }
public GraphQL.IResolveFieldContext Parent { get; set; }
public GraphQL.Types.IObjectGraphType ParentType { get; set; }
public System.Collections.Generic.IEnumerable<object> Path { get; set; }
public System.IServiceProvider RequestServices { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions src/GraphQL.MicrosoftDI/ScopedResolveFieldContextAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public ScopedResolveFieldContextAdapter(IResolveFieldContext baseContext, IServi

public IObjectGraphType ParentType => _baseContext.ParentType;

public IResolveFieldContext Parent => _baseContext.Parent;

public IDictionary<string, ArgumentValue> Arguments => _baseContext.Arguments;

public object RootValue => _baseContext.RootValue;
Expand Down
109 changes: 109 additions & 0 deletions src/GraphQL.Tests/Bugs/Issue899.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using GraphQL.Types;
using Shouldly;
using Xunit;

namespace GraphQL.Tests.Bugs
{
public class Issue899 : QueryTestBase<Issue899Schema>
{
[Fact]
public void Issue899_Should_Work()
{
var query = @"
query {
level1(arg1: ""1"") {
level2(arg2: ""2"") {
level3(arg3: ""3"") {
level4(arg4: ""4"")
}
}
}
}
";
var expected = @"{
""level1"": {
""level2"": [[{
""level3"": [{
""level4"": ""X""
}]
}]]
}
}";
AssertQuerySuccess(query, expected, null);
}
}

public class Issue899Schema : Schema
{
public Issue899Schema()
{
Query = new Issue899Query();
}
}

public class Issue899Query : ObjectGraphType
{
public Issue899Query()
{
Field<Issue899Level1>("level1", resolve: context =>
{
context.GetArgument<string>("arg1").ShouldBe("1");
context.Parent.ShouldBeNull();
return new object();
},
arguments: new QueryArguments(new QueryArgument<StringGraphType> { Name = "arg1" }));
}
}

public class Issue899Level1 : ObjectGraphType
{
public Issue899Level1()
{
Field<ListGraphType<ListGraphType<Issue899Level2>>>("level2", resolve: context =>
{
context.GetArgument<string>("arg2").ShouldBe("2");
context.Parent.GetArgument<string>("arg1").ShouldBe("1");
context.Parent.Parent.ShouldBeNull();
return new[] { new[] { new object() } };
},
arguments: new QueryArguments(new QueryArgument<StringGraphType> { Name = "arg2" }));
}
}

public class Issue899Level2 : ObjectGraphType
{
public Issue899Level2()
{
Field<ListGraphType<Issue899Level3>>("level3", resolve: context =>
{
context.GetArgument<string>("arg3").ShouldBe("3");
context.Parent.GetArgument<string>("arg2").ShouldBe("2");
context.Parent.Parent.GetArgument<string>("arg1").ShouldBe("1");
context.Parent.Parent.Parent.ShouldBeNull();
return new[] { new object() };
},
arguments: new QueryArguments(new QueryArgument<StringGraphType> { Name = "arg3" }));
}
}

public class Issue899Level3 : ObjectGraphType
{
public Issue899Level3()
{
Field<StringGraphType>("level4", resolve: context =>
{
context.GetArgument<string>("arg4").ShouldBe("4");
context.Parent.GetArgument<string>("arg3").ShouldBe("3");
context.Parent.Parent.GetArgument<string>("arg2").ShouldBe("2");
context.Parent.Parent.Parent.GetArgument<string>("arg1").ShouldBe("1");
context.Parent.Parent.Parent.Parent.ShouldBeNull();
return "X";
},
arguments: new QueryArguments(new QueryArgument<StringGraphType> { Name = "arg4" }));
}
}
}
10 changes: 8 additions & 2 deletions src/GraphQL/ResolveFieldContext/IResolveFieldContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ public interface IResolveFieldContext : IProvideUserContext
/// <summary>The field's parent graph type.</summary>
IObjectGraphType ParentType { get; }

/// <summary>
/// Provides access to the parent context (up to the root). This may be needed to get the parameters of parent nodes.
/// Returns <see langword="null"/> when called on the root.
/// </summary>
IResolveFieldContext Parent { get; }

/// <summary>
/// A dictionary of arguments passed to the field. It is recommended to use the
/// <see cref="GraphQL.ResolveFieldContextExtensions.GetArgument{TType}(IResolveFieldContext, string, TType)">GetArgument</see>
/// and <see cref="GraphQL.ResolveFieldContextExtensions.HasArgument(IResolveFieldContext, string)">HasArgument</see> extension
/// <see cref="ResolveFieldContextExtensions.GetArgument{TType}(IResolveFieldContext, string, TType)">GetArgument</see>
/// and <see cref="ResolveFieldContextExtensions.HasArgument(IResolveFieldContext, string)">HasArgument</see> extension
/// methods rather than this dictionary, so the names can be converted by the selected <see cref="INameConverter"/>.
/// </summary>
IDictionary<string, ArgumentValue> Arguments { get; }
Expand Down
23 changes: 23 additions & 0 deletions src/GraphQL/ResolveFieldContext/ReadonlyResolveFieldContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ namespace GraphQL
/// </summary>
public class ReadonlyResolveFieldContext : IResolveFieldContext<object>
{
// WARNING: if you add a new field here, then don't forget to clear it in Reset method!
private ExecutionNode _executionNode;
private ExecutionContext _executionContext;
private IDictionary<string, ArgumentValue> _arguments;
private Fields _subFields;
private IResolveFieldContext _parent;

/// <summary>
/// Initializes an instance with the specified <see cref="ExecutionNode"/> and <see cref="ExecutionContext"/>.
Expand All @@ -33,6 +35,8 @@ internal ReadonlyResolveFieldContext Reset(ExecutionNode node, ExecutionContext
_executionContext = context;
_arguments = null;
_subFields = null;
_parent = null;

return this;
}

Expand All @@ -58,6 +62,25 @@ private Fields GetSubFields()
/// <inheritdoc/>
public IObjectGraphType ParentType => _executionNode.GetParentType(_executionContext.Schema);

/// <inheritdoc/>
public IResolveFieldContext Parent
{
get
{
if (_parent == null)
{
var parent = _executionNode.Parent;
while (parent is ArrayExecutionNode)
parent = parent.Parent;

if (parent != null && !(parent is RootExecutionNode))
_parent = new ReadonlyResolveFieldContext(parent, _executionContext);
}

return _parent;
}
}

/// <inheritdoc/>
public IDictionary<string, ArgumentValue> Arguments => _arguments ??= GetArguments();

Expand Down
4 changes: 4 additions & 0 deletions src/GraphQL/ResolveFieldContext/ResolveFieldContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class ResolveFieldContext : IResolveFieldContext<object>
/// <inheritdoc/>
public IObjectGraphType ParentType { get; set; }

/// <inheritdoc/>
public IResolveFieldContext Parent { get; set; }

/// <inheritdoc/>
public IDictionary<string, ArgumentValue> Arguments { get; set; }

Expand Down Expand Up @@ -91,6 +94,7 @@ public ResolveFieldContext(IResolveFieldContext context)
FieldAst = context.FieldAst;
FieldDefinition = context.FieldDefinition;
ParentType = context.ParentType;
Parent = context.Parent;
Arguments = context.Arguments;
Schema = context.Schema;
Document = context.Document;
Expand Down
2 changes: 2 additions & 0 deletions src/GraphQL/ResolveFieldContext/ResolveFieldContextAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ internal ResolveFieldContextAdapter<T> Set(IResolveFieldContext baseContext)

public IObjectGraphType ParentType => _baseContext.ParentType;

public IResolveFieldContext Parent => _baseContext.Parent;

public IDictionary<string, ArgumentValue> Arguments => _baseContext.Arguments;

public object RootValue => _baseContext.RootValue;
Expand Down
4 changes: 2 additions & 2 deletions src/GraphQL/Types/Composite/IAbstractGraphType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace GraphQL.Types
{
/// <summary>
/// An interface for such graph types that do not represent concrete graph types, that is, for interfaces and unions.
/// An interface for such graph types that do not represent concrete graph types, that is, for interfaces and unions.
/// </summary>
public interface IAbstractGraphType : IGraphType
{
Expand Down Expand Up @@ -42,7 +42,7 @@ public static IObjectGraphType GetObjectType(this IAbstractGraphType abstractTyp
return result;
}

public static IObjectGraphType GetTypeOf(this IAbstractGraphType abstractType, object value)
public static IObjectGraphType GetTypeOf(this IAbstractGraphType abstractType, object value) //TODO: possible merge this into method above
{
foreach (var possible in abstractType.PossibleTypes.List)
{
Expand Down

0 comments on commit c277b10

Please sign in to comment.