Skip to content

Commit

Permalink
Populate request ID on ShipEngineException
Browse files Browse the repository at this point in the history
Capture the request ID from the response headers, in case the body does not include it.
  • Loading branch information
joshuaflanagan committed Jun 28, 2024
1 parent b4aec9a commit 5997aaf
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
7 changes: 6 additions & 1 deletion ShipEngine.Tests/Helpers/MockShipEngineFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace ShipEngineTest
using Moq;
using Moq.Protected;
using ShipEngineSDK;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
Expand Down Expand Up @@ -74,10 +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 void StubRequest(HttpMethod method, string path, HttpStatusCode status, string response)
public string StubRequest(HttpMethod method, string path, HttpStatusCode status, string response)
{
var requestId = Guid.NewGuid().ToString();
var responseMessage = new HttpResponseMessage(status);
responseMessage.Content = new StringContent(response);
responseMessage.Headers.Add("x-shipengine-requestid", requestId);
responseMessage.Headers.Add("request-id", requestId);

MockHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
Expand All @@ -87,6 +91,7 @@ public void StubRequest(HttpMethod method, string path, HttpStatusCode status, s
m.RequestUri.AbsolutePath == path),
ItExpr.IsAny<CancellationToken>())
.Returns(Task.FromResult(responseMessage));
return requestId;
}
}
}
12 changes: 8 additions & 4 deletions ShipEngine.Tests/ShipEngineClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public async Task FailureStatusWithoutShipEngineDetailsThrowsShipEngineException
var shipengine = mockShipEngineFixture.ShipEngine;

var responseBody = @"{""description"": ""valid JSON, but not what you expect""}";
mockShipEngineFixture.StubRequest(HttpMethod.Get, "/v1/something", System.Net.HttpStatusCode.NotFound,
var requestId = mockShipEngineFixture.StubRequest(HttpMethod.Get, "/v1/something", System.Net.HttpStatusCode.NotFound,
responseBody);
var ex = await Assert.ThrowsAsync<ShipEngineException>(
async () => await shipengine.SendHttpRequestAsync<Result>(HttpMethod.Get, "/v1/something", null,
Expand All @@ -67,6 +67,7 @@ public async Task FailureStatusWithoutShipEngineDetailsThrowsShipEngineException

Assert.NotNull(ex.ResponseMessage);
Assert.Equal(404, (int) ex.ResponseMessage.StatusCode);
Assert.Equal(requestId, ex.RequestId);
}

[Fact]
Expand All @@ -77,7 +78,7 @@ public async Task FailureStatusWithoutJsonContentThrowsShipEngineExceptionWithOr
var shipengine = mockShipEngineFixture.ShipEngine;

var responseBody = @"<h1>Bad Gateway</h1>";
mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.BadGateway,
var requestId = mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.BadGateway,
responseBody);
var ex = await Assert.ThrowsAsync<ShipEngineException>(
async () => await shipengine.SendHttpRequestAsync<Result>(HttpMethod.Post, "/v1/something", "",
Expand All @@ -86,6 +87,7 @@ public async Task FailureStatusWithoutJsonContentThrowsShipEngineExceptionWithOr

Assert.NotNull(ex.ResponseMessage);
Assert.Equal(502, (int) ex.ResponseMessage.StatusCode);
Assert.Equal(requestId, ex.RequestId);
}

[Fact]
Expand All @@ -96,7 +98,7 @@ public async Task SuccessResponseThatCannotBeParsedThrowsExceptionWithUnparsedRe
var shipengine = mockShipEngineFixture.ShipEngine;

var responseBody = @"Unexpected response - not JSON";
mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.OK,
var requestId = mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.OK,
responseBody);
var ex = await Assert.ThrowsAsync<ShipEngineException>(
async () => await shipengine.SendHttpRequestAsync<Result>(HttpMethod.Post, "/v1/something", "",
Expand All @@ -107,6 +109,7 @@ public async Task SuccessResponseThatCannotBeParsedThrowsExceptionWithUnparsedRe
Assert.NotNull(ex.ResponseMessage);
Assert.Equal(200, (int)ex.ResponseMessage.StatusCode);
Assert.Equal(responseBody, await ex.ResponseMessage.Content.ReadAsStringAsync());
Assert.Equal(requestId, ex.RequestId);
}

[Fact]
Expand All @@ -118,7 +121,7 @@ public async Task SuccessResponseWithNullContentThrowsShipEngineExceptionWithUnp

// this scenario is similar to unparseable JSON - except that it is valid JSON
var responseBody = @"null";
mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.OK,
var requestId = mockShipEngineFixture.StubRequest(HttpMethod.Post, "/v1/something", System.Net.HttpStatusCode.OK,
responseBody);
var ex = await Assert.ThrowsAsync<ShipEngineException>(
async () => await shipengine.SendHttpRequestAsync<Result>(HttpMethod.Post, "/v1/something", "",
Expand All @@ -130,6 +133,7 @@ public async Task SuccessResponseWithNullContentThrowsShipEngineExceptionWithUnp
Assert.Equal("Unexpected null response", ex.Message);
Assert.Equal(200, (int)ex.ResponseMessage.StatusCode);
Assert.Equal(responseBody, await ex.ResponseMessage.Content.ReadAsStringAsync());
Assert.Equal(requestId, ex.RequestId);
}
}
}
14 changes: 10 additions & 4 deletions ShipEngine/ShipEngineClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ public static HttpClient ConfigureHttpClient(HttpClient client, string apiKey, U
private async Task<T> DeserializedResultOrThrow<T>(HttpResponseMessage response)
{
var contentString = await response.Content.ReadAsStringAsync();
string? requestId = null;
if (response.Headers.TryGetValues("x-shipengine-requestid", out var requestIdValues))
{
requestId = requestIdValues.FirstOrDefault();
}


if (!response.IsSuccessStatusCode)
{
Expand All @@ -104,7 +110,7 @@ private async Task<T> DeserializedResultOrThrow<T>(HttpResponseMessage response)
if (deserializedError == null)
{
// in this case, the response body was not parseable JSON
throw new ShipEngineException("Unexpected HTTP status", responseMessage: response);
throw new ShipEngineException("Unexpected HTTP status", requestID: requestId, responseMessage: response);
}

var error = deserializedError.Errors?.FirstOrDefault(e => e.Message != null);
Expand All @@ -114,7 +120,7 @@ private async Task<T> DeserializedResultOrThrow<T>(HttpResponseMessage response)
error?.ErrorSource ?? ErrorSource.Shipengine,
error?.ErrorType ?? ErrorType.System,
error?.ErrorCode ?? ErrorCode.Unspecified,
deserializedError.RequestId,
deserializedError.RequestId ?? requestId,
response
);
}
Expand All @@ -126,7 +132,7 @@ private async Task<T> DeserializedResultOrThrow<T>(HttpResponseMessage response)
}
catch (JsonException)
{
throw new ShipEngineException("Unable to parse response", responseMessage: response);
throw new ShipEngineException("Unable to parse response", requestID: requestId, responseMessage: response);
}


Expand All @@ -135,7 +141,7 @@ private async Task<T> DeserializedResultOrThrow<T>(HttpResponseMessage response)
return result;
}

throw new ShipEngineException(message: "Unexpected null response", responseMessage: response);
throw new ShipEngineException(message: "Unexpected null response", requestID: requestId, responseMessage: response);
}


Expand Down

0 comments on commit 5997aaf

Please sign in to comment.