Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ private async Task ExecuteInternalAsync(CancellationToken cancellationToken)
/// The cancellation token.
/// </param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
protected virtual ValueTask OnAfterCompletedAsync(CancellationToken cancellationToken) => ValueTask.CompletedTask;
protected virtual ValueTask OnAfterCompletedAsync(CancellationToken cancellationToken)
=> ValueTask.CompletedTask;

/// <summary>
/// Completes the task as faulted.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,79 @@
using System.Collections.Immutable;
using System.Text;

namespace HotChocolate.Fusion.Execution.Nodes;
namespace HotChocolate.Execution;

/// <summary>
/// Represents a path to a selection set or a field selection within a GraphQL operation.
/// </summary>
public sealed class SelectionPath : IEquatable<SelectionPath>
{
private readonly ImmutableArray<Segment> _segments;
private readonly Segment[] _segments;
private readonly int _length;

private SelectionPath(ImmutableArray<Segment> segments)
private SelectionPath(Segment[] segments, int length)
{
_segments = segments;
_length = length;
}

/// <summary>
/// Gets a value indicating whether this path represents the root of an operation.
/// The root of an operation is the root selection set.
/// </summary>
public bool IsRoot => _segments.IsEmpty;
public bool IsRoot => _length == 0;

/// <summary>
/// Gets the name of the last segment in the path, or <c>null</c> if the path is root.
/// </summary>
public string? Name => _length > 0 ? _segments[_length - 1].Name : null;

/// <summary>
/// Gets the parent path.
/// </summary>
public SelectionPath? Parent
=> _segments.Length > 0
? new SelectionPath(_segments.RemoveAt(_segments.Length - 1))
=> _length > 0
? new SelectionPath(_segments, _length - 1)
: null;

/// <summary>
/// Gets the segments that make up this path.
/// Gets the number of segments in this path.
/// </summary>
public int Length => _length;

/// <summary>
/// Gets the segment at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the segment to get.</param>
/// <returns>The segment at the specified index.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="index"/> is less than 0 or greater than or equal to <see cref="Length"/>.
/// </exception>
public Segment this[int index] => _segments[index];

/// <summary>
/// Returns a new <see cref="SelectionPath"/> sharing the same backing array
/// but with the specified number of segments.
/// </summary>
/// <value>
/// An immutable array of segments representing the path from the root path segment to the current path segment.
/// </value>
public ImmutableArray<Segment> Segments => _segments;
/// <param name="length">The number of segments to include.</param>
/// <returns>A new <see cref="SelectionPath"/> with the specified length.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="length"/> is less than 0 or greater than <see cref="Length"/>.
/// </exception>
public SelectionPath Slice(int length)
{
if ((uint)length > (uint)_length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}

if (length == 0)
{
return Root;
}

return new SelectionPath(_segments, length);
}

/// <summary>
/// Creates a new selection path by appending a field segment to the current path.
Expand All @@ -46,7 +84,12 @@ public SelectionPath? Parent
/// Thrown when <paramref name="fieldName"/> is <c>null</c>.
/// </exception>
public SelectionPath AppendField(string fieldName)
=> new(_segments.Add(new Segment(fieldName, SelectionPathSegmentKind.Field)));
{
var newSegments = new Segment[_length + 1];
Array.Copy(_segments, newSegments, _length);
newSegments[_length] = new Segment(fieldName, SelectionPathSegmentKind.Field);
return new SelectionPath(newSegments, newSegments.Length);
}

/// <summary>
/// Creates a new selection path by appending an inline fragment segment to the current path.
Expand All @@ -57,12 +100,17 @@ public SelectionPath AppendField(string fieldName)
/// Thrown when <paramref name="typeName"/> is <c>null</c>.
/// </exception>
public SelectionPath AppendFragment(string typeName)
=> new(_segments.Add(new Segment(typeName, SelectionPathSegmentKind.InlineFragment)));
{
var newSegments = new Segment[_length + 1];
Array.Copy(_segments, newSegments, _length);
newSegments[_length] = new Segment(typeName, SelectionPathSegmentKind.InlineFragment);
return new SelectionPath(newSegments, newSegments.Length);
}

/// <summary>
/// Gets the root selection path (empty path).
/// </summary>
public static SelectionPath Root { get; } = new([]);
public static SelectionPath Root { get; } = new([], 0);

/// <summary>
/// Parses a string representation of a selection path into a <see cref="SelectionPath"/> instance.
Expand Down Expand Up @@ -104,7 +152,7 @@ public static SelectionPath Parse(string s)
return Root;
}

var builder = ImmutableArray.CreateBuilder<Segment>();
var builder = new List<Segment>();
var i = 0;

while (i < s.Length)
Expand Down Expand Up @@ -163,7 +211,8 @@ public static SelectionPath Parse(string s)
}
}

return new SelectionPath(builder.ToImmutable());
var segments = builder.ToArray();
return new SelectionPath(segments, segments.Length);
}

