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..818cebd7 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,16 +39,18 @@ 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.
///
+ // ReSharper disable once InconsistentNaming
public Config _config;
///
@@ -69,14 +82,23 @@ public ShipEngine(HttpClient httpClient) : base()
_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 +420,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,