< Summary

Information
Class: NGql.Tool.Executor
Assembly: NGql.Tool
File(s): /home/runner/work/NGql/NGql/tools/Tool/Executor.cs
Line coverage
100%
Covered lines: 45
Uncovered lines: 0
Coverable lines: 45
Total lines: 111
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ExecuteAsync()100%1010100%
WriteResponse(...)100%11100%
HasGraphQlErrors(...)100%66100%
.cctor()100%11100%

File(s)

/home/runner/work/NGql/NGql/tools/Tool/Executor.cs

#LineLine coverage
 1using System.Net.Http;
 2using System.Text;
 3using System.Text.Json;
 4
 5using Spectre.Console;
 6using Spectre.Console.Json;
 7
 8namespace NGql.Tool;
 9
 10/// <summary>
 11/// Sends a rendered GraphQL operation to a remote endpoint and reports the response.
 12/// </summary>
 13internal static class Executor
 14{
 15    public static async Task<int> ExecuteAsync(
 16        string graphql,
 17        string endpoint,
 18        IReadOnlyList<KeyValuePair<string, string>> headers,
 19        IReadOnlyDictionary<string, object?> variables)
 20    {
 3921        var body = new Dictionary<string, object?> { ["query"] = graphql };
 3922        if (variables.Count > 0)
 23        {
 324            body["variables"] = variables;
 25        }
 26
 3927        var bodyJson = JsonSerializer.Serialize(body, JsonWriteOptions);
 28
 3929        var content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
 30
 3931        using var http = new HttpClient();
 3932        using var req = new HttpRequestMessage(HttpMethod.Post, endpoint) { Content = content };
 33
 9034        foreach (var (name, value) in headers)
 35        {
 36            // Try request-scoped first; fall back to content-scoped for content headers.
 637            if (!req.Headers.TryAddWithoutValidation(name, value))
 38            {
 339                content.Headers.TryAddWithoutValidation(name, value);
 40            }
 41        }
 42
 43        HttpResponseMessage response;
 44        try
 45        {
 3946            response = await http.SendAsync(req);
 3647        }
 348        catch (HttpRequestException ex)
 49        {
 350            await Console.Error.WriteLineAsync($"ngql: HTTP request failed: {ex.Message}");
 351            return ExitCodes.HttpFailure;
 52        }
 53
 3654        var responseBody = await response.Content.ReadAsStringAsync();
 55
 3656        WriteResponse(responseBody);
 57
 3658        if (!response.IsSuccessStatusCode)
 59        {
 360            await Console.Error.WriteLineAsync($"ngql: HTTP {(int)response.StatusCode} {response.ReasonPhrase}");
 361            return ExitCodes.HttpFailure;
 62        }
 63
 3364        if (HasGraphQlErrors(responseBody))
 65        {
 366            return ExitCodes.GraphQlErrors;
 67        }
 68
 3069        return ExitCodes.Ok;
 3970    }
 71
 72    private static void WriteResponse(string responseBody)
 73    {
 74        // If the body parses as JSON, hand it to AnsiConsole as a JsonText so we get syntax
 75        // highlighting in interactive terminals. Spectre auto-detects redirection and falls
 76        // back to plain text when stdout isn't a TTY. If parsing fails (HTML error page,
 77        // plain text, etc.) write the raw body.
 78        try
 79        {
 3680            using var _ = JsonDocument.Parse(responseBody);
 3081            var console = AnsiConsole.Create(new AnsiConsoleSettings { Out = new AnsiConsoleOutput(Console.Out) });
 3082            console.Write(new JsonText(responseBody));
 3083            console.WriteLine();
 3084        }
 685        catch (JsonException)
 86        {
 687            Console.WriteLine(responseBody);
 688        }
 3689    }
 90
 91    private static bool HasGraphQlErrors(string responseBody)
 92    {
 93        try
 94        {
 3395            using var doc = JsonDocument.Parse(responseBody);
 3096            return doc.RootElement.ValueKind == JsonValueKind.Object
 3097                && doc.RootElement.TryGetProperty("errors", out var errors)
 3098                && errors.ValueKind == JsonValueKind.Array
 3099                && errors.GetArrayLength() > 0;
 100        }
 3101        catch (JsonException)
 102        {
 3103            return false;
 104        }
 33105    }
 106
 3107    private static readonly JsonSerializerOptions JsonWriteOptions = new()
 3108    {
 3109        WriteIndented = true,
 3110    };
 111}