Skip to content

LEGLINK-887: Terminology FHIR endpoints return Problem Details on error - #1779

Merged
MikeAtPinnacle merged 6 commits into
devfrom
users/mtherien/leglink-887-bug-fix
Aug 6, 2026
Merged

LEGLINK-887: Terminology FHIR endpoints return Problem Details on error#1779
MikeAtPinnacle merged 6 commits into
devfrom
users/mtherien/leglink-887-bug-fix

Conversation

@MikeAtPinnacle

@MikeAtPinnacle MikeAtPinnacle commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🛠️ Description of Changes

Fixes LEGLINK-887.

FhirController answered every error with a bare string body and text/plain:

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8

No id or url parameter specified

No type, title, status or traceId — nothing a client can branch on, and nothing to correlate against a trace. LEGLINK-886 (#1777, merged) corrected the status code for a request naming no value set, but left the payload a plain string. This PR fixes the format, so the same request now returns:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "detail": "No id or url parameter specified.",
  "traceId": "00-af3d283aeb5f6bf14e01ac47f6bc2e6b-aea917848a69b991-01"
}

with Content-Type: application/problem+json.

This is not new infrastructure. AddTerminologyProblemDetails was already registered in Program.cs, and ConfigController has returned RFC 9457 Problem Details since LEGLINK-591FhirController was simply the straggler. traceId comes free from the existing customization, in W3C traceparent format.

Changes:

  • Adds TerminologyProblem plus BadRequestProblem / NotFoundProblem / InternalServerErrorProblem helpers, and routes all 13 error returns across the six actions through them (7 × BadRequest, 3 × NotFound, 3 × raw StatusCode(500, ...)).
  • 500 responses no longer place the exception message in the body. The customization substitutes a generic detail, so internal state stops leaking to callers.
  • detail is prose, so the helper appends a terminating period. Exception messages stay fragments because they are also read from logs and asserted in unit tests — this avoids rewriting every message string and its assertions.

🧪 Testing Performed

Unit: dotnet test DotNet/ServiceTests/ServiceTests.csproj --filter FullyQualifiedName~UnitTests.Terminology73 passed, 0 failed.

Manual against the local docker-compose stack (link-terminology on :8076, real terminology data — 98 value sets, 91 code systems):

