diff --git a/ShipEngine.Tests/Helpers/MockShipEngineFixture.cs b/ShipEngine.Tests/Helpers/MockShipEngineFixture.cs index bfd91425..d5fca799 100644 --- a/ShipEngine.Tests/Helpers/MockShipEngineFixture.cs +++ b/ShipEngine.Tests/Helpers/MockShipEngineFixture.cs @@ -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; @@ -74,10 +75,13 @@ public void AssertRequest(HttpMethod method, string path, int numberOfCalls = 1) /// The HTTP path. /// The status code to return. /// The response body to return. - 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>( @@ -87,6 +91,7 @@ public void StubRequest(HttpMethod method, string path, HttpStatusCode status, s m.RequestUri.AbsolutePath == path), ItExpr.IsAny()) .Returns(Task.FromResult(responseMessage)); + return requestId; } } } \ No newline at end of file diff --git a/ShipEngine.Tests/ShipEngineClientTests.cs b/ShipEngine.Tests/ShipEngineClientTests.cs index dc78305d..844b0e93 100644 --- a/ShipEngine.Tests/ShipEngineClientTests.cs +++ b/ShipEngine.Tests/ShipEngineClientTests.cs @@ -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( async () => await shipengine.SendHttpRequestAsync(HttpMethod.Get, "/v1/something", null, @@ -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] @@ -77,7 +78,7 @@ public async Task FailureStatusWithoutJsonContentThrowsShipEngineExceptionWithOr var shipengine = mockShipEngineFixture.ShipEngine; var responseBody = @"

Bad Gateway

"; - 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( async () => await shipengine.SendHttpRequestAsync(HttpMethod.Post, "/v1/something", "", @@ -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] @@ -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( async () => await shipengine.SendHttpRequestAsync(HttpMethod.Post, "/v1/something", "", @@ -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] @@ -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( async () => await shipengine.SendHttpRequestAsync(HttpMethod.Post, "/v1/something", "", @@ -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); } } } \ No newline at end of file diff --git a/ShipEngine/ShipEngineClient.cs b/ShipEngine/ShipEngineClient.cs index 99559774..9a7ee8b9 100644 --- a/ShipEngine/ShipEngineClient.cs +++ b/ShipEngine/ShipEngineClient.cs @@ -88,6 +88,12 @@ public static HttpClient ConfigureHttpClient(HttpClient client, string apiKey, U private async Task DeserializedResultOrThrow(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) { @@ -104,7 +110,7 @@ private async Task DeserializedResultOrThrow(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); @@ -114,7 +120,7 @@ private async Task DeserializedResultOrThrow(HttpResponseMessage response) error?.ErrorSource ?? ErrorSource.Shipengine, error?.ErrorType ?? ErrorType.System, error?.ErrorCode ?? ErrorCode.Unspecified, - deserializedError.RequestId, + deserializedError.RequestId ?? requestId, response ); } @@ -126,7 +132,7 @@ private async Task DeserializedResultOrThrow(HttpResponseMessage response) } catch (JsonException) { - throw new ShipEngineException("Unable to parse response", responseMessage: response); + throw new ShipEngineException("Unable to parse response", requestID: requestId, responseMessage: response); } @@ -135,7 +141,7 @@ private async Task DeserializedResultOrThrow(HttpResponseMessage response) return result; } - throw new ShipEngineException(message: "Unexpected null response", responseMessage: response); + throw new ShipEngineException(message: "Unexpected null response", requestID: requestId, responseMessage: response); }