fix(core): honour HttpException status in the default exception handler

HttpException carries the status the caller meant, and its own javadoc says
extensions map it to a structured response — but nothing did. Every one reached
the catch-all and came back as 500, including the 400s that RequestHelper
raises for a malformed query param and that flash-ext-jackson raises for an
unparseable body. A handler doing the documented thing produced the wrong
status.

The default handler now renders HttpException at its own status, in both dev
and prod modes, with the message JSON-escaped. Not pre-encoded like JSON_404
and JSON_500: the message is per-exception, and a path that already unwound a
stack does not need the allocation shaved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Zakaria El Orche
2026-09-09 12:20:33 +00:00
co-authored by Claude Opus 5
parent 58bae41f7a
commit fe8c6ed162
2 changed files with 63 additions and 0 deletions
@@ -1,5 +1,7 @@
package dev.relism.flash.routing;
import dev.relism.flash.exceptions.HttpException;
import dev.relism.flash.http.ContentType;
import dev.relism.flash.http.HttpMethod;
import dev.relism.flash.models.Request;
import dev.relism.flash.models.RequestHandler;
@@ -7,6 +9,8 @@ import dev.relism.flash.models.Response;
import dev.relism.flash.models.SimpleHandler;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
class AbstractRouterTest {
@@ -77,4 +81,23 @@ class AbstractRouterTest {
router.onException((ex, req, res) -> "Caught");
assertEquals("Caught", router.getExceptionHandler().handle(new RuntimeException(), null, null));
}
/**
* HttpException carries the status the caller meant. Before this was honoured every one of
* them came back as 500, including the 400s RequestHelper and flash-ext-jackson raise.
*/
@Test
void defaultExceptionHandlerHonoursHttpExceptionStatus() throws Exception {
DummyRouter router = new DummyRouter();
Response res = new Response(200, ContentType.TEXT_PLAIN);
Object body = router.getExceptionHandler()
.handle(HttpException.badRequest("bad \"input\""), null, res);
assertEquals(400, res.getStatusCode());
String rendered = new String((byte[]) body, StandardCharsets.UTF_8);
assertTrue(rendered.contains("\\\"input\\\""), "message must be JSON-escaped: " + rendered);
assertTrue(rendered.contains("\"status\":400"), rendered);
}
}