Request Before After
empty valueUri in POST body (887's reported request) 400 text/plain No id or url parameter specified 400 problem+json, detail No id or url parameter specified.
empty url query parameter same same
markup-only display 400 text/plain 400 problem+json, detail Invalid value supplied for 'display'.
GET ValueSet/does-not-exist 404 text/plain 404 problem+json, type #section-15.5.5, title Not Found
$validate-code with an unknown code 200 {result:false, message:...} unchanged
$validate-code happy path 200 {result:true} unchanged

Every 4xx response was confirmed to carry type, title, status, detail and a W3C-format traceId under Content-Type: application/problem+json.

A Postman post-response script asserting the exact payload above (status, content type, all five members, type shape, traceparent format, and absence of exception/stackTrace) passes against this build.

🧑‍🔬 Unit Testing

  • I have written or updated unit tests to cover my changes
  • Coverage: 60.9%

📓 Documentation Updated

No documentation changes. No config keys were added, and the endpoints' success payloads are unchanged, so docs/command-validatevaluesetcode.html and docs/command-validatecodesystemcode.html remain accurate.

Summary by CodeRabbit

  • Bug Fixes
    • Standardized terminology API error responses using RFC 9457 Problem Details.
    • Improved consistency of HTTP status codes, titles, types, and error message formatting.
    • Updated validation handling for invalid or empty terminology input, including valueUri values.

FhirController answered every error with a bare string body and text/plain, so a
rejected request carried no type, title, status or traceId. LEGLINK-886 corrected the
status code for a request naming no value set, but the payload was still a plain
string; this makes the body match the RFC 9457 format ConfigController has returned
since LEGLINK-591.

- Adds TerminologyProblem plus BadRequestProblem, NotFoundProblem and
  InternalServerErrorProblem helpers, and routes all 13 error returns across the six
  actions through them (7 BadRequest, 3 NotFound, 3 raw 500).
- traceId comes from the existing AddTerminologyProblemDetails customization.
- detail is prose, so the helper appends a terminating period. Exception messages stay
  fragments because they are also read from logs and asserted in unit tests.
- 500 responses no longer place the exception message in the body; the customization
  substitutes a generic detail so internal state is not exposed to the caller.

Testing: 73 unit tests pass in UnitTests.Terminology, one new covering LEGLINK-887's
reported request (empty valueUri in the POST body). Verified against the local
docker-compose stack that an empty valueUri in the body, an empty url query parameter,
a markup-only display and an unknown ValueSet id all return application/problem+json
carrying type, title, status, detail and a W3C traceId, and that the $validate-code
success and failure payloads are unchanged.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b874fc30-c751-4f1d-bc6a-4f0ebc90158b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately describes the main change: adding RFC 9457 Problem Details responses to Terminology FHIR endpoints on error.
Description check ✅ Passed The description comprehensively covers the problem, solution, changes made, testing performed, and documentation status.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
DotNet/ServiceTests/UnitTests/Terminology/Controllers/FhirControllerTests.cs (1)

57-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover 404 and 500 Problem Details contracts.

AssertBadRequestProblem only verifies 400 responses. The controller now also returns NotFoundProblem and InternalServerErrorProblem. Add focused XUnit cases for both contracts. For 500, verify that the configured customization removes the raw exception detail without making an HTTP network call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/ServiceTests/UnitTests/Terminology/Controllers/FhirControllerTests.cs`
around lines 57 - 75, Add focused xUnit tests alongside AssertBadRequestProblem
for the controller’s NotFoundProblem and InternalServerErrorProblem responses,
validating their RFC 9457 status, title, type, and detail contracts. For the 500
case, exercise the configured customization directly or through the existing
test setup and assert the raw exception detail is removed, without making an
HTTP network call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@DotNet/ServiceTests/UnitTests/Terminology/Controllers/FhirControllerTests.cs`:
- Around line 57-75: Add focused xUnit tests alongside AssertBadRequestProblem
for the controller’s NotFoundProblem and InternalServerErrorProblem responses,
validating their RFC 9457 status, title, type, and detail contracts. For the 500
case, exercise the configured customization directly or through the existing
test setup and assert the raw exception detail is removed, without making an
HTTP network call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 507f1eea-306e-4015-9515-50a0cba662ff

📥 Commits

Reviewing files that changed from the base of the PR and between bf1a0bb and 94eed94.

📒 Files selected for processing (2)
  • DotNet/ServiceTests/UnitTests/Terminology/Controllers/FhirControllerTests.cs
  • DotNet/Terminology/Controllers/FhirController.cs

Follow-up to review feedback on #1779: AssertBadRequestProblem was the only assertion
helper, so NotFoundProblem and InternalServerErrorProblem shipped untested.

- Generalises the helper to AssertProblem(result, status, title, type, detail) and keeps
  AssertBadRequestProblem as a wrapper, leaving the existing call sites unchanged.
- Adds 404 coverage via GetValueSetById with an id the cache does not hold, and 500
  coverage via a code group cached under the ValueSet type whose resource is a CodeSystem.
- Exercises the configured CustomizeProblemDetails callback directly, with no host and no
  HTTP call, to assert a 5xx detail is replaced by the generic message and a traceId is
  added. A paired client-error test pins the scrubbing to 5xx so an over-broad change
  cannot silently erase the actionable 4xx detail this change exists to deliver.

The controller-level 500 test asserts status, title and type only: ProblemDetailsFactory
is null in unit tests, so ControllerBase.Problem builds a plain ProblemDetails and the
scrubbing belongs to the customization test.

Testing: 77 unit tests pass in UnitTests.Terminology, up from 73. Test project only; no
production code changed.
@MikeAtPinnacle
MikeAtPinnacle marked this pull request as ready for review August 4, 2026 15:52
@MikeAtPinnacle
MikeAtPinnacle merged commit 908f46f into dev Aug 6, 2026
22 of 23 checks passed
@MikeAtPinnacle
MikeAtPinnacle deleted the users/mtherien/leglink-887-bug-fix branch August 6, 2026 14:39
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.

2 participants