Skip to content

Commit 86773a7

Browse files
authored
Allow for IDocumentStore to provide document hash. (#8228)
1 parent 2abdde9 commit 86773a7

File tree

9 files changed

+337
-22
lines changed

9 files changed

+337
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace HotChocolate.Execution;
2+
3+
/// <summary>
4+
/// Provides the hash of an operation document.
5+
/// </summary>
6+
public interface IOperationDocumentHashProvider
7+
{
8+
/// <summary>
9+
/// Gets the hash of the operation document.
10+
/// </summary>
11+
OperationDocumentHash Hash { get; }
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using HotChocolate.Language;
2+
3+
namespace HotChocolate.Execution;
4+
5+
/// <summary>
6+
/// Provides the document syntax node of an operation document.
7+
/// </summary>
8+
public interface IOperationDocumentNodeProvider
9+
{
10+
/// <summary>
11+
/// Gets the document syntax node of the operation document.
12+
/// </summary>
13+
DocumentNode Document { get; }
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using HotChocolate.Language;
2+
3+
namespace HotChocolate.Execution;
4+
5+
/// <summary>
6+
/// Represents the hash of an operation document.
7+
/// </summary>
8+
public readonly struct OperationDocumentHash
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="OperationDocumentHash"/> struct.
12+
/// </summary>
13+
/// <param name="hash">
14+
/// The hash of the operation document.
15+
/// </param>
16+
/// <param name="algorithm">
17+
/// The algorithm used to compute the hash.
18+
/// </param>
19+
/// <param name="format">
20+
/// The format of the hash.
21+
/// </param>
22+
/// <exception cref="ArgumentNullException">
23+
/// Thrown when <paramref name="hash"/> or <paramref name="algorithm"/> is <c>null</c>.
24+
/// </exception>
25+
public OperationDocumentHash(string hash, string algorithm, HashFormat format)
26+
{
27+
Hash = hash ?? throw new ArgumentNullException(nameof(hash));
28+
AlgorithmName = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
29+
Format = format;
30+
}
31+
32+
/// <summary>
33+
/// Gets the hash of the operation document.
34+
/// </summary>
35+
public string Hash { get; }
36+
37+
/// <summary>
38+
/// Gets the algorithm used to compute the hash.
39+
/// </summary>
40+
public string AlgorithmName { get; }
41+
42+
/// <summary>
43+
/// Gets the format of the hash.
44+
/// </summary>
45+
public HashFormat Format { get; }
46+
}

Diff for: src/HotChocolate/Core/src/Execution/Pipeline/DocumentCacheMiddleware.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public async ValueTask InvokeAsync(IRequestContext context)
3838
_documentCache.TryGetDocument(request.DocumentId.Value.Value, out var document))
3939
{
4040
context.DocumentId = request.DocumentId;
41+
context.DocumentHash = document.Hash;
4142
context.Document = document.Body;
4243
context.ValidationResult = DocumentValidatorResult.Ok;
4344
context.IsCachedDocument = true;
@@ -49,6 +50,7 @@ public async ValueTask InvokeAsync(IRequestContext context)
4950
_documentCache.TryGetDocument(request.DocumentHash, out document))
5051
{
5152
context.DocumentId = request.DocumentHash;
53+
context.DocumentHash = document.Hash;
5254
context.Document = document.Body;
5355
context.ValidationResult = DocumentValidatorResult.Ok;
5456
context.IsCachedDocument = true;
@@ -81,7 +83,18 @@ public async ValueTask InvokeAsync(IRequestContext context)
8183
{
8284
_documentCache.TryAddDocument(
8385
context.DocumentId.Value.Value,
84-
new CachedDocument(context.Document, context.IsPersistedDocument));
86+
new CachedDocument(context.Document, context.DocumentHash, context.IsPersistedDocument));
87+
88+
// The hash and the documentId can differ if the id is not a hash or
89+
// if the hash algorithm is different from the one that Hot Chocolate uses internally.
90+
// In the case they differ we just add another lookup to the cache.
91+
if(context.DocumentHash is not null)
92+
{
93+
_documentCache.TryAddDocument(
94+
context.DocumentHash,
95+
new CachedDocument(context.Document, context.DocumentHash, context.IsPersistedDocument));
96+
}
97+
8598
_diagnosticEvents.AddedDocumentToCache(context);
8699
}
87100
}

Diff for: src/HotChocolate/Core/src/Execution/Pipeline/ReadPersistedOperationMiddleware.cs

+32-15
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ internal sealed class ReadPersistedOperationMiddleware
1111
private readonly RequestDelegate _next;
1212
private readonly IExecutionDiagnosticEvents _diagnosticEvents;
1313
private readonly IOperationDocumentStorage _operationDocumentStorage;
14+
private readonly IDocumentHashProvider _documentHashAlgorithm;
1415
private readonly PersistedOperationOptions _options;
1516

