From 233241bcd64833df0a71f1766872d8115e444306 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 14 Dec 2023 10:54:27 +1100 Subject: [PATCH] Using SystemTextJson instead of JsonNet to extract leaky bucket state for GraphQL --- ShopifySharp.Tests/Graph_Tests.cs | 1 + .../BucketStates/GraphQLBucketState.cs | 30 +++++++++---------- .../LeakyBucketExecutionPolicy.cs | 19 +++++++----- ShopifySharp/Infrastructure/RequestResult.cs | 2 +- ShopifySharp/Services/Graph/GraphService.cs | 8 +++++ 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/ShopifySharp.Tests/Graph_Tests.cs b/ShopifySharp.Tests/Graph_Tests.cs index 90447966..c783c016 100644 --- a/ShopifySharp.Tests/Graph_Tests.cs +++ b/ShopifySharp.Tests/Graph_Tests.cs @@ -45,6 +45,7 @@ public class Graph_Tests : IClassFixture public Graph_Tests(Graph_Tests_Fixture fixture) { _fixture = fixture; + _fixture.Service.SetExecutionPolicy(new LeakyBucketExecutionPolicy()); } [Fact(DisplayName = "Lists orders using the GraphService")] diff --git a/ShopifySharp/Infrastructure/BucketStates/GraphQLBucketState.cs b/ShopifySharp/Infrastructure/BucketStates/GraphQLBucketState.cs index 95980f2b..3dfafae7 100644 --- a/ShopifySharp/Infrastructure/BucketStates/GraphQLBucketState.cs +++ b/ShopifySharp/Infrastructure/BucketStates/GraphQLBucketState.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json.Linq; - -namespace ShopifySharp +namespace ShopifySharp { public class GraphQLBucketState { @@ -14,24 +12,26 @@ public class GraphQLBucketState public int? ActualQueryCost { get; private set; } - public static GraphQLBucketState Get(JToken response) + public static GraphQLBucketState Get(System.Text.Json.JsonDocument response) { - var cost = response.SelectToken("extensions.cost"); - if (cost == null) + if (!response.RootElement.TryGetProperty("extensions", out var extensions)) + return null; + if (!extensions.TryGetProperty("cost", out var cost)) return null; - var throttleStatus = cost["throttleStatus"]; - int maximumAvailable = (int)throttleStatus["maximumAvailable"]; - int restoreRate = (int)throttleStatus["restoreRate"]; - int currentlyAvailable = (int)throttleStatus["currentlyAvailable"]; - int requestedQueryCost = (int)cost["requestedQueryCost"]; - int? actualQueryCost = (int?)cost["actualQueryCost"];//actual query cost is null if THROTTLED + int requestedQueryCost = cost.GetProperty("requestedQueryCost").GetInt32(); + int? actualQueryCost = cost.TryGetProperty("actualQueryCost", out var actualQueryCostElt) && actualQueryCostElt.ValueKind != System.Text.Json.JsonValueKind.Null ? actualQueryCostElt.GetInt32() : null;//actual query cost is null if THROTTLED + + var throttleStatus = cost.GetProperty("throttleStatus"); + decimal maximumAvailable = throttleStatus.GetProperty("maximumAvailable").GetDecimal(); + decimal restoreRate = throttleStatus.GetProperty("restoreRate").GetDecimal(); + decimal currentlyAvailable = throttleStatus.GetProperty("currentlyAvailable").GetDecimal(); return new GraphQLBucketState { - MaxAvailable = maximumAvailable, - RestoreRate = restoreRate, - CurrentlyAvailable = currentlyAvailable, + MaxAvailable = (int)maximumAvailable, + RestoreRate = (int)restoreRate, + CurrentlyAvailable = (int)currentlyAvailable, RequestedQueryCost = requestedQueryCost, ActualQueryCost = actualQueryCost, }; diff --git a/ShopifySharp/Infrastructure/Policies/LeakyBucketPolicy/LeakyBucketExecutionPolicy.cs b/ShopifySharp/Infrastructure/Policies/LeakyBucketPolicy/LeakyBucketExecutionPolicy.cs index 2d344981..9fa13b0a 100644 --- a/ShopifySharp/Infrastructure/Policies/LeakyBucketPolicy/LeakyBucketExecutionPolicy.cs +++ b/ShopifySharp/Infrastructure/Policies/LeakyBucketPolicy/LeakyBucketExecutionPolicy.cs @@ -65,13 +65,16 @@ public async Task> Run(CloneableRequestMessage baseRequest, await bucket.WaitForAvailableGraphQLAsync(graphqlQueryCost.Value, cancellationToken); var graphRes = await executeRequestAsync(request); - var json = graphRes.Result as JToken; - if (graphRes.Result is System.Text.Json.JsonDocument jsonDoc) - json = JToken.Parse(jsonDoc.RootElement.ToString()); + var jsonDoc = graphRes.Result switch + { + System.Text.Json.JsonDocument systemTextJsonDoc => systemTextJsonDoc, + JToken jsonNetDoc => System.Text.Json.JsonDocument.Parse(jsonNetDoc.ToString(Newtonsoft.Json.Formatting.None)), + _ => throw new Exception($"Unexpected non json result of type {graphRes.Response.GetType().Name} for GraphQL Admin API") + }; if (bucket != null) { - var graphBucketState = graphRes.GetGraphQLBucketState(json); + var graphBucketState = graphRes.GetGraphQLBucketState(jsonDoc); if (graphBucketState != null) { int actualQueryCost = graphBucketState.ActualQueryCost ?? graphqlQueryCost.Value;//actual query cost is null if THROTTLED @@ -84,10 +87,10 @@ public async Task> Run(CloneableRequestMessage baseRequest, } } - if (json.SelectToken("errors") - ?.Children() - .Any(r => r.SelectToken("extensions.code")?.Value() == "THROTTLED") - == true) + if (jsonDoc.RootElement.TryGetProperty("errors", out var errors) && + errors.EnumerateArray().Any(error => error.TryGetProperty("extensions", out var extensions) && + extensions.TryGetProperty("code", out var code) && + code.GetString() == "THROTTLED")) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); break; diff --git a/ShopifySharp/Infrastructure/RequestResult.cs b/ShopifySharp/Infrastructure/RequestResult.cs index b2b28e81..aae492d7 100644 --- a/ShopifySharp/Infrastructure/RequestResult.cs +++ b/ShopifySharp/Infrastructure/RequestResult.cs @@ -29,7 +29,7 @@ public RestBucketState GetRestBucketState() return RestBucketState.Get(this.Response); } - public GraphQLBucketState GetGraphQLBucketState(JToken response) + public GraphQLBucketState GetGraphQLBucketState(System.Text.Json.JsonDocument response) { return GraphQLBucketState.Get(response); } diff --git a/ShopifySharp/Services/Graph/GraphService.cs b/ShopifySharp/Services/Graph/GraphService.cs index c4e748ef..52f412c3 100644 --- a/ShopifySharp/Services/Graph/GraphService.cs +++ b/ShopifySharp/Services/Graph/GraphService.cs @@ -63,6 +63,14 @@ public virtual Task SendAsync(string graphqlQuery, int? graphq return SendAsync(new GraphRequest { query = graphqlQuery }, graphqlQueryCost, cancellationToken); } + /// + /// Issue a single value query and return the value as an strongly typed object. + /// Use a type from the ShopifySharp.GraphQL namespace + /// + /// Use a type from the ShopifySharp.GraphQL namespace + /// + /// + /// public virtual async Task SendAsync(GraphRequest request, int? graphqlQueryCost = null, CancellationToken cancellationToken = default) where TResult : class {