Skip to content

Status code change in response from 4XX to 500#2949

Open
predic8 wants to merge 3 commits into
masterfrom
status-code-change-in-response
Open

Status code change in response from 4XX to 500#2949
predic8 wants to merge 3 commits into
masterfrom
status-code-change-in-response

Conversation

@predic8
Copy link
Copy Markdown
Member

@predic8 predic8 commented May 18, 2026

Summary by CodeRabbit

  • Bug Fixes

    • More consistent HTTP status handling: certain 4xx conditions are now converted to 500 in response flows; XML-to-JSON failures consistently return 400.
    • Improved detection and mapping of XML parsing errors.
  • Tests

    • Added coverage for response error status behavior.
    • Test files refactored for clarity and modern imports.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e6e1595-8f80-448c-b564-70f033bf2a6d

📥 Commits

Reviewing files that changed from the base of the PR and between 8be0335 and 2f08ef5.

📒 Files selected for processing (1)
  • core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java

📝 Walkthrough

Walkthrough

This PR centralizes response-status handling: ProblemDetails adds a helper that maps certain 4XX response codes to 500 in response/Exchange contexts; Xml2JsonInterceptor now always sets 400 on transform errors; XML parse-error detection is broadened via substring matches; tests and imports are cleaned up accordingly.

Changes

Error Handling and XML Detection Refinement

Layer / File(s) Summary
ProblemDetails 4XX→500 status code mapping helper
core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java
Replaces wildcard imports with explicit imports and adds correctStatusCodeForResponse(Exchange, int); ResponseBuilder now uses the corrected status code rather than the raw status.
Xml2JsonInterceptor status code simplification and test coverage
core/src/main/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptor.java, core/src/test/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptorTest.java
Xml2JsonInterceptor sets a constant 400 on transformation errors for both request and response flows. A new test verifies response-path errors result in a 500 after ProblemDetails mapping.
XML parsing error detection refinement
core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java, core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
Imports made explicit; prolog/trailing-content parse errors are detected via shorter substring matches (e.g., "rolog", "ontent") to match more parser message variants.
Tests, imports, visibility updates, and CLI cleanup
core/src/test/..., core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java
Many tests updated to explicit imports, method visibility/fixture changes (public→package-private, instance→static parameter providers), some var local usage; small RouterCLI import cleanup. Some test expectations updated to reflect 500 after ProblemDetails mapping.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • membrane/api-gateway#2751: Directly related refinement of XSLTInterceptor error handling based on prolog-related message inspection and error mapping.
  • membrane/api-gateway#2658: Earlier refactoring of XSLTInterceptor exception-to-error mapping for XML/prolog parsing problems.

Suggested reviewers

  • christiangoerdes
  • rrayst

Poem

A rabbit hops through error flows,
Where 4XX turns into five-hundred throws,
XML whispers fragmented signs,
Imports trimmed and tests align,
Hooray — the gateway’s tidy now! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change across multiple files: converting 4XX HTTP status codes to 500 in error response scenarios.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch status-code-change-in-response

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java (1)

127-141: ⚡ Quick win

Consider case-insensitive matching for cleaner intent.

Using "rolog" and "ontent" as substring fragments works but is fragile and unclear. Since the helper method centralizes the logic, updating it to use case-insensitive matching would be cleaner:

♻️ Suggested fix
 private static boolean causeMessageContains(Throwable t, String fragment) {
     for (Throwable c = t; c != null; c = c.getCause()) {
         String m = c.getMessage();
-        if (m != null && m.contains(fragment))
+        if (m != null && m.toLowerCase().contains(fragment.toLowerCase()))
             return true;
     }
     return false;
 }

Then use the full words:

-            // Matches: prolog and Prolog
-            if (causeMessageContains(e, "rolog")) {
+            if (causeMessageContains(e, "prolog")) {
...
-            // Matches: Content and content
-            if (causeMessageContains(e, "ontent")) {
+            if (causeMessageContains(e, "content")) {
🤖 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
`@core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java`
around lines 127 - 141, The current checks in XPathExchangeExpression use
fragile substrings ("rolog"/"ontent"); update the logic to be case-insensitive
and use full words instead: change the calls in XPathExchangeExpression to check
for "prolog" and "content" (instead of "rolog"/"ontent") and modify the helper
causeMessageContains to perform a case-insensitive contains (e.g., normalize
both the exception message and the needle to lower-case) so
ExchangeExpressionException creation (expression, e), body(...) calls
(msg.getBodyAsStringDecoded(), truncateAfter, tail) continue to work unchanged
but the matching is robust and clearer.
core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java (1)

82-92: ⚡ Quick win

Consider case-insensitive matching instead of partial substring.

Using "rolog" as a substring match is fragile and could theoretically match unintended words. A cleaner approach that explicitly conveys the case-insensitive intent:

cause.getMessage().toLowerCase().contains("prolog")

Also note the inconsistency: this check now uses a short fragment, but line 92 still uses the full phrase "is not allowed in trailing section". If parser variations are a concern for prolog messages, the same likely applies to trailing section messages.

♻️ Suggested fix using case-insensitive matching
-            // rolog matches Prolog and prolog
-            if (cause.getMessage() != null && cause.getMessage().contains("rolog")) {
+            if (cause.getMessage() != null && cause.getMessage().toLowerCase().contains("prolog")) {

And for consistency at line 92:

-            if (cause.getMessage() != null && cause.getMessage().contains("is not allowed in trailing section")) {
+            if (cause.getMessage() != null && cause.getMessage().toLowerCase().contains("trailing section")) {
🤖 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
`@core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java`
around lines 82 - 92, The current fragile substring check in XSLTInterceptor
using cause.getMessage().contains("rolog") should be made case-insensitive and
explicit by checking cause.getMessage() != null and using
toLowerCase().contains("prolog") (update the condition around the
prolog-handling branch that builds the user(...) response), and make the
trailing-section check consistent by similarly normalizing case for the "is not
allowed in trailing section" check; ensure you only alter the conditional
expressions (around cause.getMessage()) and leave the
user(...).title(...).detail(...).internal(...).stacktrace(false).buildAndSetResponse(exc)
flow intact.
🤖 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.

Inline comments:
In `@core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java`:
- Around line 371-373: The current check in correctStatusCodeForResponse uses
exc.getResponse() != null to detect response flow which is too broad; change the
condition to detect actual response flow (e.g., use an explicit
Exchange.isResponseFlow() or equivalent flow flag/property) instead of presence
of a Response object, and only up-convert 4xx to 500 when that true
response-flow check indicates we're in the response path; if Exchange lacks such
a helper, add/consume a clear boolean (e.g., isResponseFlow or a RESPONSE_FLOW
exchange property) and update correctStatusCodeForResponse to rely on it rather
than getResponse() != null.
- Around line 354-355: The problem is that createMap() is using the original
status while you call statusCode(correctStatusCodeForResponse(...)) afterward,
causing HTTP status and the "status" field to diverge; fix by computing the
corrected status first (call correctStatusCodeForResponse(exchange, status) once
into a local correctedStatus), use that correctedStatus when creating the
builder (statusCode(correctedStatus)) and ensure createMap() is invoked
with/after that correctedStatus (or accept correctedStatus as a parameter) so
the map's "status" field matches the HTTP response status; update references
around statusCode(...), correctStatusCodeForResponse(...), createMap(), and the
builder variable accordingly.

---

Nitpick comments:
In
`@core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java`:
- Around line 82-92: The current fragile substring check in XSLTInterceptor
using cause.getMessage().contains("rolog") should be made case-insensitive and
explicit by checking cause.getMessage() != null and using
toLowerCase().contains("prolog") (update the condition around the
prolog-handling branch that builds the user(...) response), and make the
trailing-section check consistent by similarly normalizing case for the "is not
allowed in trailing section" check; ensure you only alter the conditional
expressions (around cause.getMessage()) and leave the
user(...).title(...).detail(...).internal(...).stacktrace(false).buildAndSetResponse(exc)
flow intact.

In
`@core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java`:
- Around line 127-141: The current checks in XPathExchangeExpression use fragile
substrings ("rolog"/"ontent"); update the logic to be case-insensitive and use
full words instead: change the calls in XPathExchangeExpression to check for
"prolog" and "content" (instead of "rolog"/"ontent") and modify the helper
causeMessageContains to perform a case-insensitive contains (e.g., normalize
both the exception message and the needle to lower-case) so
ExchangeExpressionException creation (expression, e), body(...) calls
(msg.getBodyAsStringDecoded(), truncateAfter, tail) continue to work unchanged
but the matching is robust and clearer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a61f8891-990c-4b8c-bf8f-82387fad832b

📥 Commits

Reviewing files that changed from the base of the PR and between 003b0e9 and 8be0335.

📒 Files selected for processing (9)
  • core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java
  • core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptor.java
  • core/src/main/java/com/predic8/membrane/core/lang/xpath/XPathExchangeExpression.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/xml/Xml2JsonInterceptorTest.java
  • core/src/test/java/com/predic8/membrane/core/interceptor/xslt/XSLTInterceptorTest.java
  • core/src/test/java/com/predic8/membrane/core/openapi/serviceproxy/OpenAPIPublisherInterceptorTest.java
  • core/src/test/java/com/predic8/membrane/core/openapi/validators/exceptions/ExceptionInterceptorTest.java
💤 Files with no reviewable changes (1)
  • core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java

Comment on lines +354 to 355
var builder = statusCode(correctStatusCodeForResponse(exchange,status));
try {
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

HTTP status and problem payload status can diverge after remapping.

Line 354 applies corrected HTTP status, but createMap() already wrote the original status field. This can return HTTP 500 with body "status": 400, which is inconsistent for Problem Details consumers.

Proposed fix
-    private Response createContent(Map<String, Object> root, Exchange exchange) {
-        var builder = statusCode(correctStatusCodeForResponse(exchange,status));
+    private Response createContent(Map<String, Object> root, Exchange exchange) {
+        var effectiveStatus = correctStatusCodeForResponse(exchange, status);
+        root.put(STATUS, effectiveStatus);
+        var builder = statusCode(effectiveStatus);
         try {
🤖 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 `@core/src/main/java/com/predic8/membrane/core/exceptions/ProblemDetails.java`
around lines 354 - 355, The problem is that createMap() is using the original
status while you call statusCode(correctStatusCodeForResponse(...)) afterward,
causing HTTP status and the "status" field to diverge; fix by computing the
corrected status first (call correctStatusCodeForResponse(exchange, status) once
into a local correctedStatus), use that correctedStatus when creating the
builder (statusCode(correctedStatus)) and ensure createMap() is invoked
with/after that correctedStatus (or accept correctedStatus as a parameter) so
the map's "status" field matches the HTTP response status; update references
around statusCode(...), correctStatusCodeForResponse(...), createMap(), and the
builder variable accordingly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

statusCode(..) sets the statusCode on the builder

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.

@predic8, you're correct that statusCode(correctStatusCodeForResponse(exchange, status)) properly sets the HTTP response line (e.g., HTTP/1.1 500). My concern is about a different thing — the "status" field inside the JSON problem-details body.

createMap() is called before createContent, and it writes:

root.put(STATUS, status);   // e.g. 400 — the *original* field value

Then createContent receives that already-populated map and sets the corrected HTTP status on the builder. The result is:

HTTP/1.1 500 Internal Server Error          ← builder: corrected to 500 ✅
Content-Type: application/problem+json

{
  "status": 400,                             ← body field: still original 400 ❌
  ...
}

Per RFC 9457 §3.1, the "status" member "is advisory; it conveys the HTTP status code used for the convenience of the consumer", so it should match the actual HTTP status. A Problem Details consumer relying on that field would see an inconsistency here.

The minimal fix is to update the "status" entry in root inside createContent after the remapping:

 private Response createContent(Map<String, Object> root, Exchange exchange) {
-    var builder = statusCode(correctStatusCodeForResponse(exchange, status));
+    var effectiveStatus = correctStatusCodeForResponse(exchange, status);
+    root.put(STATUS, effectiveStatus);   // keep body in sync with HTTP status
+    var builder = statusCode(effectiveStatus);
     try {

Would you like to address this, or should I open a follow-up issue for it?


🧠 Learnings used
Learnt from: rrayst
Repo: membrane/api-gateway PR: 2863
File: core/src/main/java/com/predic8/membrane/core/interceptor/server/WSDLPublisherInterceptor.java:107-107
Timestamp: 2026-03-11T21:11:47.936Z
Learning: When reviewing refactors that extract logic from a synchronized block into a new method, verify that the synchronization guard (the GuardedBy target, e.g., the lock on paths) is carried into the extracted method and not dropped. Ensure shared fields annotated with GuardedBy(

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