/// <summary>
Expand All @@ -177,7 +226,8 @@ public static SelectionPath Parse(string s)
/// </returns>
public static SelectionPath From(ImmutableArray<Segment> segments)
{
return new SelectionPath(segments);
var array = segments.AsSpan().ToArray();
return new SelectionPath(array, array.Length);
}

/// <summary>
Expand Down Expand Up @@ -211,9 +261,15 @@ public SelectionPath RelativeTo(SelectionPath basePath)
return Root;
}

return !basePath.IsParentOfOrSame(this)
? throw new ArgumentException(null, nameof(basePath))
: new SelectionPath(_segments.RemoveRange(0, basePath._segments.Length));
if (!basePath.IsParentOfOrSame(this))
{
throw new ArgumentException(null, nameof(basePath));
}

var newLength = _length - basePath._length;
var newSegments = new Segment[newLength];
Array.Copy(_segments, basePath._length, newSegments, 0, newLength);
return new SelectionPath(newSegments, newLength);
}

/// <summary>
Expand All @@ -233,12 +289,12 @@ public SelectionPath RelativeTo(SelectionPath basePath)
/// </example>
public bool IsParentOfOrSame(SelectionPath other)
{
if (other._segments.Length < _segments.Length)
if (other._length < _length)
{
return false;
}

for (var i = 0; i < _segments.Length; i++)
for (var i = 0; i < _length; i++)
{
if (_segments[i].Kind != other._segments[i].Kind
|| !string.Equals(_segments[i].Name, other._segments[i].Name, StringComparison.Ordinal))
Expand All @@ -259,7 +315,19 @@ public bool IsParentOfOrSame(SelectionPath other)
/// otherwise, <c>false</c>.
/// </returns>
public bool Equals(SelectionPath? other)
=> other is not null && _segments.SequenceEqual(other._segments);
{
if (other is null)
{
return false;
}

if (_length != other._length)
{
return false;
}

return _segments.AsSpan(0, _length).SequenceEqual(other._segments.AsSpan(0, other._length));
}

/// <summary>
/// Determines whether this <see cref="SelectionPath"/> is equal to the specified object.
Expand All @@ -270,7 +338,7 @@ public bool Equals(SelectionPath? other)
/// otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object? obj)
=> ReferenceEquals(this, obj) || obj is SelectionPath p && Equals(p);
=> ReferenceEquals(this, obj) || (obj is SelectionPath p && Equals(p));

/// <summary>
/// Returns a hash code for this <see cref="SelectionPath"/>.
Expand All @@ -279,10 +347,10 @@ public override bool Equals(object? obj)
public override int GetHashCode()
{
var hash = new HashCode();
foreach (var s in _segments)
for (var i = 0; i < _length; i++)
{
hash.Add(s.Name);
hash.Add((int)s.Kind);
hash.Add(_segments[i].Name);
hash.Add((int)_segments[i].Kind);
}
return hash.ToHashCode();
}
Expand All @@ -304,16 +372,15 @@ public override int GetHashCode()
/// </example>
public override string ToString()
{
if (_segments.IsEmpty)
if (_length == 0)
{
return "$";
}

var sb = new StringBuilder();
sb.Append('$');

// Iterate forward through segments, not backward
for (var i = 0; i < _segments.Length; i++)
for (var i = 0; i < _length; i++)
{
var seg = _segments[i];

Expand Down Expand Up @@ -363,14 +430,14 @@ public sealed record Segment(string Name, SelectionPathSegmentKind Kind);
/// <returns>A new <see cref="Builder"/> instance.</returns>
public static Builder CreateBuilder() => new();

public static Builder CreateBuilder(SelectionPath path) => new(path.Segments);
public static Builder CreateBuilder(SelectionPath path) => new(path);

/// <summary>
/// A builder for creating <see cref="SelectionPath"/> instances.
/// </summary>
public readonly struct Builder
{
private readonly ImmutableArray<Segment>.Builder _segments = ImmutableArray.CreateBuilder<Segment>();
private readonly List<Segment> _segments = [];

/// <summary>
/// Initializes new instance of <see cref="Builder"/>.
Expand All @@ -382,9 +449,9 @@ public Builder()
/// <summary>
/// Initializes new instance of <see cref="Builder"/>.
/// </summary>
public Builder(ImmutableArray<Segment> segments)
public Builder(SelectionPath path)
{
_segments.AddRange(segments);
_segments.AddRange(path._segments.AsSpan(0, path._length));
}

/// <summary>
Expand Down Expand Up @@ -419,6 +486,10 @@ public Builder AppendFragment(string typeName)
/// Builds a <see cref="SelectionPath"/> from the segments in the builder.
/// </summary>
/// <returns>A new <see cref="SelectionPath"/> with the segments in the builder.</returns>
public SelectionPath Build() => new(_segments.ToImmutable());
public SelectionPath Build()
{
var segments = _segments.ToArray();
return new SelectionPath(segments, segments.Length);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace HotChocolate.Fusion.Execution.Nodes;
namespace HotChocolate.Execution;

public enum SelectionPathSegmentKind
{
Expand Down
Loading
Loading