Skip to content

fix: read problem details when the serializer has no usable metadata - #2371

Closed
AtakanUk wants to merge 1 commit into
reactiveui:mainfrom
AtakanUk:fix-problem-details-fallback
Closed

AtakanUk wants to merge 1 commit into
reactiveui:mainfrom
AtakanUk:fix-problem-details-fallback

Conversation

@AtakanUk

@AtakanUk AtakanUk commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #2368

ApiException.Create turns a problem+json response into a ValidationApiException through the configured serializer. With a source-generated context (including RefitSettings.ForJsonContext), or in a trimmed or Native AOT app, that read throws, the failure is swallowed, and the caller gets a plain ApiException.

Changes in ValidationApiException:

  • CreateAsync still tries the configured serializer first, so fix: ValidationApiException propagates ContentHeaders and uses configured serializer #2146 keeps working. If it throws NotSupportedException or InvalidOperationException, the content is read with DeserializeProblemDetails, which needs no metadata. A JsonException means the body itself was rejected, so that case stays a plain ApiException, as it does today.
  • A DynamicDependency keeps the ProblemDetails constructor and properties. The fallback alone isn't enough: it makes the constructor reachable, so the reflection path stops throwing in a trimmed app and returns an empty result. In a published Blazor WebAssembly app this gave a ValidationApiException with no Errors and no Extensions.
  • The constructor now carries over the message, inner exception and RequestContent. Otherwise clients that used to get a plain ApiException would lose them. This also applies to responses that already became a ValidationApiException.

The Errors and ContextClientSample documentation samples no longer need the manual parse, so they now catch ValidationApiException.

Things worth checking in review:

  • On context and Native AOT clients the fallback is the only reader, so they behave like the public ValidationApiException.Create: non-object errors are ignored, and a body it can't read, such as "status":"400", stays a plain ApiException.
  • Any serializer that throws one of those two exception types takes the same path, so XmlContentSerializer clients now get a ValidationApiException for problem+json too. Newtonsoft results are unchanged.
  • ExceptionRedactor can't clear the parsed ValidationApiException.Content. Default clients already work this way, but context clients now reach that path too.
  • On .NET 11, System.Text.Json can read a context that describes ProblemDetails, so that case only uses the fallback on earlier targets.

Keeping the swallowed failure visible (the second suggestion in the issue) is not part of this PR.

Tests: unit tests in ApiExceptionTests.ProblemDetailsFallback.cs and a problem reply in Refit.NativeAotSmoke, which fails without the fix. The DynamicDependency has no automated test, because the smoke app has no reflection path. I checked it by hand with PublishTrimmed console apps on .NET 8, 9 and 10, and a published Blazor WebAssembly app run in headless Edge.

@glennawatson

Copy link
Copy Markdown
Contributor

Thanks @AtakanUk, this is a solid fix and the tests are thorough. I'm closing this PR so I can rework it on a new branch built on your commit. There is an existing internal ProblemDetailsJsonContext that I'd like to use for the read, which should avoid the catch filter and the DynamicDependency. Your commit stays in the history, so you keep the credit, and we'll credit you in the follow-up PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: problem+json responses silently become a plain ApiException with source-generated JSON metadata or in a trimmed app

2 participants