Return network failures, timeouts and bad payloads as errors - #18
Merged
Merged
Conversation
The live API returns table B rates in the same shape as table A (table, currency, code, rates) without `country` and `symbol`, so the required properties of CountryExchangeRatesDto made every GetCountry* call end in a SerializationError. - Remove GetCountry* methods, CountryExchangeRates and CountryExchangeRatesDto. - Add an optional `TableType table = TableType.A` parameter to the mid-rate methods of INbpCurrencyExchangeRateClient, validated and recorded as the `nbp.table` span tag. - Document the table B publication schedule (Wednesdays). - Replace hand-written WireMock payloads with fixtures captured from the real API and add manual tests against the real API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The request and response were never disposed. With ResponseHeadersRead, a non-success response kept its connection until GC. Both are now disposed on every path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows the status mapping in ADR-0002: - 400 -> new BadRequestError carrying the NBP plain-text message (e.g. "400 BadRequest - Przekroczony limit 255 wyników / ..."), truncated to 500 characters. - 401/403 -> UnauthorizedError. - 5xx -> ServiceUnavailableError (previously UnknownError). - Every HTTP error stores the status code in Metadata under OpenUrzednikError.StatusCodeMetadataKey. Adds BadRequestError/BadRequestException to Core, an optional statusCode parameter to the HTTP-related errors, and WireMock tests for 400/401/403/404/429/5xx using bodies captured from the real API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SerializationError.ToException() returned the raw JsonException, so EnsureSuccess() threw outside the library's exception hierarchy. It now returns a new SerializationException (an OpenUrzednikException) with the original exception as InnerException. The exception is optional, so an empty payload can be reported without one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read at most 500 characters of an error body through a StreamReader instead of buffering the whole body, and log a guarded Debug message when it can't be read. Assert exact status codes in the Core tests, check truncation properly, and use one fixtures glob so the csproj matches the table B branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…' into fix/nbp-failures-in-result
… into fix/nbp-failures-in-result # Conflicts: # docs/BACKLOG.md
Follows ADR-0002: failures that can happen during normal execution are returned in the result, not thrown. - HttpRequestException (and IOException while reading the body) -> ServiceUnavailableError, which now keeps the original exception. - Timeouts (OperationCanceledException while the caller's token is not cancelled) -> new RequestTimeoutError. HttpClient.Timeout now also covers reading the body, which ResponseHeadersRead left unbounded. - Empty payload -> SerializationError (was UnknownError). - Mappers reject bad API data (null rates, null items) with an internal InvalidPayloadException; NbpPayload.Map returns it as a SerializationError. Array mappers return IReadOnlyList<T>. - Both catch (Exception) blocks in GetNbpAsync are gone. - Caller cancellation still throws OperationCanceledException. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er client Also: a clearer timeout message when HttpClient.Timeout is infinite, an error.code tag on the HTTP span for network and timeout failures, a payload-level name for null items, and tests for timeouts and caller cancellation while reading a 400 body. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
milten89
added a commit
that referenced
this pull request
Oct 1, 2026
- P1: drop item 11, which #22 fixed (the #23 merge put it back); add item 27, the unconfirmed gold publication hour. - P2: items 12 and 13 describe what is left after #16-#18; item 26 (WireMock error paths per client) goes with item 12. - P3: item 20 is no longer a vulnerability after #12; item 21 notes what is documented; new items 24 (Microsoft.Testing.Platform, blocks Dependabot #11) and 25 (outdated GITHUB-SETUP.md). Item 22 points to the new prose skills. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
milten89
added a commit
that referenced
this pull request
Oct 1, 2026
- P1: drop item 11, which #22 fixed (the #23 merge put it back); add item 27, the unconfirmed gold publication hour. - P2: items 12 and 13 describe what is left after #16-#18; item 26 (WireMock error paths per client) goes with item 12. - P3: item 20 is no longer a vulnerability after #12; item 21 notes what is documented; new items 24 (Microsoft.Testing.Platform, blocks Dependabot #11) and 25 (outdated GITHUB-SETUP.md). Item 22 points to the new prose skills. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes BACKLOG P0 item 4 (ADR-0002: "Failures that escape the result"). This is the last P0 item.
Changes
HttpRequestExceptionwhile sendingServiceUnavailableErrorwith the original exception (Exception, andInnerExceptionofToException())HttpRequestException/IOExceptionwhile reading the bodyServiceUnavailableErrorOperationCanceledExceptionwhile the caller's token is not cancelled)RequestTimeoutError(Timeout,Exception) /RequestTimeoutExceptionOperationCanceledException)null)UnknownErrorSerializationErrorcharsetinContent-TypeInvalidOperationException)SerializationError"rates": null,[null]itemsArgumentExceptionfrom mappersSerializationErrorcatch (Exception)blocks are gone; only specific exception types are caught.InvalidPayloadException. The newNbpPayload.Maphelper (used at all 25 client call sites) returns it as aSerializationError, writes an Error log and records it on the span. Array mappers now returnIReadOnlyList<T>.Behaviour changes (pre-1.0, put them in the release notes)
HttpClient.Timeout. WithResponseHeadersRead, the timeout used to stop applying once the headers arrived. A short timeout combined with a large date range can now produce aRequestTimeoutError.try/catch (HttpRequestException / TaskCanceledException)will no longer see those exceptions. It must check the result, or useEnsureSuccess(), which throwsServiceUnavailableException/RequestTimeoutException.SerializationErrorinstead ofUnknownError.ServiceUnavailableErrorgains an optionalexceptionconstructor parameter. Source stays compatible; binary compatibility breaks.Tests
BadRequestErrorwithout the server message);NbpPayload, and the mappers throwingInvalidPayloadException.[null]item, and"rates": nullfor the currency and table clients.integrationTest.runsettings) pass, 54/54.Verification
dotnet build OpenUrzednik.slnx: 0 errors (only the existing NU1902 warning).dotnet test -f net10.0: Core 84/84, Nbp 454/454, IntegrationTests 47 passed / 7 skipped (manual tests).dotnet format --verify-no-changes: clean.revieweragent: no blocking findings. I fixed all three should-fix points (charset, client-levelrates: nulltests, behaviour changes listed above) and most of the nits.🤖 Generated with Claude Code