Skip to content

Commit 4f22cc3

Browse files
committed
Introduce "quarkus.rest.exception-mapping.disable-mapper-for"
Relates to #36155
1 parent 2d6c672 commit 4f22cc3

File tree

4 files changed

+119
-2
lines changed

4 files changed

+119
-2
lines changed

extensions/resteasy-reactive/rest-common/runtime/src/main/java/io/quarkus/resteasy/reactive/common/runtime/ResteasyReactiveConfig.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.quarkus.resteasy.reactive.common.runtime;
22

3+
import java.util.List;
4+
import java.util.Optional;
5+
36
import io.quarkus.runtime.annotations.ConfigPhase;
47
import io.quarkus.runtime.annotations.ConfigRoot;
58
import io.quarkus.runtime.configuration.MemorySize;
@@ -11,6 +14,11 @@
1114
@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
1215
public interface ResteasyReactiveConfig {
1316

17+
/**
18+
* Exception mapping configuration.
19+
*/
20+
ExceptionMappingConfig exceptionMapping();
21+
1422
/**
1523
* The amount of memory that can be used to buffer input before switching to
1624
* blocking IO, up to {@code Long.MAX_VALUE} bytes.
@@ -81,4 +89,18 @@ public interface ResteasyReactiveConfig {
8189
*/
8290
@WithDefault("true")
8391
boolean removesTrailingSlash();
92+
93+
/**
94+
* Configuration for exception mapping.
95+
*/
96+
interface ExceptionMappingConfig {
97+
/**
98+
* A list of exception mapper classes that should be disabled.
99+
* This allows users to override the default built-in exception mappers provided by Quarkus extensions.
100+
* <p>
101+
* For example, to disable the built-in Jackson MismatchedInputException mapper:
102+
* {@code quarkus.rest.exception-mapping.disable-mapper-for=io.quarkus.resteasy.reactive.jackson.runtime.mappers.BuiltinMismatchedInputExceptionMapper}
103+
*/
104+
Optional<List<String>> disableMapperFor();
105+
}
84106
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.quarkus.resteasy.reactive.jackson.deployment.test;
2+
3+
import java.util.function.Supplier;
4+
5+
import jakarta.ws.rs.core.Response;
6+
import jakarta.ws.rs.ext.ExceptionMapper;
7+
import jakarta.ws.rs.ext.Provider;
8+
9+
import org.jboss.shrinkwrap.api.ShrinkWrap;
10+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.RegisterExtension;
13+
14+
import com.fasterxml.jackson.databind.JsonMappingException;
15+
16+
import io.quarkus.test.QuarkusUnitTest;
17+
import io.restassured.RestAssured;
18+
19+
/**
20+
* Tests that disabling BuiltinMismatchedInputExceptionMapper allows a custom mapper
21+
* for JsonMappingException (superclass of MismatchedInputException) to handle the exception.
22+
* Without the disable-mapper-for config, the built-in mapper would take precedence.
23+
*/
24+
public class ExceptionInReaderWithDisabledBuiltInMapperTest {
25+
26+
@RegisterExtension
27+
static QuarkusUnitTest test = new QuarkusUnitTest()
28+
.setArchiveProducer(new Supplier<>() {
29+
@Override
30+
public JavaArchive get() {
31+
return ShrinkWrap.create(JavaArchive.class)
32+
.addClasses(FroMage.class, FroMageEndpoint.class, CustomJsonMappingExceptionMapper.class);
33+
}
34+
}).overrideConfigKey("quarkus.rest.exception-mapping.disable-mapper-for",
35+
"io.quarkus.resteasy.reactive.jackson.runtime.mappers.BuiltinMismatchedInputExceptionMapper");
36+
37+
@Test
38+
public void test() {
39+
// Send invalid JSON that triggers MismatchedInputException (price should be Integer, not String)
40+
// With BuiltinMismatchedInputExceptionMapper disabled, our custom JsonMappingException mapper
41+
// (which handles the superclass) should catch it and return 888.
42+
// Without the config, the built-in mapper would return 400.
43+
RestAssured.with().contentType("application/json").body("{\"price\": \"ten\"}").put("/fromage")
44+
.then().statusCode(888); // Custom mapper returns 888
45+
}
46+
47+
@Provider
48+
public static class CustomJsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
49+
50+
@Override
51+
public Response toResponse(JsonMappingException exception) {
52+
return Response.status(888).entity("Custom mapper handled: " + exception.getMessage()).build();
53+
}
54+
}
55+
}

extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem;
6666
import io.quarkus.resteasy.reactive.common.deployment.ResourceInterceptorsContributorBuildItem;
6767
import io.quarkus.resteasy.reactive.common.deployment.ResourceScanningResultBuildItem;
68+
import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig;
6869
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
6970
import io.quarkus.resteasy.reactive.server.spi.UnwrappedExceptionBuildItem;
7071
import io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem;
@@ -189,11 +190,21 @@ public ExceptionMappersBuildItem scanForExceptionMappers(CombinedIndexBuildItem
189190
BuildProducer<AdditionalBeanBuildItem> additionalBeanBuildItemBuildProducer,
190191
BuildProducer<ReflectiveClassBuildItem> reflectiveClassBuildItemBuildProducer,
191192
List<ExceptionMapperBuildItem> mappers, List<UnwrappedExceptionBuildItem> unwrappedExceptions,
192-
Capabilities capabilities) {
193+
Capabilities capabilities,
194+
ResteasyReactiveConfig config) {
193195
AdditionalBeanBuildItem.Builder beanBuilder = AdditionalBeanBuildItem.builder().setUnremovable();
194196
ExceptionMapping exceptions = ResteasyReactiveExceptionMappingScanner
195197
.scanForExceptionMappers(combinedIndexBuildItem.getComputingIndex(), applicationResultBuildItem.getResult());
196198

199+
// Populate disabled mappers from configuration
200+
if (config.exceptionMapping().disableMapperFor().isPresent()) {
201+
for (String disabledMapper : config.exceptionMapping().disableMapperFor().get()) {
202+
if (disabledMapper != null && !disabledMapper.isEmpty()) {
203+
exceptions.addDisabledMapper(disabledMapper);
204+
}
205+
}
206+
}
207+
197208
exceptions.addBlockingProblem(BlockingOperationNotAllowedException.class);
198209
exceptions.addBlockingProblem(BlockingNotAllowedException.class);
199210
for (UnwrappedExceptionBuildItem bi : unwrappedExceptions) {

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ExceptionMapping.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import java.util.ArrayList;
44
import java.util.Collections;
55
import java.util.HashMap;
6+
import java.util.HashSet;
67
import java.util.List;
78
import java.util.Map;
9+
import java.util.Set;
810
import java.util.function.Function;
911
import java.util.function.Predicate;
1012
import java.util.function.Supplier;
@@ -31,6 +33,11 @@ public class ExceptionMapping {
3133
// this is going to be used when there are mappers that are removable at runtime
3234
final Map<String, List<ResourceExceptionMapper<? extends Throwable>>> runtimeCheckMappers = new HashMap<>();
3335

36+
/**
37+
* Exception mapper class names that should be disabled.
38+
*/
39+
final Set<String> disabledMappers = new HashSet<>();
40+
3441
/**
3542
* Exceptions that indicate an blocking operation was performed on an IO thread.
3643
* <p>
@@ -126,9 +133,17 @@ public Map<String, List<ResourceExceptionMapper<? extends Throwable>>> getRuntim
126133
return runtimeCheckMappers;
127134
}
128135

136+
public Set<String> getDisabledMappers() {
137+
return disabledMappers;
138+
}
139+
140+
public void addDisabledMapper(String mapperClassName) {
141+
disabledMappers.add(mapperClassName);
142+
}
143+
129144
public Map<String, ResourceExceptionMapper<? extends Throwable>> effectiveMappers() {
130145
if (runtimeCheckMappers.isEmpty()) {
131-
return mappers;
146+
return filterDisabledMappers(mappers);
132147
}
133148
Map<String, ResourceExceptionMapper<? extends Throwable>> result = new HashMap<>();
134149
for (var entry : runtimeCheckMappers.entrySet()) {
@@ -147,6 +162,20 @@ public Map<String, ResourceExceptionMapper<? extends Throwable>> effectiveMapper
147162
}
148163
}
149164
result.putAll(mappers);
165+
return filterDisabledMappers(result);
166+
}
167+
168+
private Map<String, ResourceExceptionMapper<? extends Throwable>> filterDisabledMappers(
169+
Map<String, ResourceExceptionMapper<? extends Throwable>> mappers) {
170+
if (disabledMappers.isEmpty()) {
171+
return mappers;
172+
}
173+
Map<String, ResourceExceptionMapper<? extends Throwable>> result = new HashMap<>();
174+
for (Map.Entry<String, ResourceExceptionMapper<? extends Throwable>> entry : mappers.entrySet()) {
175+
if (!disabledMappers.contains(entry.getValue().getClassName())) {
176+
result.put(entry.getKey(), entry.getValue());
177+
}
178+
}
150179
return result;
151180
}
152181

0 commit comments

Comments
 (0)