Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update request modifiers so they can be scoped #109

Merged
merged 1 commit into from
Aug 26, 2024
Merged
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -168,3 +168,12 @@ Fixed handling of No Content responses
### Added

- Added basic ability to modify the HttpClient in a request

## 2.3.0

### Added

- Added ability to scope request modifiers by using the `.WithRequestModifier()` method instead of the `.ModifyRequest` property.
This will allow consumers to modify a single request without affecting any other consumers of the client. It also allows for
multiple modifiers to be added. For example, a modifier could be added at the global level that applies to all requests and then
another modifier can be added for a single request.
8 changes: 5 additions & 3 deletions ShipEngineSDK.Test/Helpers/MockShipEngineFixture.cs
Original file line number Diff line number Diff line change
@@ -75,11 +75,13 @@ public void AssertRequest(HttpMethod method, string path, int numberOfCalls = 1)
/// <param name="path">The HTTP path.</param>
/// <param name="status">The status code to return.</param>
/// <param name="response">The response body to return.</param>
public string StubRequest(HttpMethod method, string path, HttpStatusCode status, string response)
public string StubRequest(HttpMethod method, string path, HttpStatusCode status = HttpStatusCode.OK, string response = null)
{
var requestId = Guid.NewGuid().ToString();
var responseMessage = new HttpResponseMessage(status);
responseMessage.Content = new StringContent(response ?? "");
var responseMessage = new HttpResponseMessage(status)
{
Content = new StringContent(response ?? "")
};
responseMessage.Headers.Add("x-shipengine-requestid", requestId);
responseMessage.Headers.Add("request-id", requestId);

150 changes: 143 additions & 7 deletions ShipEngineSDK.Test/ShipEngineClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
namespace ShipEngineTest
{
using Moq;
using Moq.Protected;
using ShipEngineSDK;
using ShipEngineSDK.VoidLabelWithLabelId;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

@@ -173,7 +176,7 @@ public async Task SuccessResponseWithNullStringContentThrowsShipEngineExceptionW
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine;

// this scenario is similar to unparseable JSON - except that it is valid JSON
// this scenario is similar to unparsable JSON - except that it is valid JSON
var responseBody = @"null";
var requestId = mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.OK,
responseBody);
@@ -197,7 +200,7 @@ public async Task SuccessResponseWhenStringRequestedReturnsUnparsedString()
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine;

// this scenario is similar to unparseable JSON - except that it is valid JSON
// this scenario is similar to unparsable JSON - except that it is valid JSON
var responseBody = @"The Response";
mockShipEngineFixture.StubRequest(HttpMethod.Delete, "/v1/something", System.Net.HttpStatusCode.OK,
responseBody);
@@ -214,14 +217,147 @@ public async Task SuccessResponseWithNoContentCanBeReturnedIfStringRequested()
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine;

// this scenario is similar to unparseable JSON - except that it is valid JSON
string responseBody = null;
mockShipEngineFixture.StubRequest(HttpMethod.Delete, "/v1/something", System.Net.HttpStatusCode.OK,
responseBody);
// this scenario is similar to unparsable JSON - except that it is valid JSON
mockShipEngineFixture.StubRequest(HttpMethod.Delete, "/v1/something", System.Net.HttpStatusCode.OK, null);
var result = await shipengine.SendHttpRequestAsync<string>(HttpMethod.Delete, "/v1/something", "",
mockShipEngineFixture.HttpClient, config);

Assert.Null(responseBody);
Assert.Empty(result);
}

[Fact]
public void WithRequestModifierDoesNotCreateNewHttpClient()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var httpClient = mockShipEngineFixture.HttpClient;

var originalShipEngine = mockShipEngineFixture.ShipEngine;

var newShipEngine = originalShipEngine.WithRequestModifier(x => x.Headers.Add("X-Test-Header", "Test"));

Assert.Same(config, newShipEngine._config);
Assert.Same(httpClient, newShipEngine._client);
Assert.NotSame(originalShipEngine, newShipEngine);
}

[Fact]
public void ModifyRequestDoesNotCreateNewHttpClientNorShipEngineInstance()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var httpClient = mockShipEngineFixture.HttpClient;

var originalShipEngine = mockShipEngineFixture.ShipEngine;

var newShipEngine = originalShipEngine.ModifyRequest(x => x.Headers.Add("X-Test-Header", "Test"));

Assert.Same(config, newShipEngine._config);
Assert.Same(httpClient, newShipEngine._client);
Assert.Same(originalShipEngine, newShipEngine);
}

