summaryrefslogtreecommitdiffstats
path: root/container-core
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@vespa.ai>2024-02-27 08:05:51 +0100
committerBjørn Christian Seime <bjorncs@vespa.ai>2024-02-27 08:05:51 +0100
commitb107fe16cf50d5ffde3499667461103275725e83 (patch)
tree22862cb398b35342e6f07a20f74f5c7b4483d28f /container-core
parent7e89f23fb55879d7265ae74bd218a29df642fe5d (diff)
Handle `InvalidJsonException` by default. Fix mapper ordering
Diffstat (limited to 'container-core')
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java6
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java2
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java20
3 files changed, 27 insertions, 1 deletions
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index 090e06c221f..e5aeb33d45d 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -211,7 +211,11 @@ class RestApiImpl implements RestApi {
exceptionMappers.addAll(RestApiMappers.DEFAULT_EXCEPTION_MAPPERS);
}
// Topologically sort children before superclasses, so most the specific match is found by iterating through mappers in order.
- exceptionMappers.sort((a, b) -> (a.type.isAssignableFrom(b.type) ? 1 : 0) + (b.type.isAssignableFrom(a.type) ? -1 : 0));
+ exceptionMappers.sort((l, r) -> {
+ if (l.type.equals(r.type)) return 0;
+ if (l.type.isAssignableFrom(r.type)) return 1;
+ return -1;
+ });
return exceptionMappers;
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
index 62b46d26ba9..6d785ba58bc 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
@@ -1,6 +1,7 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
+import ai.vespa.json.InvalidJsonException;
import ai.vespa.json.Json;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -52,6 +53,7 @@ public class RestApiMappers {
(context, entity) -> new JacksonJsonResponse<>(200, entity, context.jacksonJsonMapper(), true)));
static List<ExceptionMapperHolder<?>> DEFAULT_EXCEPTION_MAPPERS = List.of(
+ new ExceptionMapperHolder<>(InvalidJsonException.class, (ctx, e) -> ErrorResponse.badRequest(e.getMessage())),
new ExceptionMapperHolder<>(RestApiException.class, (context, exception) -> exception.response()));
private RestApiMappers() {}
diff --git a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
index d27e04bbd7a..d959344685a 100644
--- a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
@@ -1,6 +1,7 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
+import ai.vespa.json.InvalidJsonException;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.yahoo.container.jdisc.AclMapping;
import com.yahoo.container.jdisc.HttpRequestBuilder;
@@ -20,6 +21,8 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.MissingFormatWidthException;
+import java.util.NoSuchElementException;
import java.util.Set;
import static com.yahoo.jdisc.http.HttpRequest.Method;
@@ -99,6 +102,23 @@ class RestApiImplTest {
}
@Test
+ void chooses_most_specific_exception_mapper() {
+ RestApi restApi = RestApi.builder()
+ .addRoute(route("/json").get(ctx -> { throw new InvalidJsonException("oops invalid json"); }))
+ .addRoute(route("/illegal-argument").get(ctx -> { throw new IllegalArgumentException(); }))
+ .addRoute(route("/bad-format").get(ctx -> { throw new MissingFormatWidthException(""); }))
+ .addExceptionMapper(IllegalArgumentException.class, (ctx, exception) -> ErrorResponse.badRequest("oops illegal argument"))
+ .addExceptionMapper(NoSuchElementException.class, (ctx, exception) -> ErrorResponse.badRequest("oops no such element"))
+ .addExceptionMapper(RuntimeException.class, (ctx, exception) -> ErrorResponse.internalServerError("oops runtime"))
+ .addExceptionMapper(MissingFormatWidthException.class, (ctx, exception) -> ErrorResponse.internalServerError("oops bad format width"))
+ .build();
+ // Uses default mapper for `InvalidJsonException` since it's more specific than `IllegalArgumentException`
+ verifyJsonResponse(restApi, Method.GET, "/json", null, 400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"oops invalid json\"}");
+ verifyJsonResponse(restApi, Method.GET, "/illegal-argument", null, 400, "{\"message\":\"oops illegal argument\", \"error-code\":\"BAD_REQUEST\"}");
+ verifyJsonResponse(restApi, Method.GET, "/bad-format", null, 500, "{\"message\":\"oops bad format width\", \"error-code\":\"INTERNAL_SERVER_ERROR\"}");
+ }
+
+ @Test
void method_handler_can_consume_and_produce_json() {
RestApi.HandlerWithRequestEntity<TestEntity, TestEntity> handler = (context, requestEntity) -> requestEntity;
RestApi restApi = RestApi.builder()