Skip to content

Commit

Permalink
Strawberry Shake Persisted Query Support (ChilliCream#3185)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Mar 4, 2021
1 parent fe5cbb8 commit e4be19f
Show file tree
Hide file tree
Showing 177 changed files with 2,726 additions and 1,231 deletions.
33 changes: 33 additions & 0 deletions src/StrawberryShake/Client/src/Core/DocumentHash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace StrawberryShake
{
/// <summary>
/// Represents the document hash.
/// </summary>
public readonly struct DocumentHash
{
/// <summary>
/// Creates a new instance of <see cref="DocumentHash"/>.
/// </summary>
/// <param name="algorithm">
/// The name of the hash algorithm.
/// </param>
/// <param name="value">
/// The document hash value.
/// </param>
public DocumentHash(string algorithm, string value)
{
Algorithm = algorithm;
Value = value;
}

/// <summary>
/// Gets the name of the hash algorithm.
/// </summary>
public string Algorithm { get; }

/// <summary>
/// Gets the document hash value.
/// </summary>
public string Value { get; }
}
}

This file was deleted.

5 changes: 5 additions & 0 deletions src/StrawberryShake/Client/src/Core/IDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@ public interface IDocument
/// Gets the GraphQL document body.
/// </summary>
ReadOnlySpan<byte> Body { get; }

/// <summary>
/// Gets the document hash.
/// </summary>
DocumentHash Hash { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public class JsonOperationRequestSerializer
public void Serialize(OperationRequest request, IBufferWriter<byte> bufferWriter)
{
using var jsonWriter = new Utf8JsonWriter(bufferWriter);

Serialize(request, jsonWriter);
}

Expand All @@ -28,12 +27,11 @@ public void Serialize(OperationRequest request, Utf8JsonWriter jsonWriter)

private static void WriteRequest(OperationRequest request, Utf8JsonWriter writer)
{
if (request.Id is not null)
if (request.Strategy == RequestStrategy.PersistedQuery)
{
writer.WriteString("id", request.Id);
}

if (request.Document.Body.Length > 0)
else
{
writer.WriteString("query", request.Document.Body);
}
Expand Down
89 changes: 67 additions & 22 deletions src/StrawberryShake/Client/src/Core/OperationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,79 @@

namespace StrawberryShake
{
/// <summary>
/// Represents an operation request that is send to the GraphQL server.
/// </summary>
public sealed class OperationRequest : IEquatable<OperationRequest>
{
private readonly IReadOnlyDictionary<string, object?> _variables;
private Dictionary<string, object?>? _extensions;
private Dictionary<string, object?>? _contextData;

/// <summary>
/// Creates a new instance of <see cref="OperationRequest"/>.
/// </summary>
/// <param name="name">The operation name.</param>
/// <param name="document">The GraphQL query document containing this operation.</param>
/// <param name="variables">The request variable values.</param>
/// <param name="strategy">The request strategy to the connection.</param>
public OperationRequest(
string name,
IDocument document,
IReadOnlyDictionary<string, object?>? variables = null)
: this(null, name, document, variables)
IReadOnlyDictionary<string, object?>? variables = null,
RequestStrategy strategy = RequestStrategy.Default)
: this(null, name, document, variables, strategy)
{
}

/// <summary>
/// Creates a new instance of <see cref="OperationRequest"/>.
/// </summary>
/// <param name="id">The the optional request id.</param>
/// <param name="name">The operation name.</param>
/// <param name="document">The GraphQL query document containing this operation.</param>
/// <param name="variables">The request variable values.</param>
/// <param name="strategy">The request strategy to the connection.</param>
public OperationRequest(
string? id,
string name,
IDocument document,
IReadOnlyDictionary<string, object?>? variables = null)
IReadOnlyDictionary<string, object?>? variables = null,
RequestStrategy strategy = RequestStrategy.Default)
{
Id = id;
Name = name ?? throw new ArgumentNullException(nameof(name));
Document = document ?? throw new ArgumentNullException(nameof(document));
_variables = variables ?? ImmutableDictionary<string, object?>.Empty;
Strategy = strategy;
}

/// <summary>
/// Deconstructs <see cref="OperationRequest"/>.
/// </summary>
/// <param name="id">The the optional request id.</param>
/// <param name="name">The operation name.</param>
/// <param name="document">The GraphQL query document containing this operation.</param>
/// <param name="variables">The request variable values.</param>
/// <param name="extensions">The request extension values.</param>
/// <param name="contextData">The local context data.</param>
/// <param name="strategy">The request strategy to the connection.</param>
public void Deconstruct(
out string? id,
out string name,
out IDocument document,
out IReadOnlyDictionary<string, object?> variables,
out IReadOnlyDictionary<string, object?>? extensions,
out IReadOnlyDictionary<string, object?>? contextData)
out IReadOnlyDictionary<string, object?>? contextData,
out RequestStrategy strategy)
{
id = Id;
name = Name;
document = Document;
variables = _variables;
extensions = _extensions;
contextData = _contextData;
strategy = Strategy;
}

/// <summary>
Expand All @@ -62,7 +95,7 @@ public void Deconstruct(
public IDocument Document { get; }

/// <summary>
/// Gets the variable values.
/// Gets the request variable values.
/// </summary>
public IReadOnlyDictionary<string, object?> Variables => _variables;

Expand All @@ -88,12 +121,23 @@ public void Deconstruct(
}
}

/// <summary>
/// Defines the request strategy to the connection.
/// </summary>
public RequestStrategy Strategy { get; }

/// <summary>
/// Gets the request extension values or null.
/// </summary>
public IReadOnlyDictionary<string, object?>? GetExtensionsOrNull() =>
_extensions;

/// <summary>
/// Gets the request context data values or null.
/// </summary>
public IReadOnlyDictionary<string, object?>? GetContextDataOrNull() =>
_contextData;

public bool Equals(OperationRequest? other)
{
if (ReferenceEquals(null, other))
Expand All @@ -107,9 +151,9 @@ public bool Equals(OperationRequest? other)
}

return Id == other.Id &&
Name == other.Name &&
Document.Equals(other.Document) &&
EqualsVariables(other._variables);
Name == other.Name &&
Document.Equals(other.Document) &&
EqualsVariables(other._variables);
}

public override bool Equals(object? obj)
Expand Down Expand Up @@ -147,8 +191,8 @@ private bool EqualsVariables(IReadOnlyDictionary<string, object?> others)

foreach (var key in _variables.Keys)
{
if(!_variables.TryGetValue(key, out object? a) ||
!others.TryGetValue(key, out object? b))
if (!_variables.TryGetValue(key, out object? a) ||
!others.TryGetValue(key, out object? b))
{
return false;
}
Expand All @@ -162,21 +206,22 @@ private bool EqualsVariables(IReadOnlyDictionary<string, object?> others)
return true;
}

public override int GetHashCode()
{
unchecked
{
var hash = (Id?.GetHashCode() ?? 0) * 397 ^
public override int GetHashCode()
{
unchecked
{
var hash =
(Id?.GetHashCode() ?? 0) * 397 ^
Name.GetHashCode() * 397 ^
Document.GetHashCode() * 397;

foreach (KeyValuePair<string, object?> variable in _variables)
{
hash ^= variable.GetHashCode();
}
foreach (KeyValuePair<string, object?> variable in _variables)
{
hash ^= variable.GetHashCode();
}

return hash;
}
}
return hash;
}
}
}
}
24 changes: 24 additions & 0 deletions src/StrawberryShake/Client/src/Core/RequestStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace StrawberryShake
{
/// <summary>
/// Specifies the GraphQL request strategy.
/// </summary>
public enum RequestStrategy
{
/// <summary>
/// The full GraphQL query is send.
/// </summary>
Default,

/// <summary>
/// An id is send representing the query that is stored on the server.
/// </summary>
PersistedQuery,

/// <summary>
/// The full GraphQL query is only send if the server has not yet stored the
/// persisted query.
/// </summary>
AutomaticPersistedQuery
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Collections.Generic;

namespace StrawberryShake.Serialization
{
public static class BuiltInScalarNames
Expand All @@ -22,29 +20,5 @@ public static class BuiltInScalarNames
public const string ByteArray = "ByteArray";
public const string Any = "Any";
public const string TimeSpan = "TimeSpan";

private static readonly HashSet<string> _typeNames = new()
{
String,
ID,
Boolean,
Byte,
Short,
Int,
Long,
Float,
Decimal,
Url,
Uuid,
DateTime,
Date,
MultiplierPath,
Name,
ByteArray,
Any,
TimeSpan
};

public static bool IsBuiltInScalar(string typeName) => _typeNames.Contains(typeName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using HotChocolate;
using HotChocolate.Execution;
using HotChocolate.Language;
using Microsoft.Extensions.DependencyInjection;

namespace StrawberryShake.Transport.InMemory
Expand Down Expand Up @@ -56,11 +57,21 @@ public async ValueTask<IExecutionResult> ExecuteAsync(
throw ThrowHelper.InMemoryClient_NoExecutorConfigured(_name);
}

IQueryRequestBuilder requestBuilder = QueryRequestBuilder
.New()
.SetOperation(request.Name)
.SetQuery(request.Document.Print())
.SetVariableValues(request.Variables);
var requestBuilder = new QueryRequestBuilder();

if (request.Id is null)
{
requestBuilder.SetQuery(Utf8GraphQLParser.Parse(request.Document.Body));
}
else
{
requestBuilder.SetQueryId(request.Id);
}

requestBuilder.SetOperation(request.Name);
requestBuilder.SetVariableValues(request.Variables);
requestBuilder.SetExtensions(request.GetExtensionsOrNull());
requestBuilder.SetProperties(request.GetContextDataOrNull());

IServiceProvider applicationService = Executor.Services.GetApplicationServices();
foreach (var interceptor in RequestInterceptors)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ private GetHeroQueryDocument() { }

public ReadOnlySpan<byte> Body => _body;

public DocumentHash Hash { get; } = new("MD5", "ABC");

public override string ToString() => _bodyString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ public void Serialize_Request_With_Extensions()
private class Document : IDocument
{
public OperationKind Kind => OperationKind.Query;

public ReadOnlySpan<byte> Body => Encoding.UTF8.GetBytes("{ __typename }");

public DocumentHash Hash { get; } = new("MD5", "ABCDEF");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ public void Deconstruct()
IReadOnlyDictionary<string, object?> vars;
IReadOnlyDictionary<string, object?>? ext;
IReadOnlyDictionary<string, object?>? contextData;
(id, name, doc, vars, ext, contextData) = request;
RequestStrategy strategy;
(id, name, doc, vars, ext, contextData, strategy) = request;

// assert
Assert.Equal(request.Id, id);
Expand All @@ -127,6 +128,7 @@ public void Deconstruct()
Assert.Equal(request.Variables, vars);
Assert.Null(ext);
Assert.Null(contextData);
Assert.Equal(request.Strategy, strategy);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public MockDocument(string query)
public OperationKind Kind => OperationKind.Query;

public ReadOnlySpan<byte> Body => _query;

public DocumentHash Hash { get; } = new("MD5", "ABC");
}
}
}
Loading

0 comments on commit e4be19f

Please sign in to comment.