Skip to content

Commit 0f3f30d

Browse files
committed
fix: Return 400 for malformed request bodies
An unparseable request body (e.g. a double comma in JSON) made the body mapper throw its library-specific runtime exception, which fell through to the default 500 branch of the exception handler. Convert any parse failure at the single mapper.readFrom call site into a ValidationException so it renders as a 400 application/problem+json response, consistent with the other body-validation errors. Covers all body mappers.
1 parent e9cfa18 commit 0f3f30d

2 files changed

Lines changed: 34 additions & 14 deletions

File tree

src/main/java/com/retailsvc/http/internal/RequestPreparationFilter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,16 @@ private ParsedBody validateAndParseBody(HttpExchange exchange, Operation op, byt
243243
new ValidationError(
244244
BODY_POINTER, "content-type", "unsupported content type: " + mediaType, null));
245245
}
246-
Object parsed = mapper.readFrom(body, header);
246+
Object parsed;
247+
try {
248+
parsed = mapper.readFrom(body, header);
249+
} catch (RuntimeException e) {
250+
// Body could not be parsed (e.g. malformed JSON). Untrusted input -> 400, not 500.
251+
LOG.debug("Failed to parse request body", e);
252+
throw new ValidationException(
253+
new ValidationError(
254+
BODY_POINTER, "malformed", "request body is not valid " + mediaType, null));
255+
}
247256
if (mediaType.equals("application/x-www-form-urlencoded") && parsed instanceof Map<?, ?> map) {
248257
@SuppressWarnings("unchecked")
249258
Map<String, Object> typed = (Map<String, Object>) map;

src/test/java/com/retailsvc/http/OpenApiServerIT.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,28 @@ void postDataShouldReturnJsonBody() {
127127
}
128128
}
129129

130+
@Test
131+
void postDataShouldReturnBadRequestOnMalformedJson() throws Exception {
132+
try (var server = newServer(Map.of("post-data", new EchoHandler()));
133+
var client = httpClient()) {
134+
135+
// Double comma after a property is invalid JSON; must be 400, not 500.
136+
var body = "{\"id\":\"some-id\",,\"age\":42}";
137+
var headers = Map.of("correlation-id", UUID.randomUUID().toString());
138+
var request = newRequest(server, path, "POST", ofString(body), headers);
139+
140+
var response = client.send(request, BodyHandlers.ofString());
141+
var statusCode = response.statusCode();
142+
var contentType = response.headers().firstValue("Content-Type").orElse("");
143+
var responseBody = response.body();
144+
145+
assertThat(statusCode).isEqualTo(400);
146+
assertThat(contentType).contains("application/problem+json");
147+
assertThat(responseBody).contains("keyword");
148+
assertThat(responseBody).contains("pointer");
149+
}
150+
}
151+
130152
@Test
131153
void postDataShouldReturnBadRequestOnMissingRequiredProperties() {
132154
Map<String, RequestHandler> handlers = Map.of("post-data", new EchoHandler());
@@ -217,7 +239,7 @@ void listObjectsShouldReturnJsonBody() {
217239
}
218240

219241
@Test
220-
void listObjectsShouldReturnBadRequestOnPassingObjectInsteadOfArray() {
242+
void listObjectsShouldReturnBadRequestOnPassingObjectInsteadOfArray() throws Exception {
221243
try (var server = newServer(Map.of("post-list-objects", new EchoHandler()));
222244
var client = httpClient()) {
223245

@@ -235,12 +257,6 @@ void listObjectsShouldReturnBadRequestOnPassingObjectInsteadOfArray() {
235257
assertThat(contentType).contains("application/problem+json");
236258
assertThat(responseBody).contains("keyword");
237259
assertThat(responseBody).contains("pointer");
238-
239-
} catch (IOException e) {
240-
fail(e);
241-
} catch (InterruptedException e) {
242-
Thread.currentThread().interrupt();
243-
fail(e);
244260
}
245261
}
246262
}
@@ -448,7 +464,7 @@ void postShapeUnknownKindReturns400() {
448464
}
449465

450466
@Test
451-
void postShapeMissingDiscriminatorReturns400() {
467+
void postShapeMissingDiscriminatorReturns400() throws Exception {
452468
// omitting "kind" makes both branches fail "required".
453469
try (var server = newServer(Map.of("post-shape", new EchoHandler()));
454470
var client = httpClient()) {
@@ -464,11 +480,6 @@ void postShapeMissingDiscriminatorReturns400() {
464480
// Both branches fail identically at /kind required -> de-duplicated to one entry.
465481
assertThat(response.body()).contains("\"errors\"").contains("#/kind");
466482
assertThat(response.body().split("#/kind", -1)).hasSize(2); // exactly one occurrence
467-
} catch (IOException e) {
468-
fail(e);
469-
} catch (InterruptedException e) {
470-
Thread.currentThread().interrupt();
471-
fail(e);
472483
}
473484
}
474485
}

0 commit comments

Comments
 (0)