Skip to content

Allow for IDocumentStore to provide document hash. #8228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 4, 2025
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
@@ -0,0 +1,12 @@
namespace HotChocolate.Execution;

/// <summary>
/// Provides the hash of an operation document.
/// </summary>
public interface IOperationDocumentHashProvider
{
/// <summary>
/// Gets the hash of the operation document.
/// </summary>
OperationDocumentHash Hash { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using HotChocolate.Language;

namespace HotChocolate.Execution;

/// <summary>
/// Provides the document syntax node of an operation document.
/// </summary>
public interface IOperationDocumentNodeProvider
{
/// <summary>
/// Gets the document syntax node of the operation document.
/// </summary>
DocumentNode Document { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using HotChocolate.Language;

namespace HotChocolate.Execution;

/// <summary>
/// Represents the hash of an operation document.
/// </summary>
public readonly struct OperationDocumentHash
{
/// <summary>
/// Initializes a new instance of the <see cref="OperationDocumentHash"/> struct.
/// </summary>
/// <param name="hash">
/// The hash of the operation document.
/// </param>
/// <param name="algorithm">
/// The algorithm used to compute the hash.
/// </param>
/// <param name="format">
/// The format of the hash.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="hash"/> or <paramref name="algorithm"/> is <c>null</c>.
/// </exception>
public OperationDocumentHash(string hash, string algorithm, HashFormat format)
{
Hash = hash ?? throw new ArgumentNullException(nameof(hash));
AlgorithmName = algorithm ?? throw new ArgumentNullException(nameof(algorithm));
Format = format;
}

/// <summary>
/// Gets the hash of the operation document.
/// </summary>
public string Hash { get; }

/// <summary>
/// Gets the algorithm used to compute the hash.
/// </summary>
public string AlgorithmName { get; }

/// <summary>
/// Gets the format of the hash.
/// </summary>
public HashFormat Format { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public async ValueTask InvokeAsync(IRequestContext context)
_documentCache.TryGetDocument(request.DocumentId.Value.Value, out var document))
{
context.DocumentId = request.DocumentId;
context.DocumentHash = document.Hash;
context.Document = document.Body;
context.ValidationResult = DocumentValidatorResult.Ok;
context.IsCachedDocument = true;
Expand All @@ -49,6 +50,7 @@ public async ValueTask InvokeAsync(IRequestContext context)
_documentCache.TryGetDocument(request.DocumentHash, out document))
{
context.DocumentId = request.DocumentHash;
context.DocumentHash = document.Hash;
context.Document = document.Body;
context.ValidationResult = DocumentValidatorResult.Ok;
context.IsCachedDocument = true;
Expand Down Expand Up @@ -81,7 +83,18 @@ public async ValueTask InvokeAsync(IRequestContext context)
{
_documentCache.TryAddDocument(
context.DocumentId.Value.Value,
new CachedDocument(context.Document, context.IsPersistedDocument));
new CachedDocument(context.Document, context.DocumentHash, context.IsPersistedDocument));

// The hash and the documentId can differ if the id is not a hash or
// if the hash algorithm is different from the one that Hot Chocolate uses internally.
// In the case they differ we just add another lookup to the cache.
if(context.DocumentHash is not null)
{
_documentCache.TryAddDocument(
context.DocumentHash,
new CachedDocument(context.Document, context.DocumentHash, context.IsPersistedDocument));
}

_diagnosticEvents.AddedDocumentToCache(context);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ internal sealed class ReadPersistedOperationMiddleware
private readonly RequestDelegate _next;
private readonly IExecutionDiagnosticEvents _diagnosticEvents;
private readonly IOperationDocumentStorage _operationDocumentStorage;
private readonly IDocumentHashProvider _documentHashAlgorithm;
private readonly PersistedOperationOptions _options;

private ReadPersistedOperationMiddleware(
RequestDelegate next,
[SchemaService] IExecutionDiagnosticEvents diagnosticEvents,
[SchemaService] IOperationDocumentStorage operationDocumentStorage,
IDocumentHashProvider documentHashAlgorithm,
PersistedOperationOptions options)
{
_next = next ??
Expand All @@ -25,6 +27,8 @@ private ReadPersistedOperationMiddleware(
throw new ArgumentNullException(nameof(diagnosticEvents));
_operationDocumentStorage = operationDocumentStorage ??
throw new ArgumentNullException(nameof(operationDocumentStorage));
_documentHashAlgorithm = documentHashAlgorithm ??
throw new ArgumentNullException(nameof(documentHashAlgorithm));
_options = options;
}

Expand Down Expand Up @@ -53,45 +57,58 @@ await _operationDocumentStorage.TryReadAsync(
documentId.Value, context.RequestAborted)
.ConfigureAwait(false);

if (operationDocument is OperationDocument parsedDoc)
if (operationDocument is not null)
{
context.DocumentId = documentId;
context.Document = parsedDoc.Document;
context.Document = GetOrParseDocument(operationDocument);
context.DocumentHash = GetDocumentHash(operationDocument);
context.ValidationResult = DocumentValidatorResult.Ok;
context.IsCachedDocument = true;
context.IsPersistedDocument = true;
if (_options.SkipPersistedDocumentValidation)
{
context.ValidationResult = DocumentValidatorResult.Ok;
}
_diagnosticEvents.RetrievedDocumentFromStorage(context);
}

if (operationDocument is OperationDocumentSourceText sourceTextDoc)
{
context.DocumentId = documentId;
context.Document = Utf8GraphQLParser.Parse(sourceTextDoc.AsSpan());
context.ValidationResult = DocumentValidatorResult.Ok;
context.IsCachedDocument = true;
context.IsPersistedDocument = true;
if (_options.SkipPersistedDocumentValidation)
{
context.ValidationResult = DocumentValidatorResult.Ok;
}

_diagnosticEvents.RetrievedDocumentFromStorage(context);
}
}
}

private static DocumentNode GetOrParseDocument(IOperationDocument document)
{
if (document is IOperationDocumentNodeProvider nodeProvider)
{
return nodeProvider.Document;
}

return Utf8GraphQLParser.Parse(document.AsSpan());
}

private string? GetDocumentHash(IOperationDocument document)
{
if (document is IOperationDocumentHashProvider hashProvider
&& _documentHashAlgorithm.Name.Equals(hashProvider.Hash.AlgorithmName)
&& _documentHashAlgorithm.Format.Equals(hashProvider.Hash.Format))
{
return hashProvider.Hash.Hash;
}

return null;
}

public static RequestCoreMiddleware Create()
=> (core, next) =>
{
var diagnosticEvents = core.SchemaServices.GetRequiredService<IExecutionDiagnosticEvents>();
var persistedOperationStore = core.SchemaServices.GetRequiredService<IOperationDocumentStorage>();
var documentHashAlgorithm = core.Services.GetRequiredService<IDocumentHashProvider>();
var middleware = new ReadPersistedOperationMiddleware(
next,
diagnosticEvents,
persistedOperationStore,
documentHashAlgorithm,
core.Options.PersistedOperations);
return context => middleware.InvokeAsync(context);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task RetrieveItemFromCache_DocumentFoundOnCache()
.Build();

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

var requestContext = new Mock<IRequestContext>();
var schema = new Mock<ISchema>();
Expand Down Expand Up @@ -63,7 +63,7 @@ public async Task RetrieveItemFromCacheByHash_DocumentFoundOnCache()
.Build();

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

var requestContext = new Mock<IRequestContext>();
var schema = new Mock<ISchema>();
Expand Down Expand Up @@ -101,7 +101,7 @@ public async Task RetrieveItemFromCache_DocumentNotFoundOnCache()
.Build();

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

var requestContext = new Mock<IRequestContext>();
var schema = new Mock<ISchema>();
Expand Down
Loading
Loading