[Fact]
public async Task WithSingleRequestModifierAppliesBeforeRequest()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine.WithRequestModifier(x => x.Headers.Add("X-Test-Header", "Test"));
mockShipEngineFixture.StubRequest(HttpMethod.Get, "/foo");

await shipengine.SendHttpRequestAsync<string>(HttpMethod.Get, "/foo", "", mockShipEngineFixture.HttpClient, config);

mockShipEngineFixture.MockHandler.Protected()
.Verify(
"SendAsync",
Times.Once(),
ItExpr.Is<HttpRequestMessage>(m => m.Headers.Any(x => x.Key == "X-Test-Header")),
ItExpr.IsAny<CancellationToken>());
}

[Fact]
public async Task WithRequestModifierDoesNotAffectOriginalClient()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine;
var modifiedShipEngine = shipengine.WithRequestModifier(x => x.Headers.Add("X-Test-Header", "Test"));
mockShipEngineFixture.StubRequest(HttpMethod.Get, "/foo");

await shipengine.SendHttpRequestAsync<string>(HttpMethod.Get, "/foo", "", mockShipEngineFixture.HttpClient, config);

mockShipEngineFixture.MockHandler.Protected()
.Verify(
"SendAsync",
Times.Once(),
ItExpr.Is<HttpRequestMessage>(m => !m.Headers.Any(x => x.Key == "X-Test-Header")),
ItExpr.IsAny<CancellationToken>());
}

[Fact]
public async Task WithTwoRequestModifierAppliesBeforeRequest()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine
.WithRequestModifier(x =>
{
x.Headers.Add("X-Test-Header", "Test 1");
x.Headers.Add("X-Second-Header", "Test 2");
})
.WithRequestModifier(x => x.Headers.Remove("X-Test-Header"));
mockShipEngineFixture.StubRequest(HttpMethod.Get, "/foo");

await shipengine.SendHttpRequestAsync<string>(HttpMethod.Get, "/foo", "", mockShipEngineFixture.HttpClient, config);

mockShipEngineFixture.MockHandler.Protected()
.Verify(
"SendAsync",
Times.Once(),
ItExpr.Is<HttpRequestMessage>(m =>
!m.Headers.Any(x => x.Key == "X-Test-Header") &&
m.Headers.Any(x => x.Key == "X-Second-Header")),
ItExpr.IsAny<CancellationToken>());
}

[Fact]
public async Task ModifyRequestAppliesBeforeRequest()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine.ModifyRequest(x => x.Headers.Add("X-Test-Header", "Test"));
mockShipEngineFixture.StubRequest(HttpMethod.Get, "/foo");

await shipengine.SendHttpRequestAsync<string>(HttpMethod.Get, "/foo", "", mockShipEngineFixture.HttpClient, config);

mockShipEngineFixture.MockHandler.Protected()
.Verify(
"SendAsync",
Times.Once(),
ItExpr.Is<HttpRequestMessage>(m => m.Headers.Any(x => x.Key == "X-Test-Header")),
ItExpr.IsAny<CancellationToken>());
}

[Fact]
public async Task ModifyRequestReplacesExistingModifiersAppliesBeforeRequest()
{
var config = new Config(apiKey: "test", timeout: TimeSpan.FromSeconds(0.5));
var mockShipEngineFixture = new MockShipEngineFixture(config);
var shipengine = mockShipEngineFixture.ShipEngine
.WithRequestModifier(x => x.Headers.Add("X-Test-Header", "Test 1"))
.ModifyRequest(x => x.Headers.Add("X-Second-Header", "Test 2"));
mockShipEngineFixture.StubRequest(HttpMethod.Get, "/foo");

await shipengine.SendHttpRequestAsync<string>(HttpMethod.Get, "/foo", "", mockShipEngineFixture.HttpClient, config);

mockShipEngineFixture.MockHandler.Protected()
.Verify(
"SendAsync",
Times.Once(),
ItExpr.Is<HttpRequestMessage>(m =>
m.Headers.Any(x => x.Key == "X-Second-Header") &&
!m.Headers.Any(x => x.Key == "X-Test-Header")),
ItExpr.IsAny<CancellationToken>());
}
}
}
35 changes: 30 additions & 5 deletions ShipEngineSDK/ShipEngine.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ShipEngineSDK.Common;
using ShipEngineSDK.Manifests;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Tasks;
using Result = ShipEngineSDK.ValidateAddresses.Result;

[assembly: InternalsVisibleTo("ShipEngineSDK.Test")]

