From 4ec67cd6837ecb7de6aeb1acafafe269b60b262e Mon Sep 17 00:00:00 2001 From: Joe Pill Date: Thu, 20 Jun 2024 10:42:43 -0500 Subject: [PATCH] wip --- ShipEngine/Models/Dto/Common/Customs.cs | 2 +- .../Models/Dto/CreateLabelFromRate/Result.cs | 2 +- .../CreateLabelFromShipmentDetails/Result.cs | 2 +- .../Dto/GetRatesFromShipmentDetails/Params.cs | 3 + .../Dto/GetRatesFromShipmentDetails/Result.cs | 59 +-- .../Models/Dto/VoidLabelWithLabelId/Result.cs | 2 +- ShipEngine/Models/ShipEngineException.cs | 8 +- ShipEngine/ShipEngine.cs | 373 +++++++++++++++++- ShipEngine/ShipEngineClient.cs | 40 +- 9 files changed, 444 insertions(+), 47 deletions(-) diff --git a/ShipEngine/Models/Dto/Common/Customs.cs b/ShipEngine/Models/Dto/Common/Customs.cs index 98379f7a..188ba31d 100644 --- a/ShipEngine/Models/Dto/Common/Customs.cs +++ b/ShipEngine/Models/Dto/Common/Customs.cs @@ -24,7 +24,7 @@ public class Customs /// /// Customs declarations for each item in the shipment. /// - public List CustomsItems { get; set; } + public List? CustomsItems { get; set; } } /// diff --git a/ShipEngine/Models/Dto/CreateLabelFromRate/Result.cs b/ShipEngine/Models/Dto/CreateLabelFromRate/Result.cs index 33a59855..e757b43a 100644 --- a/ShipEngine/Models/Dto/CreateLabelFromRate/Result.cs +++ b/ShipEngine/Models/Dto/CreateLabelFromRate/Result.cs @@ -158,7 +158,7 @@ public class Result /// /// The label's package(s). /// - public List Packages { get; set; } + public List? Packages { get; set; } } /// diff --git a/ShipEngine/Models/Dto/CreateLabelFromShipmentDetails/Result.cs b/ShipEngine/Models/Dto/CreateLabelFromShipmentDetails/Result.cs index bc0fe88b..7ddb99e4 100644 --- a/ShipEngine/Models/Dto/CreateLabelFromShipmentDetails/Result.cs +++ b/ShipEngine/Models/Dto/CreateLabelFromShipmentDetails/Result.cs @@ -161,7 +161,7 @@ public class Result /// /// The label's package(s). /// - public List Packages { get; set; } + public List? Packages { get; set; } } /// diff --git a/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Params.cs b/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Params.cs index 95335909..63161b30 100644 --- a/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Params.cs +++ b/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Params.cs @@ -145,6 +145,9 @@ public class Shipment /// public Weight Weight { get; set; } + /// + /// The type of comparison rate + /// [JsonConverter(typeof(JsonStringEnumConverter))] public ComparisonRateType? ComparisonRateType { get; set; } diff --git a/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Result.cs b/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Result.cs index 115965be..e8b0ed53 100644 --- a/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Result.cs +++ b/ShipEngine/Models/Dto/GetRatesFromShipmentDetails/Result.cs @@ -4,6 +4,9 @@ namespace ShipEngineSDK.GetRatesWithShipmentDetails { + /// + /// The result of the GetRatesWithShipmentDetails operation + /// public class Result { /// @@ -29,12 +32,12 @@ public class Result /// /// Describe the packages included in this shipment as related to potential metadata that was imported from external order sources /// - public List Items { get; set; } + public List? Items { get; set; } /// /// Tax identifiers /// - public List TaxIdentifiers { get; set; } + public List? TaxIdentifiers { get; set; } /// /// You can optionally use this field to store your own identifier for this shipment. @@ -64,13 +67,13 @@ public class Result /// /// The recipient's mailing address /// - public Address ShipTo { get; set; } + public Address? ShipTo { get; set; } /// /// The shipment's origin address. If you frequently ship from the same location, consider creating a warehouse. /// Then you can simply specify the warehouse_id rather than the complete address each time. /// - public Address ShipFrom { get; set; } + public Address? ShipFrom { get; set; } /// /// The warehouse that the shipment is being shipped from. Either warehouse_id or ship_from must be specified. @@ -80,7 +83,7 @@ public class Result /// /// The return address for this shipment. Defaults to the ship_from address. /// - public Address ReturnTo { get; set; } + public Address? ReturnTo { get; set; } /// /// The type of delivery confirmation that is required for this shipment. @@ -90,12 +93,12 @@ public class Result /// /// Customs information. This is usually only needed for international shipments. /// - public Customs Customs { get; set; } + public Customs? Customs { get; set; } /// /// Advanced shipment options. These are entirely optional. /// - public AdvancedShipmentOptions AdvancedOptions { get; set; } + public AdvancedShipmentOptions? AdvancedOptions { get; set; } /// /// Indicates if the package will be picked up or dropped off by the carrier @@ -110,13 +113,13 @@ public class Result /// /// Arbitrary tags associated with this shipment. Tags can be used to categorize shipments, and shipments can be queried by their tags. /// - public List Tags { get; set; } + public List? Tags { get; set; } /// /// Total Weight of the Shipment /// - public Weight TotalWeight { get; set; } + public Weight? TotalWeight { get; set; } /// /// The order sources that are supported by ShipEngine @@ -126,30 +129,33 @@ public class Result /// /// The packages in the shipment. /// - public List Packages { get; set; } + public List? Packages { get; set; } /// /// The combined weight of all packages in the shipment /// - public Weight Weight { get; set; } + public Weight? Weight { get; set; } /// /// The rate responses /// - public RateResponse RateResponse { get; set; } + public RateResponse? RateResponse { get; set; } } + /// + /// The rate response object + /// public class RateResponse { /// /// A list of shipment rates /// - public List Rates { get; set; } + public List? Rates { get; set; } /// /// A list of invalid shipment rates /// - public List InvalidRates { get; set; } + public List? InvalidRates { get; set; } /// /// A string that uniquely identifies the rate request @@ -174,9 +180,12 @@ public class RateResponse /// /// Any errors associated with the rate request /// - public List Errors { get; set; } + public List? Errors { get; set; } } + /// + /// The rate object + /// public class Rate { /// @@ -197,33 +206,31 @@ public class Rate /// /// The shipping amount /// - public MonetaryValue ShippingAmount { get; set; } + public MonetaryValue? ShippingAmount { get; set; } /// /// The insurance amount /// - public MonetaryValue InsuranceAmount { get; set; } + public MonetaryValue? InsuranceAmount { get; set; } /// /// The sum of the carrier costs for the shipment /// - public MonetaryValue RequestedComparisonAmount { get; set; } - - - + public MonetaryValue? RequestedComparisonAmount { get; set; } + /// /// The confirmation amount /// - public MonetaryValue ConfirmationAmount { get; set; } + public MonetaryValue? ConfirmationAmount { get; set; } /// /// Any other charges associated with this rate /// - public MonetaryValue OtherAmount { get; set; } + public MonetaryValue? OtherAmount { get; set; } /// /// Tariff and additional taxes associated with an international shipment. /// - public MonetaryValue TaxAmount { get; set; } + public MonetaryValue? TaxAmount { get; set; } /// /// Certain carriers base their rates off of custom zones that vary depending upon @@ -306,11 +313,11 @@ public class Rate /// /// The warning messages /// - public List WarningMessages { get; set; } + public List? WarningMessages { get; set; } /// /// The error messages /// - public List ErrorMessages { get; set; } + public List? ErrorMessages { get; set; } } } \ No newline at end of file diff --git a/ShipEngine/Models/Dto/VoidLabelWithLabelId/Result.cs b/ShipEngine/Models/Dto/VoidLabelWithLabelId/Result.cs index f26b9d34..ebb90214 100644 --- a/ShipEngine/Models/Dto/VoidLabelWithLabelId/Result.cs +++ b/ShipEngine/Models/Dto/VoidLabelWithLabelId/Result.cs @@ -13,6 +13,6 @@ public class Result /// /// Message associated with the result of the void label attempt /// - public string Message { get; set; } + public string? Message { get; set; } } } \ No newline at end of file diff --git a/ShipEngine/Models/ShipEngineException.cs b/ShipEngine/Models/ShipEngineException.cs index 6d687e0a..b58bae78 100644 --- a/ShipEngine/Models/ShipEngineException.cs +++ b/ShipEngine/Models/ShipEngineException.cs @@ -1,6 +1,7 @@ #pragma warning disable 1591 using System; +using System.Net.Http; namespace ShipEngineSDK { @@ -32,18 +33,21 @@ public class ShipEngineException : Exception /// public ErrorCode ErrorCode { get; set; } + public HttpResponseMessage? Response { get; set; } public ShipEngineException( string message, ErrorSource errorSource = ErrorSource.Shipengine, ErrorType errorType = ErrorType.System, ErrorCode errorCode = ErrorCode.Unspecified, - string requestID = null) : base(message) + HttpResponseMessage? response = null, + string? requestId = null) : base(message) { ErrorSource = errorSource; ErrorType = errorType; ErrorCode = errorCode; - RequestId = requestID; + Response = response; + RequestId = requestId; } } } \ No newline at end of file diff --git a/ShipEngine/ShipEngine.cs b/ShipEngine/ShipEngine.cs index 4b9f6ad7..498162ae 100644 --- a/ShipEngine/ShipEngine.cs +++ b/ShipEngine/ShipEngine.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ShipEngineSDK.Common; +using ShipEngineSDK.Manifests; +using Result = ShipEngineSDK.ValidateAddresses.Result; using System; using System.Collections.Generic; using System.Net.Http; @@ -9,14 +11,23 @@ namespace ShipEngineSDK { + /// + /// Extension method to allow customized client configuration + /// public static class ShipEngineExtensions { + /// + /// Adds ShipEngine to the host builder and configures the client. + /// + /// + /// + /// public static IHostApplicationBuilder AddShipEngine(this IHostApplicationBuilder builder, Action? configureClient = null) { builder.Services.AddHttpClient(c => { var baseUri = builder.Configuration["ShipEngine:BaseUrl"] ?? "https://api.shipengine.com"; - var apiKey = builder.Configuration["ShipEngine:ApiKey"]; + var apiKey = builder.Configuration["ShipEngine:ApiKey"] ?? ""; ShipEngineClient.ConfigureHttpClient(c, apiKey, new Uri(baseUri)); configureClient?.Invoke(c); }); @@ -28,17 +39,19 @@ public static IHostApplicationBuilder AddShipEngine(this IHostApplicationBuilder /// /// Contains methods for interacting with the ShipEngine API. /// - public class ShipEngine : ShipEngineClient, IDisposable + public class ShipEngine : ShipEngineClient, IDisposable, IShipEngine { /// /// Global HttpClient for ShipEngine instance. /// + // ReSharper disable once InconsistentNaming public HttpClient _client; /// /// Global config for ShipEngine instance. /// - public Config _config; + // ReSharper disable once InconsistentNaming + private Config _config; /// /// Initialize the ShipEngine SDK with an API Key @@ -66,17 +79,28 @@ public ShipEngine(Config config) : base() /// HttpClient object to be used for ShipEngine API calls. We expect the httpClient has already been configured with ConfigureHttpClient public ShipEngine(HttpClient httpClient) : base() { + var apiKey = httpClient.DefaultRequestHeaders.GetValues("Api-Key"); + _config = new Config(apiKey.ToString()); _client = httpClient; } - // - // Dispose of the ShipEngine client - // + /// + /// Dispose of the ShipEngine client + /// public void Dispose() { _client.Dispose(); } + /// + /// Modify the ShipEngine request + /// + public new ShipEngine ModifyRequest(Action modifyRequest) + { + base.ModifyRequest = modifyRequest; + return this; + } + /// /// Validates an address in nearly any country in the world. /// @@ -398,4 +422,341 @@ public void Dispose() return labelResult; } } + + /// + /// Mock implementation of IShipEngine + /// + public class ShipEngineMock : IShipEngine + { + /// + /// Validates an address in nearly any country in the world. + /// + /// The address to validate. This can even be an incomplete or improperly formatted address + /// An address validation result object + public virtual Task> ValidateAddresses(List
addresses) + { + return Task.FromResult(new List()); + } + + /// + /// Validates an address in nearly any country in the world. + /// + /// The address to validate. This can even be an incomplete or improperly formatted address + /// Configuration object that overrides the global config for this method call. + /// An address validation result object + public virtual Task> ValidateAddresses(List
addresses, Config methodConfig) + { + return Task.FromResult(new List()); + } + + /// + /// Retrieve a list of all carriers that have been added to this account + /// + /// A list of carriers + public virtual Task ListCarriers() + { + return Task.FromResult(new ListCarriers.Result()); + } + + /// + /// Retrieve a list of all carriers that have been added to this account + /// + /// Configuration object that overrides the global config for this method call. + /// A list of carriers + public virtual Task ListCarriers(Config methodConfig) + { + return Task.FromResult(new ListCarriers.Result()); + } + + /// + /// Create a manifest + /// + /// The details of the manifest you want to create. + /// + public virtual Task CreateManifest(Params manifestParams) + { + return Task.FromResult(new Manifests.Result()); + } + + /// + /// Create a manifest + /// + /// Configuration object that overrides the global config for this method call. + /// The details of the manifest you want to create. + /// + public virtual Task CreateManifest(Config methodConfig, Params manifestParams) + { + return Task.FromResult(new Manifests.Result()); + } + + /// + /// Void a label by ID to get a refund. + /// + /// The id of the label to void + /// Result object indicating the success of the void label attempt + public virtual Task VoidLabelWithLabelId(string labelId) + { + return Task.FromResult(new VoidLabelWithLabelId.Result()); + } + + /// + /// Void a label by ID to get a refund. + /// + /// The id of the label to void + /// Configuration object that overrides the global config for this method call + /// Result object indicating the success of the void label attempt + public virtual Task VoidLabelWithLabelId(string labelId, Config methodConfig) + { + return Task.FromResult(new VoidLabelWithLabelId.Result()); + } + + /// + /// Track a shipment using the label id + /// + /// The label id associated with the shipment + /// An object that contains the label id tracking information + public virtual Task TrackUsingLabelId(string labelId) + { + return Task.FromResult(new TrackUsingLabelId.Result()); + } + + /// + /// Track a shipment using the label id + /// + /// The label id associated with the shipment + /// Configuration object that overrides the global config for this method call + /// An object that contains the label id tracking information + public virtual Task TrackUsingLabelId(string labelId, Config methodConfig) + { + return Task.FromResult(new TrackUsingLabelId.Result()); + } + + /// + /// Tracks a package based on the trackingNumber and carrierCode. + /// + /// The tracking number of the package you wish to track. + /// The carrierCode for the trackingNumber you are using to track the package. + /// + public virtual Task TrackUsingCarrierCodeAndTrackingNumber(string trackingNumber, string carrierCode) + { + return Task.FromResult(new TrackUsingCarrierCodeAndTrackingNumber.Result()); + } + + /// + /// Tracks a package based on the trackingNumber and carrierCode. + /// + /// The tracking number of the package you wish to track. + /// The carrierCode for the trackingNumber you are using to track the package. + /// Configuration object that overrides the global config for this method call + /// + public virtual Task TrackUsingCarrierCodeAndTrackingNumber(string trackingNumber, string carrierCode, Config methodConfig) + { + return Task.FromResult(new TrackUsingCarrierCodeAndTrackingNumber.Result()); + } + + /// + /// Create a label from shipment details + /// + /// Details of the label that you want to create + /// Object containing the created label information + public virtual Task CreateLabelFromShipmentDetails(CreateLabelFromShipmentDetails.Params labelParams) + { + return Task.FromResult(new CreateLabelFromShipmentDetails.Result()); + } + + /// + /// Create a label from shipment details + /// + /// Details of the label that you want to create + /// Configuration object that overrides the global config for this method call + /// Object containing the created label information + public virtual Task CreateLabelFromShipmentDetails(CreateLabelFromShipmentDetails.Params labelParams, Config methodConfig) + { + return Task.FromResult(new CreateLabelFromShipmentDetails.Result()); + } + + /// + /// Create a label from a rate id + /// + /// The details of the rate that you want to use to purchase a label + /// Object containing the created label information + public virtual Task CreateLabelFromRate(CreateLabelFromRate.Params createLabelFromRateParams) + { + return Task.FromResult(new CreateLabelFromRate.Result()); + } + + /// + /// Create a label from a rate id + /// + /// The details of the rate that you want to use to purchase a label + /// Configuration object that overrides the global config for this method call + /// Object containing the created label information + public virtual Task CreateLabelFromRate(CreateLabelFromRate.Params createLabelFromRateParams, Config methodConfig) + { + return Task.FromResult(new CreateLabelFromRate.Result()); + } + + /// + /// Retrieve rates for a package with the provided shipment details. + /// + /// + /// The rates result + public virtual Task GetRatesWithShipmentDetails(GetRatesWithShipmentDetails.Params rateParams) + { + return Task.FromResult(new GetRatesWithShipmentDetails.Result()); + } + + /// + /// Retrieve rates for a package with the provided shipment details. + /// + /// + /// Configuration object that overrides the global config for this method call + /// The rates result + public virtual Task GetRatesWithShipmentDetails(GetRatesWithShipmentDetails.Params rateParams, Config methodConfig) + { + return Task.FromResult(new GetRatesWithShipmentDetails.Result()); + } + } + + + /// + /// Interface for ShipEngine + /// + public interface IShipEngine + { + /// + /// Validates an address in nearly any country in the world. + /// + /// The address to validate. This can even be an incomplete or improperly formatted address + /// An address validation result object + Task> ValidateAddresses(List
addresses); + + /// + /// Validates an address in nearly any country in the world. + /// + /// The address to validate. This can even be an incomplete or improperly formatted address + /// Configuration object that overrides the global config for this method call + /// An address validation result object + Task> ValidateAddresses(List
addresses, Config methodConfig); + + /// + /// Retrieve a list of all carriers that have been added to this account + /// + /// A list of carriers + Task ListCarriers(); + + /// + /// Retrieve a list of all carriers that have been added to this account + /// + /// Configuration object that overrides the global config for this method call. + /// A list of carriers + Task ListCarriers(Config methodConfig); + + /// + /// Create a manifest + /// + /// The details of the manifest you want to create. + /// + Task CreateManifest(Manifests.Params manifestParams); + + /// + /// Create a manifest + /// + /// Configuration object that overrides the global config for this method call. + /// The details of the manifest you want to create. + /// + Task CreateManifest(Config methodConfig, Manifests.Params manifestParams); + + /// + /// Void a label by ID to get a refund. + /// + /// The id of the label to void + /// Result object indicating the success of the void label attempt + Task VoidLabelWithLabelId(string labelId); + + /// + /// Void a label by ID to get a refund. + /// + /// The id of the label to void + /// Configuration object that overrides the global config for this method call + /// Result object indicating the success of the void label attempt + Task VoidLabelWithLabelId(string labelId, Config methodConfig); + + /// + /// Track a shipment using the label id + /// + /// The label id associated with the shipment + /// An object that contains the label id tracking information + Task TrackUsingLabelId(string labelId); + + /// + /// Track a shipment using the label id + /// + /// The label id associated with the shipment + /// Configuration object that overrides the global config for this method call + /// An object that contains the label id tracking information + Task TrackUsingLabelId(string labelId, Config methodConfig); + + /// + /// Tracks a package based on the trackingNumber and carrierCode. + /// + /// The tracking number of the package you wish to track. + /// The carrierCode for the trackingNumber you are using to track the package. + /// + Task TrackUsingCarrierCodeAndTrackingNumber(string trackingNumber, string carrierCode); + + /// + /// Tracks a package based on the trackingNumber and carrierCode. + /// + /// The tracking number of the package you wish to track. + /// The carrierCode for the trackingNumber you are using to track the package. + /// Configuration object that overrides the global config for this method call + /// + Task TrackUsingCarrierCodeAndTrackingNumber(string trackingNumber, string carrierCode, Config methodConfig); + + /// + /// Create a label from shipment details + /// + /// Details of the label that you want to create + /// Object containing the created label information + Task CreateLabelFromShipmentDetails(CreateLabelFromShipmentDetails.Params labelParams); + + /// + /// Create a label from shipment details + /// + /// Details of the label that you want to create + /// Configuration object that overrides the global config for this method call + /// Object containing the created label information + Task CreateLabelFromShipmentDetails(CreateLabelFromShipmentDetails.Params labelParams, Config methodConfig); + + /// + /// Create a label from a rate id + /// + /// The details of the rate that you want to use to purchase a label + /// Object containing the created label information + Task CreateLabelFromRate(CreateLabelFromRate.Params createLabelFromRateParams); + + /// + /// Create a label from a rate id + /// + /// The details of the rate that you want to use to purchase a label + /// Configuration object that overrides the global config for this method call + /// Object containing the created label information + Task CreateLabelFromRate(CreateLabelFromRate.Params createLabelFromRateParams, Config methodConfig); + + /// + /// Retrieve rates for a package with the provided shipment details. + /// + /// + /// The rates result + Task GetRatesWithShipmentDetails(GetRatesWithShipmentDetails.Params rateParams); + + /// + /// Retrieve rates for a package with the provided shipment details. + /// + /// + /// Configuration object that overrides the global config for this method call + /// The rates result + Task GetRatesWithShipmentDetails(GetRatesWithShipmentDetails.Params rateParams, Config methodConfig); + } } \ No newline at end of file diff --git a/ShipEngine/ShipEngineClient.cs b/ShipEngine/ShipEngineClient.cs index 8c094dbb..980b38be 100644 --- a/ShipEngine/ShipEngineClient.cs +++ b/ShipEngine/ShipEngineClient.cs @@ -6,11 +6,11 @@ using System.Net.Http.Headers; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading; using System.Threading.Tasks; namespace ShipEngineSDK { - /// /// ShipEngine Client is used for handling generic calls and settings that /// are needed for all ShipEngine API calls. @@ -32,9 +32,19 @@ public class ShipEngineClient private const string JsonMediaType = "application/json"; + /// + /// Modifies the client request before it is sent + /// + public Action? ModifyRequest { get; set; } + + /// + /// Token to cancel the request + /// + public CancellationToken CancellationToken { get; set; } + /// /// Sets the HttpClient User agent, the json media type, and the API key to be used - /// for all ShipEngine API calls unless overrwritten at the method level. + /// for all ShipEngine API calls unless overwritten at the method level. /// /// Config object used to configure the HttpClient /// The HttpClient to be configured @@ -63,6 +73,15 @@ public static HttpClient ConfigureHttpClient(Config config, HttpClient client) return client; } + /// + /// Sets the HttpClient User agent, the json media type, and the API key to be used + /// for all ShipEngine API calls unless overwritten at the method level. + /// + /// + /// + /// + /// + /// public static HttpClient ConfigureHttpClient(HttpClient client, string apiKey, Uri? baseUri, TimeSpan? timeout = null) { client.DefaultRequestHeaders.Accept.Clear(); @@ -111,6 +130,7 @@ private async Task DeserializedResultOrThrow(HttpResponseMessage response) error.ErrorSource, error.ErrorType, error.ErrorCode, + response, deserializedError.RequestId ); } @@ -143,14 +163,15 @@ public virtual async Task SendHttpRequestAsync(HttpMethod method, string p { int retry = 0; - HttpResponseMessage response = null; + HttpResponseMessage? response = null; ShipEngineException requestException; while (true) { try { var request = BuildRequest(method, path, jsonContent); - var streamTask = client.SendAsync(request); + ModifyRequest?.Invoke(request); + var streamTask = client.SendAsync(request, CancellationToken); response = await streamTask; var deserializedResult = await DeserializedResultOrThrow(response); @@ -192,13 +213,13 @@ public virtual async Task SendHttpRequestAsync(HttpMethod method, string p } } - private async Task WaitAndRetry(HttpResponseMessage response, Config config, ShipEngineException ex) + private async Task WaitAndRetry(HttpResponseMessage? response, Config config, ShipEngineException ex) { int? retryAfter; try { - retryAfter = Int32.Parse(response?.Headers.GetValues("RetryAfter").First()); + retryAfter = Int32.Parse(response?.Headers.GetValues("RetryAfter").First() ?? string.Empty); } catch { @@ -212,14 +233,15 @@ private async Task WaitAndRetry(HttpResponseMessage response, Config config, Shi ErrorSource.Shipengine, ErrorType.System, ErrorCode.Timeout, + response, ex.RequestId ); } - await Task.Delay((int)retryAfter * 1000).ConfigureAwait(false); + await Task.Delay((int)retryAfter * 1000, CancellationToken).ConfigureAwait(false); } - private HttpRequestMessage BuildRequest(HttpMethod method, string path, string? jsonContent) + private static HttpRequestMessage BuildRequest(HttpMethod method, string path, string? jsonContent) { var request = new HttpRequestMessage(method, path); @@ -231,7 +253,7 @@ private HttpRequestMessage BuildRequest(HttpMethod method, string path, string? return request; } - private bool ShouldRetry( + private static bool ShouldRetry( int numRetries, HttpStatusCode? statusCode, HttpHeaders? headers,