Skip to content

Commit 0b47264

Browse files
committed
move code for generation of the APQ extension into GraphQLRequest
1 parent 77a6d7e commit 0b47264

File tree

9 files changed

+94
-36
lines changed

9 files changed

+94
-36
lines changed

src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs

-4
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IG
1313
cancellationToken: cancellationToken);
1414
}
1515

16-
#if NET6_0_OR_GREATER
1716
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
1817
GraphQLQuery query, object? variables = null,
1918
string? operationName = null, Func<TResponse>? defineResponseType = null,
2019
CancellationToken cancellationToken = default)
2120
=> SendQueryAsync(client, query.Text, variables, operationName, defineResponseType,
2221
cancellationToken);
23-
#endif
2422

2523
public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this IGraphQLClient client,
2624
[StringSyntax("GraphQL")] string query, object? variables = null,
@@ -31,13 +29,11 @@ public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this
3129
cancellationToken: cancellationToken);
3230
}
3331

34-
#if NET6_0_OR_GREATER
3532
public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this IGraphQLClient client,
3633
GraphQLQuery query, object? variables = null, string? operationName = null, Func<TResponse>? defineResponseType = null,
3734
CancellationToken cancellationToken = default)
3835
=> SendMutationAsync(client, query.Text, variables, operationName, defineResponseType,
3936
cancellationToken);
40-
#endif
4137

4238
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
4339
GraphQLRequest request, Func<TResponse> defineResponseType, CancellationToken cancellationToken = default)

src/GraphQL.Client/GraphQL.Client.csproj

-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@
2222
<PackageReference Include="System.Net.WebSockets.Client.Managed" Version="1.0.22" />
2323
</ItemGroup>
2424

25-
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
26-
<PackageReference Include="System.Memory" Version="4.5.5" />
27-
</ItemGroup>
28-
2925
<ItemGroup>
3026
<ProjectReference Include="..\GraphQL.Client.Abstractions.Websocket\GraphQL.Client.Abstractions.Websocket.csproj" />
3127
<ProjectReference Include="..\GraphQL.Client.Abstractions\GraphQL.Client.Abstractions.csproj" />

src/GraphQL.Client/GraphQLHttpClient.cs