1617
private ReadPersistedOperationMiddleware(
1718
RequestDelegate next,
1819
[SchemaService] IExecutionDiagnosticEvents diagnosticEvents,
1920
[SchemaService] IOperationDocumentStorage operationDocumentStorage,
21+
IDocumentHashProvider documentHashAlgorithm,
2022
PersistedOperationOptions options)
2123
{
2224
_next = next ??
@@ -25,6 +27,8 @@ private ReadPersistedOperationMiddleware(
2527
throw new ArgumentNullException(nameof(diagnosticEvents));
2628
_operationDocumentStorage = operationDocumentStorage ??
2729
throw new ArgumentNullException(nameof(operationDocumentStorage));
30+
_documentHashAlgorithm = documentHashAlgorithm ??
31+
throw new ArgumentNullException(nameof(documentHashAlgorithm));
2832
_options = options;
2933
}
3034

@@ -53,45 +57,58 @@ await _operationDocumentStorage.TryReadAsync(
5357
documentId.Value, context.RequestAborted)
5458
.ConfigureAwait(false);
5559

56-
if (operationDocument is OperationDocument parsedDoc)
60+
if (operationDocument is not null)
5761
{
5862
context.DocumentId = documentId;
59-
context.Document = parsedDoc.Document;
63+
context.Document = GetOrParseDocument(operationDocument);
64+
context.DocumentHash = GetDocumentHash(operationDocument);
6065
context.ValidationResult = DocumentValidatorResult.Ok;
6166
context.IsCachedDocument = true;
6267
context.IsPersistedDocument = true;
63-
if (_options.SkipPersistedDocumentValidation)
64-
{
65-
context.ValidationResult = DocumentValidatorResult.Ok;
66-
}
67-
_diagnosticEvents.RetrievedDocumentFromStorage(context);
68-
}
6968

70-
if (operationDocument is OperationDocumentSourceText sourceTextDoc)
71-
{
72-
context.DocumentId = documentId;
73-
context.Document = Utf8GraphQLParser.Parse(sourceTextDoc.AsSpan());
74-
context.ValidationResult = DocumentValidatorResult.Ok;
75-
context.IsCachedDocument = true;
76-
context.IsPersistedDocument = true;
7769
if (_options.SkipPersistedDocumentValidation)
7870
{
7971
context.ValidationResult = DocumentValidatorResult.Ok;
8072
}
73+
8174
_diagnosticEvents.RetrievedDocumentFromStorage(context);
8275
}
8376
}
8477
}
8578

79+
private static DocumentNode GetOrParseDocument(IOperationDocument document)
80+
{
81+
if (document is IOperationDocumentNodeProvider nodeProvider)
82+
{
83+
return nodeProvider.Document;
84+
}
85+
86+
return Utf8GraphQLParser.Parse(document.AsSpan());
87+
}
88+
89+
private string? GetDocumentHash(IOperationDocument document)
90+
{
91+
if (document is IOperationDocumentHashProvider hashProvider
92+
&& _documentHashAlgorithm.Name.Equals(hashProvider.Hash.AlgorithmName)
93+
&& _documentHashAlgorithm.Format.Equals(hashProvider.Hash.Format))
94+
{
95+
return hashProvider.Hash.Hash;
96+
}
97+
98+
return null;
99+
}
100+
86101
public static RequestCoreMiddleware Create()
87102
=> (core, next) =>
88103
{
89104
var diagnosticEvents = core.SchemaServices.GetRequiredService<IExecutionDiagnosticEvents>();
90105
var persistedOperationStore = core.SchemaServices.GetRequiredService<IOperationDocumentStorage>();
106+
var documentHashAlgorithm = core.Services.GetRequiredService<IDocumentHashProvider>();
91107
var middleware = new ReadPersistedOperationMiddleware(
92108
next,
93109
diagnosticEvents,
94110
persistedOperationStore,
111+
documentHashAlgorithm,
95112
core.Options.PersistedOperations);
96113
return context => middleware.InvokeAsync(context);
97114
};

Diff for: src/HotChocolate/Core/test/Execution.Tests/Pipeline/DocumentCacheMiddlewareTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public async Task RetrieveItemFromCache_DocumentFoundOnCache()
2525
.Build();
2626

2727
var document = Utf8GraphQLParser.Parse("{ a }");
28-
cache.TryAddDocument("a", new CachedDocument(document, false));
28+
cache.TryAddDocument("a", new CachedDocument(document, null, false));
2929

3030
var requestContext = new Mock<IRequestContext>();
3131
var schema = new Mock<ISchema>();
@@ -63,7 +63,7 @@ public async Task RetrieveItemFromCacheByHash_DocumentFoundOnCache()
6363
.Build();
6464

6565
var document = Utf8GraphQLParser.Parse("{ a }");
66-
cache.TryAddDocument("a", new CachedDocument(document, false));
66+
cache.TryAddDocument("a", new CachedDocument(document, null, false));
6767

6868
var requestContext = new Mock<IRequestContext>();
6969
var schema = new Mock<ISchema>();
@@ -101,7 +101,7 @@ public async Task RetrieveItemFromCache_DocumentNotFoundOnCache()
101101
.Build();
102102

103103
var document = Utf8GraphQLParser.Parse("{ a }");
104-
cache.TryAddDocument("b", new CachedDocument(document, false));
104+
cache.TryAddDocument("b", new CachedDocument(document, null, false));
105105

106106
var requestContext = new Mock<IRequestContext>();
107107
var schema = new Mock<ISchema>();

0 commit comments

Comments
 (0)