@@ -220,19 +219,45 @@
/// Initialize the ShipEngine SDK with an httpClient object
/// </summary>
/// <param name="httpClient">HttpClient object to be used for ShipEngine API calls. We expect the httpClient has already been configured with ConfigureHttpClient</param>
public ShipEngine(HttpClient httpClient) : base()

Check warning on line 222 in ShipEngineSDK/ShipEngine.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Non-nullable field '_config' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 222 in ShipEngineSDK/ShipEngine.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Non-nullable field '_config' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 222 in ShipEngineSDK/ShipEngine.cs

GitHub Actions / .Net 8.0 on windows-latest

Non-nullable field '_config' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 222 in ShipEngineSDK/ShipEngine.cs

GitHub Actions / .Net 8.0 on windows-latest

Non-nullable field '_config' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
{
_client = httpClient;
}

/// <summary>
/// Copy constructor that adds a request modifier to the existing collection
/// </summary>
/// <param name="client">Client to use for requests</param>
/// <param name="config">Config to use for the requests</param>
/// <param name="requestModifiers">List of request modifiers to use</param>
private ShipEngine(HttpClient client, Config config, IEnumerable<Action<HttpRequestMessage>> requestModifiers) :
base(requestModifiers)
{
_client = client;
_config = config;
}

/// <summary>
/// Gets a new instance of the ShipEngine client with the provided request modifier added to the collection
/// </summary>
/// <param name="modifier">Request modifier that will be added</param>
/// <returns>A new instance of the ShipEngine client</returns>
/// <remarks>The existing ShipEngine client is not modified</remarks>
public ShipEngine WithRequestModifier(Action<HttpRequestMessage> modifier) =>
new(_client, _config, requestModifiers.Append(modifier));

/// <summary>
/// Modifies the request before it is sent to the ShipEngine API
/// </summary>
/// <param name="modifyRequest"></param>
/// <returns></returns>
/// <param name="modifyRequest">Request modifier that will be used</param>
/// <returns>The current instance of the ShipEngine client</returns>
/// <remarks>
/// This method modifies the existing ShipEngine client and will replace any existing request modifiers with the one provided.
/// If you want to add a request modifier to the existing collection, use the WithRequestModifier method.
/// </remarks>
public ShipEngine ModifyRequest(Action<HttpRequestMessage> modifyRequest)
{
base.ModifyRequest = modifyRequest;
requestModifiers = [modifyRequest];
return this;
}

