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
@@ -1,6 +1,7 @@
using System.Buffers;
using System.Text;
using System.Text.Json;
using HotChocolate.Fusion.Execution.Nodes;
using HotChocolate.Fusion.Transport;
using HotChocolate.Fusion.Transport.Serialization;
using HotChocolate.Language;
Expand All @@ -25,7 +26,7 @@ internal sealed class AliasBatchedRequestBody : IRequestBody
private readonly int _itemCount;
private readonly List<Utf8VariableDefinitionNode> _sharedVariables;
private readonly string? _operationName;
private readonly string _operationShortHash;
private readonly string _nameSafeShortHash;
private readonly ulong _compositionHash;
private readonly ErrorHandlingMode? _onError;

Expand Down Expand Up @@ -60,7 +61,7 @@ public AliasBatchedRequestBody(
_itemCount = itemCount;
_sharedVariables = sharedVariables;
_operationName = operationName;
_operationShortHash = operationShortHash;
_nameSafeShortHash = OperationShortHash.ToNameSafe(operationShortHash);
_compositionHash = compositionHash;
_onError = onError;
}
Expand Down Expand Up @@ -138,7 +139,7 @@ private void WriteQuery(JsonWriter writer, ReadOnlySpan<AliasBatchItem> items)
private int GetOperationNameLength()
=> (_operationName?.Length ?? AnonymousOperationName.Length)
+ 1
+ _operationShortHash.Length
+ _nameSafeShortHash.Length
+ Batch.Length
+ CompositionHashLength;

Expand All @@ -161,7 +162,7 @@ private ReadOnlySpan<byte> ComposeOperationName(Span<byte> buffer)
}

buffer[written++] = NameSeparator;
written += Encoding.ASCII.GetBytes(_operationShortHash, buffer[written..]);
written += Encoding.ASCII.GetBytes(_nameSafeShortHash, buffer[written..]);
Batch.CopyTo(buffer[written..]);
written += Batch.Length;
WriteHexLower(buffer[written..], _compositionHash);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace HotChocolate.Fusion.Execution.Nodes;

