Skip to content

Commit

Permalink
move APQ code to SendQueryAsync method to allow usage over websocket,…
Browse files Browse the repository at this point in the history
… too
  • Loading branch information
rose-a committed Apr 22, 2024
1 parent d199fce commit bbdf13c
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 48 deletions.
86 changes: 42 additions & 44 deletions src/GraphQL.Client/GraphQLHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,57 +89,19 @@ public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serial

#region IGraphQLClient

/// <inheritdoc />
public async Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default)
{
return Options.UseWebSocketForQueriesAndMutations || Options.WebSocketEndPoint is not null && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme()
? await GraphQlHttpWebSocket.SendRequestAsync<TResponse>(request, cancellationToken).ConfigureAwait(false)
: await SendAPQHttpRequestAsync<TResponse>(request, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc />
public Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(GraphQLRequest request,
CancellationToken cancellationToken = default)
=> SendQueryAsync<TResponse>(request, cancellationToken);

/// <inheritdoc />
public IObservable<GraphQLResponse<TResponse>> CreateSubscriptionStream<TResponse>(GraphQLRequest request)
=> CreateSubscriptionStream<TResponse>(request, null);
private const int APQ_SUPPORTED_VERSION = 1;

/// <inheritdoc />
public IObservable<GraphQLResponse<TResponse>> CreateSubscriptionStream<TResponse>(GraphQLRequest request, Action<Exception>? exceptionHandler)
{
if (_disposed)
throw new ObjectDisposedException(nameof(GraphQLHttpClient));

var observable = GraphQlHttpWebSocket.CreateSubscriptionStream<TResponse>(request, exceptionHandler);
return observable;
}

#endregion

/// <inheritdoc />
public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket();

/// <inheritdoc />
public Task SendPingAsync(object? payload) => GraphQlHttpWebSocket.SendPingAsync(payload);

/// <inheritdoc />
public Task SendPongAsync(object? payload) => GraphQlHttpWebSocket.SendPongAsync(payload);

#region Private Methods

private async Task<GraphQLHttpResponse<TResponse>> SendAPQHttpRequestAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default)
public async Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var savedQuery = request.Query;
string? savedQuery = request.Query;
bool useAPQ = false;

if (request.Query != null && !_apqDisabledPerSession && Options.EnableAutomaticPersistedQueries(request))
{
// https://www.apollographql.com/docs/react/api/link/persisted-queries/
const int APQ_SUPPORTED_VERSION = 1;
useAPQ = true;
request.Extensions ??= new();
request.Extensions["persistedQuery"] = new Dictionary<string, object>
Expand All @@ -150,7 +112,7 @@ private async Task<GraphQLHttpResponse<TResponse>> SendAPQHttpRequestAsync<TResp
request.Query = null;
}

var response = await SendHttpRequestAsync<TResponse>(request, cancellationToken);
var response = await SendQueryInternalAsync<TResponse>(request, cancellationToken);

if (useAPQ)
{
Expand All @@ -161,21 +123,57 @@ private async Task<GraphQLHttpResponse<TResponse>> SendAPQHttpRequestAsync<TResp
// Alas, for the first time we did not guess and in vain removed Query, so we return Query and
// send request again. This is one-time "cache miss", not so scary.
request.Query = savedQuery;
return await SendHttpRequestAsync<TResponse>(request, cancellationToken);
return await SendQueryInternalAsync<TResponse>(request, cancellationToken);
}
else
{
// GraphQL server either supports APQ of some other version, or does not support it at all.
// Send a request for the second time. This is better than returning an error. Let the client work with APQ disabled.
_apqDisabledPerSession = Options.DisableAPQ(response);
request.Query = savedQuery;
return await SendHttpRequestAsync<TResponse>(request, cancellationToken);
return await SendQueryInternalAsync<TResponse>(request, cancellationToken);
}
}

return response;
}

/// <inheritdoc />
public Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(GraphQLRequest request,
CancellationToken cancellationToken = default)
=> SendQueryAsync<TResponse>(request, cancellationToken);

/// <inheritdoc />
public IObservable<GraphQLResponse<TResponse>> CreateSubscriptionStream<TResponse>(GraphQLRequest request)
=> CreateSubscriptionStream<TResponse>(request, null);

/// <inheritdoc />
public IObservable<GraphQLResponse<TResponse>> CreateSubscriptionStream<TResponse>(GraphQLRequest request, Action<Exception>? exceptionHandler)
{
if (_disposed)
throw new ObjectDisposedException(nameof(GraphQLHttpClient));

var observable = GraphQlHttpWebSocket.CreateSubscriptionStream<TResponse>(request, exceptionHandler);
return observable;
}

#endregion

/// <inheritdoc />
public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket();

/// <inheritdoc />
public Task SendPingAsync(object? payload) => GraphQlHttpWebSocket.SendPingAsync(payload);

/// <inheritdoc />
public Task SendPongAsync(object? payload) => GraphQlHttpWebSocket.SendPongAsync(payload);

#region Private Methods
private async Task<GraphQLResponse<TResponse>> SendQueryInternalAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default) =>
Options.UseWebSocketForQueriesAndMutations || Options.WebSocketEndPoint is not null && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme()
? await GraphQlHttpWebSocket.SendRequestAsync<TResponse>(request, cancellationToken).ConfigureAwait(false)
: await SendHttpRequestAsync<TResponse>(request, cancellationToken).ConfigureAwait(false);

private async Task<GraphQLHttpResponse<TResponse>> SendHttpRequestAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default)
{
var preprocessedRequest = await Options.PreprocessRequest(request, this).ConfigureAwait(false);
Expand Down
6 changes: 3 additions & 3 deletions src/GraphQL.Client/GraphQLHttpClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ public static bool DefaultIsValidResponseToDeserialize(HttpResponseMessage r)
/// A delegate which takes an <see cref="IGraphQLResponse"/> and returns a boolean to disable any future persisted queries for that session.
/// This defaults to disabling on PersistedQueryNotSupported or a 400 or 500 HTTP error.
/// </summary>
public Func<IGraphQLHttpResponse, bool> DisableAPQ { get; set; } = response =>
public Func<IGraphQLResponse, bool> DisableAPQ { get; set; } = response =>
{
return ((int)response.StatusCode >= 400 && (int)response.StatusCode < 600) ||
response.Errors?.Any(error => string.Equals(error.Message, "PersistedQueryNotSupported", StringComparison.CurrentCultureIgnoreCase)) == true;
return response.Errors?.Any(error => string.Equals(error.Message, "PersistedQueryNotSupported", StringComparison.CurrentCultureIgnoreCase)) == true
|| response is IGraphQLHttpResponse httpResponse && (int)httpResponse.StatusCode >= 400 && (int)httpResponse.StatusCode < 600;
};
}
2 changes: 1 addition & 1 deletion src/GraphQL.Primitives/GraphQLRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class GraphQLRequest : Dictionary<string, object>, IEquatable<GraphQLRequ
/// The Query
/// </summary>
[StringSyntax("GraphQL")]
public string Query
public string? Query
{
get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null;
set => this[QUERY_KEY] = value;
Expand Down

0 comments on commit bbdf13c

Please sign in to comment.