28 changes: 25 additions & 3 deletions ShipEngineSDK/ShipEngineClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ShipEngineSDK.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -19,6 +20,20 @@
/// </summary>
public class ShipEngineClient
{
/// <summary>
/// Default constructor
/// </summary>
public ShipEngineClient() { }

/// <summary>
/// Constructor that takes a collection of request modifiers to apply to the request before it is sent
/// </summary>
/// <param name="requestModifiers">Collection of modifiers to be used for each request</param>
protected ShipEngineClient(IEnumerable<Action<HttpRequestMessage>> requestModifiers)

Check warning on line 32 in ShipEngineSDK/ShipEngineClient.cs

GitHub Actions / .Net 8.0 on windows-latest

Check warning on line 32 in ShipEngineSDK/ShipEngineClient.cs

GitHub Actions / .Net 8.0 on windows-latest

{
this.requestModifiers = requestModifiers;
}

/// <summary>
/// Options for serializing the method call params to JSON.
/// A separate inline setting is used for deserializing the response
@@ -46,9 +61,13 @@
public CancellationToken CancellationToken { get; set; }

/// <summary>
/// Modifies the client request before it is sent
/// Collections of request modifiers to apply to the request before it is sent
/// </summary>
public Action<HttpRequestMessage>? ModifyRequest { get; set; }
/// <remarks>
/// This is a collection instead of a single action so that modifiers can be added at multiple levels.
/// For example, a consumer could add a modifier at the client level, and then add another at the method level.
/// </remarks>
protected IEnumerable<Action<HttpRequestMessage>> requestModifiers = [];

Check warning on line 70 in ShipEngineSDK/ShipEngineClient.cs

GitHub Actions / .Net 8.0 on windows-latest

Check warning on line 70 in ShipEngineSDK/ShipEngineClient.cs

GitHub Actions / .Net 8.0 on windows-latest


/// <summary>
/// Sets the HttpClient User agent, the json media type, and the API key to be used
@@ -209,7 +228,10 @@
try
{
var request = BuildRequest(method, path, jsonContent);
ModifyRequest?.Invoke(request);
foreach (var modifier in requestModifiers ?? [])
{
modifier?.Invoke(request);
}
response = await client.SendAsync(request, cancellationToken);

var deserializedResult = await DeserializedResultOrThrow<T>(response);
2 changes: 1 addition & 1 deletion ShipEngineSDK/ShipEngineSDK.csproj
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
<PackageId>ShipEngine</PackageId>
<PackageTags>sdk;rest;api;shipping;rates;label;tracking;cost;address;validation;normalization;fedex;ups;usps;</PackageTags>

<Version>2.2.1</Version>
<Version>2.3.0</Version>
<Authors>ShipEngine</Authors>
<Company>ShipEngine</Company>
<Summary>The official ShipEngine C# SDK for .NET</Summary>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shipengine-dotnet",
"version": "2.2.1",
"version": "2.3.0",
"description": "Package primarily used to generate the API and models from OpenApi spec\"",
"main": "index.js",
"directories": {

Unchanged files with check annotations Beta

{
return attr.Value;
}
return null;

Check warning on line 125 in ShipEngineSDK/ClientUtils.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Possible null reference return.

Check warning on line 125 in ShipEngineSDK/ClientUtils.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Possible null reference return.

Check warning on line 125 in ShipEngineSDK/ClientUtils.cs

GitHub Actions / .Net 8.0 on windows-latest

Possible null reference return.

Check warning on line 125 in ShipEngineSDK/ClientUtils.cs

GitHub Actions / .Net 8.0 on windows-latest

Possible null reference return.
}
}
/// A container for generalized request inputs. This type allows consumers to extend the request functionality
/// by abstracting away from the default (built-in) request framework (e.g. RestSharp).
/// </summary>
public class RequestOptions(string path)

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Non-nullable property 'Operation' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Non-nullable property 'Data' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Non-nullable property 'Operation' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Non-nullable property 'Data' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on windows-latest

Non-nullable property 'Operation' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on windows-latest

Non-nullable property 'Data' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on windows-latest

Non-nullable property 'Operation' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 23 in ShipEngineSDK/RequestOptions.cs

GitHub Actions / .Net 8.0 on windows-latest

Non-nullable property 'Data' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
string _path = path;
using System.Text.Json;
using System.Text.Json.Serialization;
public class DateJsonConverter : JsonConverter<DateTime>

Check warning on line 8 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter'

Check warning on line 8 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter'

Check warning on line 8 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on windows-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter'

Check warning on line 8 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on windows-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter'
{
private const string Format = "yyyy-MM-dd";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>

Check warning on line 12 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Read(ref Utf8JsonReader, Type, JsonSerializerOptions)'

Check warning on line 12 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Read(ref Utf8JsonReader, Type, JsonSerializerOptions)'

Check warning on line 12 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on windows-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Read(ref Utf8JsonReader, Type, JsonSerializerOptions)'

Check warning on line 12 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on windows-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Read(ref Utf8JsonReader, Type, JsonSerializerOptions)'
DateTime.ParseExact(reader.GetString()!, Format, CultureInfo.InvariantCulture);
public override void Write(Utf8JsonWriter writer, DateTime dateTimeValue, JsonSerializerOptions options) =>

Check warning on line 15 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Write(Utf8JsonWriter, DateTime, JsonSerializerOptions)'

Check warning on line 15 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Write(Utf8JsonWriter, DateTime, JsonSerializerOptions)'

Check warning on line 15 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on windows-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Write(Utf8JsonWriter, DateTime, JsonSerializerOptions)'

Check warning on line 15 in ShipEngineSDK/DateJsonConverter.cs

GitHub Actions / .Net 8.0 on windows-latest

Missing XML comment for publicly visible type or member 'DateJsonConverter.Write(Utf8JsonWriter, DateTime, JsonSerializerOptions)'
writer.WriteStringValue(dateTimeValue.ToString(Format, CultureInfo.InvariantCulture));
}
/// A UUID that uniquely identifies the request id.
/// This can be given to the support team to help debug non-trivial issues that may occur
/// </summary>
public string RequestId { get; set; }

Check warning on line 19 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Check warning on line 19 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

/// <summary>
/// The errors associated with the failed API call
/// <summary>
/// An error message associated with the failed API call
/// </summary>
public string Message { get; set; }

Check warning on line 55 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Check warning on line 55 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Check warning on line 55 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Check warning on line 55 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on ubuntu-latest

Check warning on line 55 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on windows-latest

Check warning on line 55 in ShipEngineSDK/Models/Dto/Common/ShipEngineAPIError.cs

GitHub Actions / .Net 8.0 on windows-latest

}
}