Skip to content
Open
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
16 changes: 12 additions & 4 deletions GraphQLSharp/Client/Exceptions/GraphQLHttpException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ namespace GraphQLSharp;

public class GraphQLHttpException : GraphQLException
{
public IReadOnlyList<string> Errors { get; }

public GraphQLHttpException(GraphQLRequest request, HttpResponse httpResponse, Exception ex)
: base(request, httpResponse, $"""
GraphQL request failed with an HTTP error:
Request: {request}
: this(new GraphQLHttpError { Request = request, HttpResponse = httpResponse }, ex)
{
}

public GraphQLHttpException(GraphQLHttpError error, Exception ex)
: base(error.Request, error.HttpResponse, $"""
GraphQL request failed with an HTTP error: {(error.errors != null ? string.Join("\n", error.errors) : "")}
Request: {error.Request}
""", ex)
{
Errors = Array.AsReadOnly(error.errors?.ToArray() ?? Array.Empty<string>());
}
}
}
17 changes: 15 additions & 2 deletions GraphQLSharp/Client/GraphQLClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,20 @@ private async Task<GraphQLResponse<T>> ExecuteCoreAsync<T>(TRequest request, Can
}
catch (Exception httpEx)
{
throw new GraphQLHttpException(request, httpResponse, httpEx);
GraphQLHttpError gqlHttpError = null;
try
{
gqlHttpError = await httpResponseMsg.Content.ReadFromJsonAsync<GraphQLHttpError>(_options.JsonSerializerOptions ?? Serializer.GetOptions(),
cancellationToken);
}
catch (Exception errorDetailsException) when (errorDetailsException is JsonException or InvalidOperationException)
{
// Error details are optional; preserve the HTTP failure for callers and retries.
}
gqlHttpError ??= new GraphQLHttpError();
gqlHttpError.Request = request;
gqlHttpError.HttpResponse = httpResponse;
throw new GraphQLHttpException(gqlHttpError, httpEx);
}

GraphQLResponse<T> res;
Expand Down Expand Up @@ -148,4 +161,4 @@ private HttpRequestMessage CreateHttpRequest(TRequest request, HttpClient httpCl
_options.ConfigureHttpRequestHeaders?.Invoke(requestMessage.Headers);
return requestMessage;
}
}
}
15 changes: 15 additions & 0 deletions GraphQLSharp/Client/Response/GraphQLHttpError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace GraphQLSharp;

public class GraphQLHttpError : IGraphQLObject
{
[JsonIgnore]
public GraphQLRequest Request { get; internal set; }

[JsonIgnore]
public HttpResponse HttpResponse { get; internal set; }

[JsonConverter(typeof(GraphQLHttpErrorMessageConverter))]
public List<string> errors { get; set; }
}
45 changes: 45 additions & 0 deletions GraphQLSharp/Serialization/GraphQLHttpErrorMessageConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace GraphQLSharp;

// Normalizes the value of an HTTP error response's "errors" property into individual messages:
// "Service unavailable" -> one message
// ["First error", "Second error"] -> multiple messages
// [{"message":"First"}, {"message":"Second"}] -> GraphQL error object messages
// Arrays may mix strings and error objects; message order is preserved.
// JSON null is handled by System.Text.Json and remains null.
internal sealed class GraphQLHttpErrorMessageConverter : JsonConverter<List<string>>
{
public override List<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
return [reader.GetString()];

using var document = JsonDocument.ParseValue(ref reader);
var errors = document.RootElement;
if (errors.ValueKind != JsonValueKind.Array)
return null;

var messages = new List<string>();
foreach (var error in errors.EnumerateArray())
{
if (error.ValueKind == JsonValueKind.String)
messages.Add(error.GetString());
else if (error.ValueKind == JsonValueKind.Object
&& error.TryGetProperty("message", out var message)
&& message.ValueKind == JsonValueKind.String)
messages.Add(message.GetString());
}
return messages;
}

public override void Write(Utf8JsonWriter writer, List<string> value, JsonSerializerOptions options)
{
// Non-null lists are always written as JSON string arrays, regardless of the input format.
writer.WriteStartArray();
foreach (var message in value)
writer.WriteStringValue(message);
writer.WriteEndArray();
}
}
Loading