/// <summary>
/// Provides the short operation document hash that generated source schema operation names carry.
/// </summary>
internal static class OperationShortHash
{
/// <summary>
/// Replaces every character of a short hash that a GraphQL name cannot hold with an underscore.
/// Digits are kept in every position: a short hash is embedded in a name, never used as one.
/// </summary>
/// <param name="shortHash">
/// The short hash of the operation document.
/// </param>
/// <returns>
/// <paramref name="shortHash"/> itself when it holds GraphQL name characters only.
/// </returns>
public static string ToNameSafe(string shortHash)
{
var index = IndexOfNonNameCharacter(shortHash);

if (index < 0)
{
return shortHash;
}

var buffer = shortHash.ToCharArray();

for (var i = index; i < buffer.Length; i++)
{
if (!IsNameCharacter(buffer[i]))
{
buffer[i] = '_';
}
}

return new string(buffer);
}

private static int IndexOfNonNameCharacter(string shortHash)
{
for (var i = 0; i < shortHash.Length; i++)
{
if (!IsNameCharacter(shortHash[i]))
{
return i;
}
}

return -1;
}

private static bool IsNameCharacter(char value)
=> value is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9' or '_';
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using HotChocolate.Fusion.Execution.Nodes;
using HotChocolate.Fusion.Types.Directives;
using HotChocolate.Language;

Expand Down Expand Up @@ -138,11 +139,13 @@ public double BestCaseCost

public string CreateOperationName(int stepId)
{
var shortHash = OperationShortHash.ToNameSafe(ShortHash);

if (OperationDefinition.Name is null)
{
return $"Op_{ShortHash}_{stepId}";
return $"Op_{shortHash}_{stepId}";
}

return $"{OperationDefinition.Name.Value}_{ShortHash}_{stepId}";
return $"{OperationDefinition.Name.Value}_{shortHash}_{stepId}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,37 @@ public void WriteTo_Should_IncludeOnError_When_TheModeIsConfigured()
Assert.EndsWith("""},"onError":"NULL"}""", json, StringComparison.Ordinal);
}

[Fact]
public void WriteTo_Should_ReplaceTheCharacter_When_TheShortHashHoldsANonNameCharacter()
{
// arrange
using var memory = new ChunkedArrayWriter();
using var document = ParseLookup(Lookup);
var items = new[] { CreateItem(memory, document, """{"__fusion_1_id":"1"}""") };

// act
var json = Write(items, [], operationShortHash: "0123456-");

// assert
Assert.StartsWith(
"query Op_0123456__Batch_0123456789abcdef(",
ReadQuery(json),
StringComparison.Ordinal);
}

private static string Write(
AliasBatchItem[] items,
List<Utf8VariableDefinitionNode> sharedVariables,
ErrorHandlingMode? onError = null,
string? operationName = null)
string? operationName = null,
string operationShortHash = OperationShortHash)
{
var body = new AliasBatchedRequestBody(
items,
items.Length,
sharedVariables,
operationName,
OperationShortHash,
operationShortHash,
CompositionHash,
onError);
using var buffer = new PooledArrayWriter();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace HotChocolate.Fusion.Execution.Nodes;

public sealed class OperationShortHashTests
{
[Fact]
public void ToNameSafe_Should_ReturnTheSameInstance_When_TheShortHashHoldsNameCharactersOnly()
{
// arrange
const string shortHash = "f26ee48d";

// act
var nameSafe = OperationShortHash.ToNameSafe(shortHash);

// assert
Assert.Same(shortHash, nameSafe);
}

[Fact]
public void ToNameSafe_Should_KeepTheLeadingDigit_When_TheShortHashStartsWithOne()
{
// arrange
const string shortHash = "94a407d5";

// act
var nameSafe = OperationShortHash.ToNameSafe(shortHash);

// assert
Assert.Equal("94a407d5", nameSafe);
}

[Theory]
[InlineData("BnjEJe8-", "BnjEJe8_")]
[InlineData("-BnjEJe8", "_BnjEJe8")]
[InlineData("--------", "________")]
[InlineData("Bnj.Je8+", "Bnj_Je8_")]
[InlineData("BnjéJe8/", "Bnj_Je8_")]
public void ToNameSafe_Should_ReplaceTheCharacter_When_AGraphQLNameCannotHoldIt(
string shortHash,
string expected)
{
// act
var nameSafe = OperationShortHash.ToNameSafe(shortHash);

// assert
Assert.Equal(expected, nameSafe);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,24 @@ interface Node {
Assert.False(ValueCompletion.TryResolveType(default, interfaceType, out _));
}

[Fact]
public void Compile_Should_KeepTheShortHash_When_ItHoldsANonNameCharacter()
{
// arrange
var schema = CreateSchema();
var operationDefinition = Utf8GraphQLParser.Parse("{ product { id } }")
.Definitions
.OfType<OperationDefinitionNode>()
.First();
var compiler = new OperationCompiler(schema, _fieldMapPool);

// act
var operation = compiler.Compile("1", "0123456789abcdef", "BnjEJe8-", operationDefinition);

// assert
Assert.Equal("BnjEJe8-", operation.ShortHash);
}

public static FusionSchemaDefinition CreateSchema()
{
const string sourceText =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ public void CreatePlan_Should_KeepTheShortHash_When_ItHoldsNameCharactersOnly()
Assert.Equal("Op_abcdef12_1", GetOperationName(plan));
}

[Fact]
public void CreatePlan_Should_ReplaceTheCharacter_When_TheShortHashHoldsANonNameCharacter()
{
// arrange
var schema = CreateCompositeSchema();

// act
var plan = CreatePlan(schema, "BnjEJe8-");

// assert
Assert.Equal("Op_BnjEJe8__1", GetOperationName(plan));
}

[Fact]
public void CreatePlan_Should_KeepTheShortHash_When_TheOperationShortHashIsRead()
{
// arrange
var schema = CreateCompositeSchema();

// act
var plan = CreatePlan(schema, "BnjEJe8-");

// assert
Assert.Equal("BnjEJe8-", plan.Operation.ShortHash);
}

private static string GetOperationName(OperationPlan plan)
=> plan.AllNodes.OfType<OperationExecutionNode>().First().Operation.Name;

Expand Down
Loading