+3-7
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,15 @@ public async Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(GraphQLR
9696
{
9797
cancellationToken.ThrowIfCancellationRequested();
9898

99-
string? savedQuery = request.Query;
99+
string? savedQuery = null;
100100
bool useAPQ = false;
101101

102102
if (request.Query != null && !APQDisabledForSession && Options.EnableAutomaticPersistedQueries(request))
103103
{
104104
// https://www.apollographql.com/docs/react/api/link/persisted-queries/
105105
useAPQ = true;
106-
request.Extensions ??= new();
107-
request.Extensions["persistedQuery"] = new Dictionary<string, object>
108-
{
109-
["version"] = APQ_SUPPORTED_VERSION,
110-
["sha256Hash"] = Hash.Compute(request.Query),
111-
};
106+
request.GeneratePersistedQueryExtension();
107+
savedQuery = request.Query;
112108
request.Query = null;
113109
}
114110

src/GraphQL.Client/GraphQLHttpRequest.cs

-3
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,10 @@ public GraphQLHttpRequest([StringSyntax("GraphQL")] string query, object? variab
1919
: base(query, variables, operationName, extensions)
2020
{
2121
}
22-
23-
#if NET6_0_OR_GREATER
2422
public GraphQLHttpRequest(GraphQLQuery query, object? variables = null, string? operationName = null, Dictionary<string, object?>? extensions = null)
2523
: base(query, variables, operationName, extensions)
2624
{
2725
}
28-
#endif
2926

3027
public GraphQLHttpRequest(GraphQLRequest other)
3128
: base(other)

src/GraphQL.Primitives/GraphQL.Primitives.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
77
</PropertyGroup>
88

9+
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
10+
<PackageReference Include="System.Memory" Version="4.5.5" />
11+
</ItemGroup>
912
</Project>
+26-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
1-
#if NET6_0_OR_GREATER
21
using System.Diagnostics.CodeAnalysis;
3-
42
namespace GraphQL;
53

64
/// <summary>
7-
/// Value record for a GraphQL query string
5+
/// Value object representing a GraphQL query string and storing the corresponding APQ hash. <br />
6+
/// Use this to hold query strings you want to use more than once.
87
/// </summary>
9-
/// <param name="Text">the actual query string</param>
10-
public readonly record struct GraphQLQuery([StringSyntax("GraphQL")] string Text)
8+
public class GraphQLQuery : IEquatable<GraphQLQuery>
119
{
10+
/// <summary>
11+
/// The actual query string
12+
/// </summary>
13+
public string Text { get; }
14+
15+
/// <summary>
16+
/// The SHA256 hash used for the advanced persisted queries feature (APQ)
17+
/// </summary>
18+
public string Sha256Hash { get; }
19+
20+
public GraphQLQuery([StringSyntax("GraphQL")] string text)
21+
{
22+
Text = text;
23+
Sha256Hash = Hash.Compute(Text);
24+
}
25+
1226
public static implicit operator string(GraphQLQuery query)
1327
=> query.Text;
14-
};
15-
#endif
28+
29+
public bool Equals(GraphQLQuery other) => Sha256Hash == other.Sha256Hash;
30+
31+
public override bool Equals(object? obj) => obj is GraphQLQuery other && Equals(other);
32+
33+
public override int GetHashCode() => Sha256Hash.GetHashCode();
34+
}

src/GraphQL.Primitives/GraphQLRequest.cs

+27-5
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,29 @@ public class GraphQLRequest : Dictionary<string, object>, IEquatable<GraphQLRequ
1111
public const string QUERY_KEY = "query";
1212
public const string VARIABLES_KEY = "variables";
1313
public const string EXTENSIONS_KEY = "extensions";
14+
public const string EXTENSIONS_PERSISTED_QUERY_KEY = "persistedQuery";
15+
public const int APQ_SUPPORTED_VERSION = 1;
16+
17+
private string? _sha265Hash;
1418

1519
/// <summary>
16-
/// The Query
20+
/// The query string
1721
/// </summary>
1822
[StringSyntax("GraphQL")]
1923
public string? Query
2024
{
2125
get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null;
22-
set => this[QUERY_KEY] = value;
26+
set
27+
{
28+
this[QUERY_KEY] = value;
29+
// if the query string gets overwritten, reset the hash value
30+
if (_sha265Hash is not null)
31+
_sha265Hash = null;
32+
}
2333
}
2434

2535
/// <summary>
26-
/// The name of the Operation
36+
/// The operation to execute
2737
/// </summary>
2838
public string? OperationName
2939
{
@@ -59,16 +69,28 @@ public GraphQLRequest([StringSyntax("GraphQL")] string query, object? variables
5969
Extensions = extensions;
6070
}
6171

62-
#if NET6_0_OR_GREATER
6372
public GraphQLRequest(GraphQLQuery query, object? variables = null, string? operationName = null,
6473
Dictionary<string, object?>? extensions = null)
6574
: this(query.Text, variables, operationName, extensions)
6675
{
76+
_sha265Hash = query.Sha256Hash;
6777
}
68-
#endif
6978

7079
public GraphQLRequest(GraphQLRequest other) : base(other) { }
7180

81+
public void GeneratePersistedQueryExtension()
82+
{
83+
if (Query is null)
84+
throw new InvalidOperationException($"{nameof(Query)} is null");
85+
86+
Extensions ??= new();
87+
Extensions[EXTENSIONS_PERSISTED_QUERY_KEY] = new Dictionary<string, object>
88+
{
89+
["version"] = APQ_SUPPORTED_VERSION,
90+
["sha256Hash"] = _sha265Hash ?? Hash.Compute(Query),
91+
};
92+
}
93+
7294
/// <summary>
7395
/// Returns a value that indicates whether this instance is equal to a specified object
7496
/// </summary>

src/GraphQL.Client/Hash.cs renamed to src/GraphQL.Primitives/Hash.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
using System.Security.Cryptography;
44
using System.Text;
55

6-
namespace GraphQL.Client.Http;
6+
namespace GraphQL;
77

88
internal static class Hash
99
{
1010
private static SHA256? _sha256;
1111

1212
internal static string Compute(string query)
1313
{
14-
var expected = Encoding.UTF8.GetByteCount(query);
15-
var inputBytes = ArrayPool<byte>.Shared.Rent(expected);
16-
var written = Encoding.UTF8.GetBytes(query, 0, query.Length, inputBytes, 0);
17-
Debug.Assert(written == expected, $"Encoding.UTF8.GetBytes returned unexpected bytes: {written} instead of {expected}");
14+
int expected = Encoding.UTF8.GetByteCount(query);
15+
byte[]? inputBytes = ArrayPool<byte>.Shared.Rent(expected);
16+
int written = Encoding.UTF8.GetBytes(query, 0, query.Length, inputBytes, 0);
17+
Debug.Assert(written == expected, (string)$"Encoding.UTF8.GetBytes returned unexpected bytes: {written} instead of {expected}");
1818

1919
var shaShared = Interlocked.Exchange(ref _sha256, null) ?? SHA256.Create();
2020

@@ -33,7 +33,7 @@ internal static string Compute(string query)
3333
return Convert.ToHexString(bytes);
3434
#else
3535
var builder = new StringBuilder(bytes.Length * 2);
36-
foreach (var item in bytes)
36+
foreach (byte item in bytes)
3737
{
3838
builder.Append(item.ToString("x2"));
3939
}

tests/GraphQL.Integration.Tests/APQ/AdvancedPersistentQueriesTest.cs

+29
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,33 @@ query Human($id: String!){
7878
Assert.Equal(name, response.Data.Human.Name);
7979
StarWarsWebsocketClient.APQDisabledForSession.Should().BeFalse("if APQ has worked it won't get disabled");
8080
}
81+
82+
[Fact]
83+
public void Verify_the_persisted_query_extension_object()
84+
{
85+
var query = new GraphQLQuery("""
86+
query Human($id: String!){
87+
human(id: $id) {
88+
name
89+
}
90+
}
91+
""");
92+
query.Sha256Hash.Should().NotBeNullOrEmpty();
93+
94+
var request = new GraphQLRequest(query);
95+
request.Extensions.Should().BeNull();
96+
request.GeneratePersistedQueryExtension();
97+
request.Extensions.Should().NotBeNull();
98+
99+
string expectedKey = "persistedQuery";
100+
var expectedExtensionValue = new Dictionary<string, object>
101+
{
102+
["version"] = 1,
103+
["sha256Hash"] = query.Sha256Hash,
104+
};
105+
106+
request.Extensions.Should().ContainKey(expectedKey);
107+
request.Extensions![expectedKey].As<Dictionary<string, object>>()
108+
.Should().NotBeNull().And.BeEquivalentTo(expectedExtensionValue);
109+
}
81110
}

0 commit comments

Comments
 (0)