Skip to content

GL-7: Handle invalid requests with proper REST exceptions#960

Open
fil512 wants to merge 4 commits intomasterfrom
GL-7-proper-rest-exceptions
Open

GL-7: Handle invalid requests with proper REST exceptions#960
fil512 wants to merge 4 commits intomasterfrom
GL-7-proper-rest-exceptions

Conversation

@fil512
Copy link

@fil512 fil512 commented Mar 6, 2026

Summary

This change replaces raw Java exceptions (IllegalArgumentException, IllegalStateException, RuntimeException) with proper HAPI FHIR REST exceptions across the CQIS and DTR REST API code paths in cdr-cr. Without this fix, invalid client requests and recoverable error conditions result in generic HTTP 500 Internal Server Error responses instead of the semantically correct HTTP 400 Bad Request or HTTP 404 Not Found responses with structured OperationOutcome bodies.

  1. Input validation errors (unsupported parameters, malformed identifiers, missing required fields) now throw InvalidRequestException (HTTP 400) instead of IllegalArgumentException across cdr-persistence-dqm and cdr-prior-auth-dtr.
  2. Missing resource lookups (Organization, Group) now throw ResourceNotFoundException (HTTP 404) instead of IllegalArgumentException.
  3. Internal state guards that are not reachable from user input — specifically in R4BundleHelper, SubjectResultsAggregator, and R4MeasureValidator scoring-code checks — were evaluated and correctly left as IllegalArgumentException or updated to InvalidRequestException only where client data can trigger them.
  4. The measure scoring-code validation in R4MeasureValidator was corrected from InternalErrorException (HTTP 500) to InvalidRequestException (HTTP 400) — a Measure with an invalid scoringCode is a bad request, not a server fault.
  5. A new RestExceptionReproductionTest in cdr-prior-auth-dtr verifies the DTR $next-question and $questionnaire-package operations surface InvalidRequestException for null/missing inputs.

Code Review Suggestions

  • R4BundleHelper left as IllegalArgumentException — Confirm the call graph: if any path from a REST operation can reach getBundlesFromParameters, getBatchJobId, or getBundleFirstMeasureReportStatus with user-supplied Parameters, these should be InvalidRequestException.
  • SubjectResultsAggregator "already has subjectResultsReference" guards reverted — Five InvalidRequestException sites in SubjectResultsAggregator were reverted to IllegalArgumentException on the grounds they are internal state guards. Verify this is safe.
  • R4MeasureValidator scoring-code: InternalErrorException to InvalidRequestException — The scoringCode null check and invalid-code check changed from 500 to 400. Verify the Measure resource is always user-supplied data.

QA Test Suggestions

Async $evaluate-measure — invalid parameters

  • POST $evaluate-measure (async) with reportType=badvalue. Confirm HTTP 400 OperationOutcome (not HTTP 500).
  • POST with a subject parameter that omits the resource type. Confirm HTTP 400 OperationOutcome.
  • POST with a reporter reference to a non-existent Organization ID. Confirm HTTP 404 OperationOutcome.

QPP $qpp-build — invalid inputs

  • POST $qpp-build with an unrecognized qualityProgram parameter value. Confirm HTTP 400 OperationOutcome.

DTR $next-question — null/missing inputs

  • POST $next-question with null/empty body. Confirm HTTP 400 OperationOutcome.
  • POST with a QuestionnaireResponse missing the questionnaire property. Confirm HTTP 400.

DTR $questionnaire-package — feature disabled

  • With questionnaire-package disabled, POST $questionnaire-package. Confirm HTTP 400 (not HTTP 500).

Regression — valid requests still succeed

  • Confirm a valid async $evaluate-measure request completes successfully end-to-end.

@fil512 fil512 self-assigned this Mar 6, 2026
@github-actions
Copy link

github-actions bot commented Mar 6, 2026

Formatting check succeeded!

@fil512 fil512 marked this pull request as ready for review March 6, 2026 23:54
@fil512 fil512 force-pushed the GL-7-proper-rest-exceptions branch from 375ae42 to 3df964f Compare March 11, 2026 20:50
Ken Stevens and others added 4 commits March 11, 2026 18:53
Establishes architectural rules for module boundaries:
- Core modules (cqf-fhir-cr, cqf-fhir-cql, cqf-fhir-utility) must not
  import REST exceptions from ca.uhn.fhir.rest.server.exceptions
- Exception translation belongs at the boundary (cqf-fhir-cr-hapi)
- Includes correct/incorrect patterns and ArchUnit enforcement test

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add CrExceptionTranslator utility that catches domain exceptions from
core modules and translates them to HAPI FHIR REST exceptions at the
boundary layer. IllegalArgumentException becomes InvalidRequestException
(HTTP 400) and UnsupportedOperationException becomes
NotImplementedOperationException (HTTP 501). IllegalStateException is
intentionally not caught as HAPI's RestfulServer already wraps it as
InternalErrorException (HTTP 500).

Applied to all 51 @Operation-annotated provider methods across both R4
and DSTU3 packages using CrExceptionTranslator.execute() wrapper.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove unused executeVoid method and its tests from CrExceptionTranslator.
All 51 provider methods use execute() with Supplier — no provider has a
void return type requiring executeVoid.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Existing code has legacy violations of the REST exception import rule.
Add note acknowledging these as tech debt to be migrated over time,
not blockers for new contributions.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@fil512 fil512 force-pushed the GL-7-proper-rest-exceptions branch from 3df964f to 587aa09 Compare March 11, 2026 22:53
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3.5% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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.

1 participant