refit icon indicating copy to clipboard operation
refit copied to clipboard

[BUG] When response deserialization fails, Content is null in ApiException

Open KarolStosik opened this issue 2 years ago • 5 comments

Describe the bug

When Refit can't deserialize response from api, ApiException is thrown. Unfortunately, Content property is always null, but it should be populated with the response content, that can't be deserialized.

Steps To Reproduce

Reproduction with simple XUnit test (api can be arbitrary, here I've used first public api I've found):

using System.Threading.Tasks;
using Refit;
using Xunit;

namespace RefitReproduction
{
    public class RefitNullContentTest
    {
        [Fact]
        public async Task ShouldHaveContentInException()
        {
            var api = RestService.For<IApi>("https://api.spacexdata.com");
            var exception = await Assert.ThrowsAsync<ApiException>(() => api.GetLaunches());
            Assert.NotNull(exception.Content); // fails!
        }
    }

    public interface IApi
    {
        [Get("/v3/launches")]
        public Task<double> GetLaunches(); // any type not matching api response
    }
}

Expected behavior

When deserialization fails, Content property should be populated with response content.

Environment

  • OS: Windows
  • Device: PC
  • .NET version: 6.0
  • Refit version: 6.3.2

KarolStosik avatar Jul 19 '22 09:07 KarolStosik

I've found the source of bug:

ApiException.cs, line 132:

            try
            {
                exception.ContentHeaders = response.Content.Headers;
                var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                exception.Content = content;

                if (response.Content.Headers?.ContentType?.MediaType?.Equals("application/problem+json") ?? false)
                {
                    exception = ValidationApiException.Create(exception);                    
                }

                response.Content.Dispose();
            }
            catch
            {
                // NB: We're already handling an exception at this point, 
                // so we want to make sure we don't throw another one 
                // that hides the real error.
            }

response.Content.ReadAsStringAsync() throws System.InvalidOperationException: 'The stream was already consumed. It cannot be read again.'

Anyone knows how to fix it?

KarolStosik avatar Jul 21 '22 10:07 KarolStosik

So, any solutions now?

XVCoder avatar Aug 20 '23 04:08 XVCoder

Hi, were you able to figure out a solution to this problem?

thomasb-gr avatar Oct 03 '23 11:10 thomasb-gr

I just ran into this myself -- I found a stopgap solution in the interim while Dwolla gets around to resolving this which unfortunately judging by the timestamps on comments on this thread could be awhile. 😞 I just manually deserialize the RawContent property into the expected type myself. The below example is utilizing Newtonsoft.Json for the deserialization and Dwolla.Client v6.0.0:

// Omitted setting up uri and headers for brevity. var result = await client.GetAsync<BalanceResponse>(uri, headers); // Omitted response error checking for brevity. return JsonConvert.DeserializeObject<BalanceResponse>(result.RawContent);

KSwift87 avatar Mar 18 '24 19:03 KSwift87