Skip to content

Commit

Permalink
[Fusion vNext] Add support for @Skip / @include (#7788)
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler authored Dec 6, 2024
1 parent 1b0e798 commit 9736539
Show file tree
Hide file tree
Showing 12 changed files with 1,166 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Text.Json;

namespace HotChocolate.Fusion.Planning.Nodes;

public sealed class ConditionPlanNode : PlanNode, ISerializablePlanNode, IPlanNodeProvider
{
private readonly List<PlanNode> _nodes = [];

public ConditionPlanNode(
string variableName,
bool passingValue,
PlanNode? parent = null)
{
VariableName = variableName;
PassingValue = passingValue;
Parent = parent;
}

/// <summary>
/// The name of the variable that controls if this node is executed.
/// </summary>
public string VariableName { get; }

/// <summary>
/// The value the <see cref="VariableName"/> has to be, in order
/// for this node to be executed.
/// </summary>
public bool PassingValue { get; }

public IReadOnlyList<PlanNode> Nodes => _nodes;

public void AddChildNode(PlanNode node)
{
ArgumentNullException.ThrowIfNull(node);
_nodes.Add(node);
node.Parent = this;
}

public PlanNodeKind Kind => PlanNodeKind.Condition;

public void Serialize(Utf8JsonWriter writer)
{
writer.WriteStartObject();
SerializationHelper.WriteKind(writer, this);
writer.WriteString("variableName", VariableName);
writer.WriteBoolean("passingValue", PassingValue);
SerializationHelper.WriteChildNodes(writer, this);
writer.WriteEndObject();
}
}

public record Condition(string VariableName, bool PassingValue);
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class FieldPlanNode : SelectionPlanNode
public FieldPlanNode(
FieldNode fieldNode,
OutputFieldInfo field)
: base(field.Type.NamedType(), fieldNode.SelectionSet?.Selections)
: base(field.Type.NamedType(), fieldNode.SelectionSet?.Selections, fieldNode.Directives)
{
FieldNode = fieldNode;
Field = field;
Expand All @@ -38,7 +38,7 @@ public FieldPlanNode(
public OutputFieldInfo Field { get; }

public IReadOnlyList<ArgumentAssignment> Arguments
=> _arguments ?? (IReadOnlyList<ArgumentAssignment>)Array.Empty<ArgumentAssignment>();
=> _arguments ?? [];

public void AddArgument(ArgumentAssignment argument)
{
Expand All @@ -49,10 +49,19 @@ public void AddArgument(ArgumentAssignment argument)

public FieldNode ToSyntaxNode()
{
var directives = new List<DirectiveNode>(Directives.ToSyntaxNode());

foreach (var condition in Conditions)
{
var directiveName = condition.PassingValue ? "include" : "skip";
directives.Add(new DirectiveNode(directiveName,
new ArgumentNode("if", new VariableNode(condition.VariableName))));
}

return new FieldNode(
new NameNode(Field.Name),
Field.Name.Equals(ResponseName) ? null : new NameNode(ResponseName),
Directives.ToSyntaxNode(),
directives,
Arguments.ToSyntaxNode(),
Selections.Count == 0 ? null : Selections.ToSyntaxNode());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public sealed class InlineFragmentPlanNode : SelectionPlanNode
public InlineFragmentPlanNode(
ICompositeNamedType declaringType,
IReadOnlyList<ISelectionNode> selectionNodes)
: base(declaringType, selectionNodes)
: base(declaringType, selectionNodes, [])
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public OperationPlanNode(
ICompositeNamedType declaringType,
SelectionSetNode selectionSet,
PlanNode? parent = null)
: base(declaringType, selectionSet.Selections)
: base(declaringType, selectionSet.Selections, [])
{
SchemaName = schemaName;
Parent = parent;
Expand All @@ -30,7 +30,7 @@ public OperationPlanNode(
ICompositeNamedType declaringType,
IReadOnlyList<ISelectionNode> selections,
PlanNode? parent = null)
: base(declaringType, selections)
: base(declaringType, selections, [])
{
SchemaName = schemaName;
Parent = parent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ namespace HotChocolate.Fusion.Planning;
public enum PlanNodeKind
{
Root,
Operation
Operation,
Condition
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public abstract class SelectionPlanNode : PlanNode
{
private List<CompositeDirective>? _directives;
private List<SelectionPlanNode>? _selections;
private List<Condition>? _conditions;

/// <summary>
/// Initializes a new instance of <see cref="SelectionPlanNode"/>.
Expand All @@ -20,13 +21,36 @@ public abstract class SelectionPlanNode : PlanNode
/// <param name="selectionNodes">
/// The child selection syntax nodes of this selection.
/// </param>
/// <param name="directiveNodes">
/// The directives applied to this selection.
/// </param>
protected SelectionPlanNode(
ICompositeNamedType declaringType,
IReadOnlyList<ISelectionNode>? selectionNodes)
IReadOnlyList<ISelectionNode>? selectionNodes,
IReadOnlyList<DirectiveNode> directiveNodes)
{
DeclaringType = declaringType;
IsEntity = declaringType.IsEntity();
SelectionNodes = selectionNodes;

foreach (var directive in directiveNodes)
{
var isSkipDirective = directive.Name.Value.Equals("skip");
var isIncludeDirective = directive.Name.Value.Equals("include");

// TODO: Ideally this would be just a lookup to the directive
if (isSkipDirective || isIncludeDirective)
{
var ifArgument = directive.Arguments.FirstOrDefault(
t => t.Name.Value.Equals("if"));

if (ifArgument?.Value is VariableNode variableNode)
{
var condition = new Condition(variableNode.Name.Value, isIncludeDirective);
(_conditions ??= []).Add(condition);
}
}
}
}

/// <summary>
Expand Down Expand Up @@ -56,6 +80,8 @@ public IReadOnlyList<CompositeDirective> Directives
public IReadOnlyList<SelectionPlanNode> Selections
=> _selections ?? (IReadOnlyList<SelectionPlanNode>)Array.Empty<SelectionPlanNode>();

public IReadOnlyList<Condition> Conditions => _conditions ?? [];

/// <summary>
/// Adds a child selection to this selection.
/// </summary>
Expand All @@ -66,7 +92,7 @@ public void AddSelection(SelectionPlanNode selection)
{
ArgumentNullException.ThrowIfNull(selection);

if(selection is OperationPlanNode)
if (selection is OperationPlanNode)
{
throw new NotSupportedException(
"An operation cannot be a child of a selection.");
Expand All @@ -87,4 +113,17 @@ public void AddDirective(CompositeDirective directive)
ArgumentNullException.ThrowIfNull(directive);
(_directives ??= []).Add(directive);
}

// TODO: Maybe remove
public void RemoveCondition(Condition condition)
{
ArgumentNullException.ThrowIfNull(condition);

if (_conditions is null)
{
return;
}

_conditions.Remove(condition);
}
}
Loading

0 comments on commit 9736539

Please sign in to comment.