From 24b183177790f9d3e0b2b82521c785d57168fbe9 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Tue, 29 Nov 2022 12:58:31 +0100 Subject: [PATCH 1/8] Use Error property when throwing exception #1376 --- Refit/ApiResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refit/ApiResponse.cs b/Refit/ApiResponse.cs index daa6e803b..6e8496c08 100644 --- a/Refit/ApiResponse.cs +++ b/Refit/ApiResponse.cs @@ -86,7 +86,7 @@ public async Task> EnsureSuccessStatusCodeAsync() { if (!IsSuccessStatusCode) { - var exception = await ApiException.Create(response.RequestMessage!, response.RequestMessage!.Method, response, Settings).ConfigureAwait(false); + var exception = Error ?? await ApiException.Create(response.RequestMessage!, response.RequestMessage!.Method, response, Settings).ConfigureAwait(false); Dispose(); From 061a8f3dca49fb8a0219d94de5d217a8d185e948 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Mon, 12 Dec 2022 11:48:20 +0100 Subject: [PATCH 2/8] Add Unit test --- Refit.Tests/ResponseTests.cs | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Refit.Tests/ResponseTests.cs b/Refit.Tests/ResponseTests.cs index 85ea11c82..989df1c2d 100644 --- a/Refit.Tests/ResponseTests.cs +++ b/Refit.Tests/ResponseTests.cs @@ -116,6 +116,43 @@ public async Task ThrowsValidationException() Assert.Equal("title", actualException.Content.Title); Assert.Equal("type", actualException.Content.Type); } + + /// + /// Test to verify if EnsureSuccessStatusCodeAsync throws a ValidationApiException for a Bad Request in terms of RFC 7807 + /// + [Fact] + public async Task When_BadRequest_EnsureSuccessStatusCodeAsync_ThrowsValidationException() + { + var expectedContent = new ProblemDetails + { + Detail = "detail", + Errors = { { "Field1", new string[] { "Problem1" } }, { "Field2", new string[] { "Problem2" } } }, + Instance = "instance", + Status = 1, + Title = "title", + Type = "type" + }; + + var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent(JsonConvert.SerializeObject(expectedContent)) + }; + + expectedResponse.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/problem+json"); + mockHandler.Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") + .Respond(req => expectedResponse); + + try + { + using var response = await fixture.GetApiResponseTestObject(); + await response.EnsureSuccessStatusCodeAsync(); + } + catch (Exception ex) + { + Assert.True(ex is ValidationApiException); + Assert.NotNull((ex as ValidationApiException)?.Content); + } + } [Fact] public async Task WhenProblemDetailsResponseContainsExtensions_ShouldHydrateExtensions() From 4ac5dc09d87c34efbadaf4cb7a74d4f640398ee2 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Mon, 12 Dec 2022 12:23:15 +0100 Subject: [PATCH 3/8] Update test assertions --- Refit.Tests/ResponseTests.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Refit.Tests/ResponseTests.cs b/Refit.Tests/ResponseTests.cs index 989df1c2d..73798fe54 100644 --- a/Refit.Tests/ResponseTests.cs +++ b/Refit.Tests/ResponseTests.cs @@ -142,16 +142,17 @@ public async Task When_BadRequest_EnsureSuccessStatusCodeAsync_ThrowsValidationE mockHandler.Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") .Respond(req => expectedResponse); - try - { - using var response = await fixture.GetApiResponseTestObject(); - await response.EnsureSuccessStatusCodeAsync(); - } - catch (Exception ex) - { - Assert.True(ex is ValidationApiException); - Assert.NotNull((ex as ValidationApiException)?.Content); - } + using var response = await fixture.GetApiResponseTestObject(); + var actualException = await Assert.ThrowsAsync(() => response.EnsureSuccessStatusCodeAsync()); + + Assert.NotNull(actualException.Content); + Assert.Equal("detail", actualException.Content.Detail); + Assert.Equal("Problem1", actualException.Content.Errors["Field1"][0]); + Assert.Equal("Problem2", actualException.Content.Errors["Field2"][0]); + Assert.Equal("instance", actualException.Content.Instance); + Assert.Equal(1, actualException.Content.Status); + Assert.Equal("title", actualException.Content.Title); + Assert.Equal("type", actualException.Content.Type); } [Fact] From 39099c665197f9d0f8898e2255c057057e87dc03 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Thu, 24 Oct 2024 11:13:18 +0200 Subject: [PATCH 4/8] Add IsSuccess property and EnsureSuccessAsync method Introduce IsSuccess property to ApiResponse and related interfaces to indicate request success. Add EnsureSuccessAsync method to handle errors, including deserialization issues. Refactor EnsureSuccessStatusCodeAsync to use ThrowsApiExceptionAsync. Update Dispose method for exception handling. Add unit tests to verify new behavior. --- ...ApprovalTests.Refit.DotNet6_0.verified.txt | 16 ++++ ...ApprovalTests.Refit.DotNet8_0.verified.txt | 16 ++++ Refit.Tests/ResponseTests.cs | 45 ++++++++++++ Refit/ApiResponse.cs | 73 +++++++++++++++---- 4 files changed, 136 insertions(+), 14 deletions(-) diff --git a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt index 34fd210ce..e4378ab8a 100644 --- a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt +++ b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt @@ -45,6 +45,11 @@ namespace Refit [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + public bool IsSuccess { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } @@ -52,6 +57,7 @@ namespace Refit public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } + public System.Threading.Tasks.Task> EnsureSuccessAsync() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] @@ -189,6 +195,11 @@ namespace Refit [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + bool IsSuccess { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } @@ -206,6 +217,11 @@ namespace Refit [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + new bool IsSuccess { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } } public interface IFormUrlEncodedParameterFormatter diff --git a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt index 182ebd24d..c8c3fa5cc 100644 --- a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt +++ b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt @@ -45,6 +45,11 @@ namespace Refit [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + public bool IsSuccess { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] public bool IsSuccessStatusCode { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } @@ -52,6 +57,7 @@ namespace Refit public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } + public System.Threading.Tasks.Task> EnsureSuccessAsync() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] @@ -189,6 +195,11 @@ namespace Refit [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + bool IsSuccess { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] bool IsSuccessStatusCode { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } @@ -206,6 +217,11 @@ namespace Refit [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + new bool IsSuccess { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] new bool IsSuccessStatusCode { get; } } public interface IFormUrlEncodedParameterFormatter diff --git a/Refit.Tests/ResponseTests.cs b/Refit.Tests/ResponseTests.cs index 0b7149f2a..507081c92 100644 --- a/Refit.Tests/ResponseTests.cs +++ b/Refit.Tests/ResponseTests.cs @@ -174,6 +174,51 @@ public async Task When_BadRequest_EnsureSuccessStatusCodeAsync_ThrowsValidationE Assert.Equal("type", actualException.Content.Type); } + /// + /// Test to verify if IsSuccess returns false if we have a success status code, but there is a deserialization exception + /// + [Fact] + public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccess_ShouldReturnFalse() + { + var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("Invalid JSON") + }; + + mockHandler + .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") + .Respond(req => expectedResponse); + + using var response = await fixture.GetApiResponseTestObject(); + + Assert.True(response.IsSuccessStatusCode); + Assert.False(response.IsSuccess); + } + + /// + /// Test to verify if EnsureSuccessAsync throws an ApiException if we have a success status code, but there is a deserialization exception + /// + [Fact] + public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessAsync_ThrowsApiException() + { + var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("Invalid JSON") + }; + + mockHandler + .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") + .Respond(req => expectedResponse); + + using var response = await fixture.GetApiResponseTestObject(); + var actualException = await Assert.ThrowsAsync( + () => response.EnsureSuccessAsync() + ); + + Assert.NotNull(actualException); + Assert.IsType(actualException.InnerException); + } + [Fact] public async Task WhenProblemDetailsResponseContainsExtensions_ShouldHydrateExtensions() { diff --git a/Refit/ApiResponse.cs b/Refit/ApiResponse.cs index 2867b8147..1ce9d26ce 100644 --- a/Refit/ApiResponse.cs +++ b/Refit/ApiResponse.cs @@ -71,12 +71,21 @@ public sealed class ApiResponse( /// Indicates whether the request was successful. /// #if NET6_0_OR_GREATER - [MemberNotNullWhen(true, nameof(Content))] [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif public bool IsSuccessStatusCode => response.IsSuccessStatusCode; + /// + /// Indicates whether the request was successful and there wasn't any other error (for example, during deserialization). + /// +#if NET6_0_OR_GREATER + [MemberNotNullWhen(true, nameof(Content))] + [MemberNotNullWhen(true, nameof(ContentHeaders))] + [MemberNotNullWhen(false, nameof(Error))] +#endif + public bool IsSuccess => IsSuccessStatusCode && Error is null; + /// /// The reason phrase which typically is sent by the server together with the status code. /// @@ -119,20 +128,22 @@ public async Task> EnsureSuccessStatusCodeAsync() { if (!IsSuccessStatusCode) { - var exception = - Error - ?? await ApiException - .Create( - response.RequestMessage!, - response.RequestMessage!.Method, - response, - Settings - ) - .ConfigureAwait(false); + await ThrowsApiExceptionAsync().ConfigureAwait(false); + } - Dispose(); + return this; + } - throw exception; + /// + /// Ensures the request was successful and without any other error by throwing an exception in case of failure + /// + /// The current + /// + public async Task> EnsureSuccessAsync() + { + if (!IsSuccess) + { + await ThrowsApiExceptionAsync().ConfigureAwait(false); } return this; @@ -147,6 +158,24 @@ void Dispose(bool disposing) response.Dispose(); } + + private async Task ThrowsApiExceptionAsync() + { + var exception = + Error + ?? await ApiException + .Create( + response.RequestMessage!, + response.RequestMessage!.Method, + response, + Settings + ) + .ConfigureAwait(false); + + Dispose(); + + throw exception; + } } /// @@ -171,10 +200,17 @@ public interface IApiResponse : IApiResponse /// /// Indicates whether the request was successful. /// - [MemberNotNullWhen(true, nameof(Content))] [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] new bool IsSuccessStatusCode { get; } + + /// + /// Indicates whether the request was successful and there wasn't any other error (for example, during deserialization). + /// + [MemberNotNullWhen(true, nameof(Content))] + [MemberNotNullWhen(true, nameof(ContentHeaders))] + [MemberNotNullWhen(false, nameof(Error))] + new bool IsSuccess { get; } #endif /// @@ -207,6 +243,15 @@ public interface IApiResponse : IDisposable #endif bool IsSuccessStatusCode { get; } + /// + /// Indicates whether the request was successful and there wasn't any other error (for example, during deserialization). + /// +#if NET6_0_OR_GREATER + [MemberNotNullWhen(true, nameof(ContentHeaders))] + [MemberNotNullWhen(false, nameof(Error))] +#endif + bool IsSuccess { get; } + /// /// HTTP response status code. /// From 9d47bbed6205c31adc1b14a4a332adee1921a0b0 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Thu, 24 Oct 2024 15:52:09 +0200 Subject: [PATCH 5/8] Refactor API response success properties and methods Renamed properties and methods related to the success status of an API response in the `Refit` library: - `IsSuccess` to `IsSuccessful` - `EnsureSuccessAsync` to `EnsureSuccessfulAsync` --- ...ApprovalTests.Refit.DotNet6_0.verified.txt | 22 +++++++++---------- ...ApprovalTests.Refit.DotNet8_0.verified.txt | 22 +++++++++---------- Refit.Tests/ResponseTests.cs | 8 +++---- Refit/ApiResponse.cs | 10 ++++----- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt index e4378ab8a..aca0a3307 100644 --- a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt +++ b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet6_0.verified.txt @@ -40,25 +40,25 @@ namespace Refit public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - public bool IsSuccess { get; } + public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - public bool IsSuccessStatusCode { get; } + public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } - public System.Threading.Tasks.Task> EnsureSuccessAsync() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } + public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + @@ -195,12 +195,12 @@ namespace Refit [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - bool IsSuccess { get; } + bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - bool IsSuccessStatusCode { get; } + bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } @@ -212,17 +212,17 @@ namespace Refit new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - new bool IsSuccess { get; } + new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - new bool IsSuccessStatusCode { get; } + new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { diff --git a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt index c8c3fa5cc..7706d4756 100644 --- a/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt +++ b/Refit.Tests/API/ApiApprovalTests.Refit.DotNet8_0.verified.txt @@ -40,25 +40,25 @@ namespace Refit public Refit.ApiException? Error { get; } public System.Net.Http.Headers.HttpResponseHeaders Headers { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - public bool IsSuccess { get; } + public bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - public bool IsSuccessStatusCode { get; } + public bool IsSuccessful { get; } public string? ReasonPhrase { get; } public System.Net.Http.HttpRequestMessage? RequestMessage { get; } public Refit.RefitSettings Settings { get; } public System.Net.HttpStatusCode StatusCode { get; } public System.Version Version { get; } public void Dispose() { } - public System.Threading.Tasks.Task> EnsureSuccessAsync() { } public System.Threading.Tasks.Task> EnsureSuccessStatusCodeAsync() { } + public System.Threading.Tasks.Task> EnsureSuccessfulAsync() { } } [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Parameter)] [System.Obsolete("Use Refit.StreamPart, Refit.ByteArrayPart, Refit.FileInfoPart or if necessary, in" + @@ -195,12 +195,12 @@ namespace Refit [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - bool IsSuccess { get; } + bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - bool IsSuccessStatusCode { get; } + bool IsSuccessful { get; } string? ReasonPhrase { get; } System.Net.Http.HttpRequestMessage? RequestMessage { get; } System.Net.HttpStatusCode StatusCode { get; } @@ -212,17 +212,17 @@ namespace Refit new System.Net.Http.Headers.HttpContentHeaders? ContentHeaders { get; } new Refit.ApiException? Error { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] - [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - new bool IsSuccess { get; } + new bool IsSuccessStatusCode { get; } [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "Error")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "Content")] [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ContentHeaders")] - new bool IsSuccessStatusCode { get; } + new bool IsSuccessful { get; } } public interface IFormUrlEncodedParameterFormatter { diff --git a/Refit.Tests/ResponseTests.cs b/Refit.Tests/ResponseTests.cs index 507081c92..8ba3583bd 100644 --- a/Refit.Tests/ResponseTests.cs +++ b/Refit.Tests/ResponseTests.cs @@ -178,7 +178,7 @@ public async Task When_BadRequest_EnsureSuccessStatusCodeAsync_ThrowsValidationE /// Test to verify if IsSuccess returns false if we have a success status code, but there is a deserialization exception /// [Fact] - public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccess_ShouldReturnFalse() + public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccessful_ShouldReturnFalse() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { @@ -192,14 +192,14 @@ public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccess_ShouldRet using var response = await fixture.GetApiResponseTestObject(); Assert.True(response.IsSuccessStatusCode); - Assert.False(response.IsSuccess); + Assert.False(response.IsSuccessful); } /// /// Test to verify if EnsureSuccessAsync throws an ApiException if we have a success status code, but there is a deserialization exception /// [Fact] - public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessAsync_ThrowsApiException() + public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessfulAsync_ThrowsApiException() { var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) { @@ -212,7 +212,7 @@ public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessAsync_ using var response = await fixture.GetApiResponseTestObject(); var actualException = await Assert.ThrowsAsync( - () => response.EnsureSuccessAsync() + () => response.EnsureSuccessfulAsync() ); Assert.NotNull(actualException); diff --git a/Refit/ApiResponse.cs b/Refit/ApiResponse.cs index 1ce9d26ce..8c0adbfbe 100644 --- a/Refit/ApiResponse.cs +++ b/Refit/ApiResponse.cs @@ -84,7 +84,7 @@ public sealed class ApiResponse( [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif - public bool IsSuccess => IsSuccessStatusCode && Error is null; + public bool IsSuccessful => IsSuccessStatusCode && Error is null; /// /// The reason phrase which typically is sent by the server together with the status code. @@ -139,9 +139,9 @@ public async Task> EnsureSuccessStatusCodeAsync() /// /// The current /// - public async Task> EnsureSuccessAsync() + public async Task> EnsureSuccessfulAsync() { - if (!IsSuccess) + if (!IsSuccessful) { await ThrowsApiExceptionAsync().ConfigureAwait(false); } @@ -210,7 +210,7 @@ public interface IApiResponse : IApiResponse [MemberNotNullWhen(true, nameof(Content))] [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] - new bool IsSuccess { get; } + new bool IsSuccessful { get; } #endif /// @@ -250,7 +250,7 @@ public interface IApiResponse : IDisposable [MemberNotNullWhen(true, nameof(ContentHeaders))] [MemberNotNullWhen(false, nameof(Error))] #endif - bool IsSuccess { get; } + bool IsSuccessful { get; } /// /// HTTP response status code. From 79f12206bd850800dc1e34034c17d949f873943c Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Thu, 24 Oct 2024 16:03:44 +0200 Subject: [PATCH 6/8] Update IsSuccessful usage and documentation --- README.md | 10 +++++++--- Refit/ApiResponse.cs | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cfa68ecff..dd5a36be0 100644 --- a/README.md +++ b/README.md @@ -1099,8 +1099,9 @@ var response = await gitHubApi.GetUser("octocat"); //Getting the status code (returns a value from the System.Net.HttpStatusCode enumeration) var httpStatus = response.StatusCode; -//Determining if a success status code was received -if(response.IsSuccessStatusCode) +//Determining if a success status code was received and there wasn't any other error +//(for example, during content deserialization) +if(response.IsSuccessfull) { //YAY! Do the thing... } @@ -1362,7 +1363,7 @@ You can then decide what to do like so: ```csharp var response = await _myRefitClient.GetSomeStuff(); -if(response.IsSuccessStatusCode) +if(response.IsSuccessful) { //do your thing } @@ -1372,6 +1373,9 @@ else } ``` +> [!NOTE] +> The `IsSuccessful` property checks whether the response status code is in the range 200-299 and there wasn't any other error (for example, during content deserialization). If you just want to check the HTTP response status code, you can use the `IsSuccessStatusCode` property. + #### When returning `Task` Refit throws any `ApiException` raised by the `ExceptionFactory` when processing the response and any errors that occur when attempting to deserialize the response to `Task`. diff --git a/Refit/ApiResponse.cs b/Refit/ApiResponse.cs index 8c0adbfbe..4affdd5bc 100644 --- a/Refit/ApiResponse.cs +++ b/Refit/ApiResponse.cs @@ -77,7 +77,7 @@ public sealed class ApiResponse( public bool IsSuccessStatusCode => response.IsSuccessStatusCode; /// - /// Indicates whether the request was successful and there wasn't any other error (for example, during deserialization). + /// Indicates whether the request was successful and there wasn't any other error (for example, during content deserialization). /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(Content))] @@ -205,7 +205,7 @@ public interface IApiResponse : IApiResponse new bool IsSuccessStatusCode { get; } /// - /// Indicates whether the request was successful and there wasn't any other error (for example, during deserialization). + /// Indicates whether the request was successful and there wasn't any other error (for example, during content deserialization). /// [MemberNotNullWhen(true, nameof(Content))] [MemberNotNullWhen(true, nameof(ContentHeaders))] @@ -244,7 +244,7 @@ public interface IApiResponse : IDisposable bool IsSuccessStatusCode { get; } /// - /// Indicates whether the request was successful and there wasn't any other error (for example, during deserialization). + /// Indicates whether the request was successful and there wasn't any other error (for example, during content deserialization). /// #if NET6_0_OR_GREATER [MemberNotNullWhen(true, nameof(ContentHeaders))] From bc39e8f2693bc48bd3b153bf9af54b2edd02efc4 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Mon, 28 Oct 2024 09:32:30 +0100 Subject: [PATCH 7/8] Fix a typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd5a36be0..fd868ab4e 100644 --- a/README.md +++ b/README.md @@ -1101,7 +1101,7 @@ var httpStatus = response.StatusCode; //Determining if a success status code was received and there wasn't any other error //(for example, during content deserialization) -if(response.IsSuccessfull) +if(response.IsSuccessful) { //YAY! Do the thing... } From be5de70b98465bce3b1a410836e913d4c193f66e Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Tue, 29 Oct 2024 09:46:25 +0100 Subject: [PATCH 8/8] Update tests Added a new test method `When_SerializationErrorOnSuccessStatusCode_EnsureSuccesStatusCodeAsync_DoNotThrowApiException` to verify that `EnsureSuccessStatusCodeAsync` does not throw an `ApiException` on deserialization error with a success status code. Included assertions to check response status and error presence. Enhanced existing test method `When_SerializationErrorOnSuccessStatusCode_EnsureSuccessfulAsync_ThrowsApiException` with additional assertions. --- Refit.Tests/ResponseTests.cs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Refit.Tests/ResponseTests.cs b/Refit.Tests/ResponseTests.cs index 8ba3583bd..ed6ff63c6 100644 --- a/Refit.Tests/ResponseTests.cs +++ b/Refit.Tests/ResponseTests.cs @@ -175,7 +175,7 @@ public async Task When_BadRequest_EnsureSuccessStatusCodeAsync_ThrowsValidationE } /// - /// Test to verify if IsSuccess returns false if we have a success status code, but there is a deserialization exception + /// Test to verify if IsSuccessful returns false if we have a success status code, but there is a deserialization error /// [Fact] public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccessful_ShouldReturnFalse() @@ -193,10 +193,34 @@ public async Task When_SerializationErrorOnSuccessStatusCode_IsSuccessful_Should Assert.True(response.IsSuccessStatusCode); Assert.False(response.IsSuccessful); + Assert.NotNull(response.Error); } /// - /// Test to verify if EnsureSuccessAsync throws an ApiException if we have a success status code, but there is a deserialization exception + /// Test to verify if EnsureSuccessStatusCodeAsync do not throw an ApiException if we have a success status code, but there is a deserialization error + /// + [Fact] + public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccesStatusCodeAsync_DoNotThrowApiException() + { + var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("Invalid JSON") + }; + + mockHandler + .Expect(HttpMethod.Get, "http://api/GetApiResponseTestObject") + .Respond(req => expectedResponse); + + using var response = await fixture.GetApiResponseTestObject(); + await response.EnsureSuccessStatusCodeAsync(); + + Assert.True(response.IsSuccessStatusCode); + Assert.False(response.IsSuccessful); + Assert.NotNull(response.Error); + } + + /// + /// Test to verify if EnsureSuccessfulAsync throws an ApiException if we have a success status code, but there is a deserialization error /// [Fact] public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessfulAsync_ThrowsApiException() @@ -215,6 +239,8 @@ public async Task When_SerializationErrorOnSuccessStatusCode_EnsureSuccessfulAsy () => response.EnsureSuccessfulAsync() ); + Assert.True(response.IsSuccessStatusCode); + Assert.False(response.IsSuccessful); Assert.NotNull(actualException); Assert.IsType(actualException.InnerException); }