diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java index 5df77ee8e68..4ee8fdc923c 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,11 +31,10 @@ import org.springframework.validation.ObjectError; import org.springframework.web.server.ServerWebInputException; + /** - * A specialization of {@link ServerWebInputException} thrown when after data - * binding and validation failure. Implements {@link BindingResult} (and its - * super-interface {@link Errors}) to allow for direct analysis of binding and - * validation errors. + * {@link ServerWebInputException} subclass that indicates a data binding or + * validation failure. * * @author Rossen Stoyanchev * @since 5.0 diff --git a/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java b/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java new file mode 100644 index 00000000000..4bf58579fa8 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/server/MissingRequestValueException.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server; + + +import org.springframework.core.MethodParameter; + + +/** + * {@link ServerWebInputException} subclass that indicates a missing request + * value such as a request header, cookie value, query parameter, etc. + * + * @author Rossen Stoyanchev + * @since 6.0 + */ +@SuppressWarnings("serial") +public class MissingRequestValueException extends ServerWebInputException { + + private final String name; + + private final Class type; + + private final String label; + + + public MissingRequestValueException(String name, Class type, String label, MethodParameter parameter) { + super("Required " + label + " '" + name + "' is not present.", parameter); + this.name = name; + this.type = type; + this.label = label; + getBody().withDetail(getReason()); + } + + + /** + * Return the name of the missing value, e.g. the name of the missing request + * header, or cookie, etc. + */ + public String getName() { + return this.name; + } + + /** + * Return the target type the value is converted when present. + */ + public Class getType() { + return this.type; + } + + /** + * Return a label that describes the request value, e.g. "request header", + * "cookie value", etc. Use this to create a custom message. + */ + public String getLabel() { + return this.label; + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java b/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java new file mode 100644 index 00000000000..62a8fa0cbb0 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/server/UnsatisfiedRequestParameterException.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server; + + +import java.util.List; + +import org.springframework.util.MultiValueMap; + +/** + * {@link ServerWebInputException} subclass that indicates an unsatisfied + * parameter condition, as typically expressed using an {@code @RequestMapping} + * annotation at the {@code @Controller} type level. + * + * @author Rossen Stoyanchev + * @since 6.0 + */ +@SuppressWarnings("serial") +public class UnsatisfiedRequestParameterException extends ServerWebInputException { + + private final List conditions; + + private final MultiValueMap requestParams; + + + public UnsatisfiedRequestParameterException( + List conditions, MultiValueMap requestParams) { + + super(initReason(conditions, requestParams)); + this.conditions = conditions; + this.requestParams = requestParams; + getBody().withDetail("Invalid request parameters."); + } + + private static String initReason(List conditions, MultiValueMap queryParams) { + StringBuilder sb = new StringBuilder("Parameter conditions "); + int i = 0; + for (String condition : conditions) { + if (i > 0) { + sb.append(" OR "); + } + sb.append('"').append(condition).append('"'); + i++; + } + sb.append(" not met for actual request parameters: ").append(queryParams); + return sb.toString(); + } + + + /** + * Return String representations of the unsatisfied condition(s). + */ + public List getConditions() { + return this.conditions; + } + + /** + * Return the actual request parameters. + */ + public MultiValueMap getRequestParams() { + return this.requestParams; + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java index 9ef5acbaf6e..aa4e9594b01 100644 --- a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java +++ b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java @@ -29,6 +29,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ProblemDetail; import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -43,7 +44,9 @@ import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.server.MethodNotAllowedException; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.NotAcceptableStatusException; +import org.springframework.web.server.UnsatisfiedRequestParameterException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.testfixture.method.ResolvableMethod; @@ -288,6 +291,29 @@ void notAcceptableStatusExceptionWithParseError() { assertThat(ex.getHeaders()).isEmpty(); } + @Test + void missingRequestValueException() { + + ErrorResponse ex = new MissingRequestValueException( + "foo", String.class, "header", this.methodParameter); + + assertStatus(ex, HttpStatus.BAD_REQUEST); + assertDetail(ex, "Required header 'foo' is not present."); + assertThat(ex.getHeaders()).isEmpty(); + } + + @Test + void unsatisfiedRequestParameterException() { + + ErrorResponse ex = new UnsatisfiedRequestParameterException( + Arrays.asList("foo=bar", "bar=baz"), + new LinkedMultiValueMap<>(Collections.singletonMap("q", Arrays.asList("1", "2")))); + + assertStatus(ex, HttpStatus.BAD_REQUEST); + assertDetail(ex, "Invalid request parameters."); + assertThat(ex.getHeaders()).isEmpty(); + } + @Test void webExchangeBindException() { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index e347507b594..2262bea233e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.server.UnsatisfiedRequestParameterException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.util.pattern.PathPattern; @@ -190,7 +191,8 @@ protected HandlerMethod handleNoMatch(Set infos, catch (InvalidMediaTypeException ex) { throw new UnsupportedMediaTypeStatusException(ex.getMessage()); } - throw new UnsupportedMediaTypeStatusException(contentType, new ArrayList<>(mediaTypes), exchange.getRequest().getMethod()); + throw new UnsupportedMediaTypeStatusException( + contentType, new ArrayList<>(mediaTypes), exchange.getRequest().getMethod()); } if (helper.hasProducesMismatch()) { @@ -199,9 +201,9 @@ protected HandlerMethod handleNoMatch(Set infos, } if (helper.hasParamsMismatch()) { - throw new ServerWebInputException( - "Expected parameters: " + helper.getParamConditions() + - ", actual query parameters: " + request.getQueryParams()); + throw new UnsatisfiedRequestParameterException( + helper.getParamConditions().stream().map(Object::toString).toList(), + request.getQueryParams()); } return null; @@ -217,10 +219,9 @@ private static class PartialMatchHelper { public PartialMatchHelper(Set infos, ServerWebExchange exchange) { - this.partialMatches.addAll(infos.stream(). - filter(info -> info.getPatternsCondition().getMatchingCondition(exchange) != null). - map(info -> new PartialMatch(info, exchange)). - collect(Collectors.toList())); + this.partialMatches.addAll(infos.stream() + .filter(info -> info.getPatternsCondition().getMatchingCondition(exchange) != null) + .map(info -> new PartialMatch(info, exchange)).toList()); } @@ -235,42 +236,38 @@ public boolean isEmpty() { * Any partial matches for "methods"? */ public boolean hasMethodsMismatch() { - return this.partialMatches.stream(). - noneMatch(PartialMatch::hasMethodsMatch); + return this.partialMatches.stream().noneMatch(PartialMatch::hasMethodsMatch); } /** * Any partial matches for "methods" and "consumes"? */ public boolean hasConsumesMismatch() { - return this.partialMatches.stream(). - noneMatch(PartialMatch::hasConsumesMatch); + return this.partialMatches.stream().noneMatch(PartialMatch::hasConsumesMatch); } /** * Any partial matches for "methods", "consumes", and "produces"? */ public boolean hasProducesMismatch() { - return this.partialMatches.stream(). - noneMatch(PartialMatch::hasProducesMatch); + return this.partialMatches.stream().noneMatch(PartialMatch::hasProducesMatch); } /** * Any partial matches for "methods", "consumes", "produces", and "params"? */ public boolean hasParamsMismatch() { - return this.partialMatches.stream(). - noneMatch(PartialMatch::hasParamsMatch); + return this.partialMatches.stream().noneMatch(PartialMatch::hasParamsMatch); } /** * Return declared HTTP methods. */ public Set getAllowedMethods() { - return this.partialMatches.stream(). - flatMap(m -> m.getInfo().getMethodsCondition().getMethods().stream()). - map(requestMethod -> HttpMethod.valueOf(requestMethod.name())). - collect(Collectors.toSet()); + return this.partialMatches.stream() + .flatMap(m -> m.getInfo().getMethodsCondition().getMethods().stream()) + .map(requestMethod -> HttpMethod.valueOf(requestMethod.name())) + .collect(Collectors.toSet()); } /** @@ -278,9 +275,10 @@ public Set getAllowedMethods() { * match the "methods" condition. */ public Set getConsumableMediaTypes() { - return this.partialMatches.stream().filter(PartialMatch::hasMethodsMatch). - flatMap(m -> m.getInfo().getConsumesCondition().getConsumableMediaTypes().stream()). - collect(Collectors.toCollection(LinkedHashSet::new)); + return this.partialMatches.stream() + .filter(PartialMatch::hasMethodsMatch) + .flatMap(m -> m.getInfo().getConsumesCondition().getConsumableMediaTypes().stream()) + .collect(Collectors.toCollection(LinkedHashSet::new)); } /** @@ -288,9 +286,10 @@ public Set getConsumableMediaTypes() { * match the "methods" and "consumes" conditions. */ public Set getProducibleMediaTypes() { - return this.partialMatches.stream().filter(PartialMatch::hasConsumesMatch). - flatMap(m -> m.getInfo().getProducesCondition().getProducibleMediaTypes().stream()). - collect(Collectors.toCollection(LinkedHashSet::new)); + return this.partialMatches.stream() + .filter(PartialMatch::hasConsumesMatch) + .flatMap(m -> m.getInfo().getProducesCondition().getProducibleMediaTypes().stream()) + .collect(Collectors.toCollection(LinkedHashSet::new)); } /** @@ -298,9 +297,10 @@ public Set getProducibleMediaTypes() { * match the "methods", "consumes", and "params" conditions. */ public List>> getParamConditions() { - return this.partialMatches.stream().filter(PartialMatch::hasProducesMatch). - map(match -> match.getInfo().getParamsCondition().getExpressions()). - collect(Collectors.toList()); + return this.partialMatches.stream() + .filter(PartialMatch::hasProducesMatch) + .map(match -> match.getInfo().getParamsCondition().getExpressions()) + .collect(Collectors.toList()); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index 79dde79b078..1990164d3ce 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -225,8 +225,14 @@ private Throwable handleReadError(MethodParameter parameter, Throwable ex) { } private ServerWebInputException handleMissingBody(MethodParameter parameter) { - String paramInfo = parameter.getExecutable().toGenericString(); - return new ServerWebInputException("Request body is missing: " + paramInfo, parameter); + + DecodingException cause = new DecodingException( + "No request body for: " + parameter.getExecutable().toGenericString()); + + ServerWebInputException ex = new ServerWebInputException("No request body", parameter, cause); + ex.setDetail("Invalid request content"); + + return ex; } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java index bac576326eb..254f9a4e489 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerErrorException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; @@ -244,9 +245,8 @@ protected void handleMissingValue(String name, MethodParameter parameter, Server * @param parameter the method parameter */ protected void handleMissingValue(String name, MethodParameter parameter) { - String typeName = parameter.getNestedParameterType().getSimpleName(); - throw new ServerWebInputException("Missing argument '" + name + "' for method " + - "parameter of type " + typeName, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "request value", parameter); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java index 6a1d9c63ea0..253d2af52fa 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; /** * Resolve method arguments annotated with {@code @CookieValue}. @@ -76,9 +76,8 @@ protected Object resolveNamedValue(String name, MethodParameter parameter, Serve @Override protected void handleMissingValue(String name, MethodParameter parameter) { - String type = parameter.getNestedParameterType().getSimpleName(); - String reason = "Missing cookie '" + name + "' for method parameter of type " + type; - throw new ServerWebInputException(reason, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "cookie", parameter); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/MatrixVariableMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/MatrixVariableMethodArgumentResolver.java index f43e6a8af31..949f4a5583a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/MatrixVariableMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/MatrixVariableMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.MatrixVariable; import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerErrorException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; @@ -120,9 +121,8 @@ else if (paramValues.size() == 1) { @Override protected void handleMissingValue(String name, MethodParameter parameter) throws ServerWebInputException { - String paramInfo = parameter.getNestedParameterType().getSimpleName(); - throw new ServerWebInputException("Missing matrix variable '" + name + "' " + - "for method parameter of type " + paramInfo, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "path parameter", parameter); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java index 627538a4a61..36200dbfae7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,8 @@ import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.ValueConstants; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; /** * Resolves method arguments annotated with an @{@link RequestAttribute}. @@ -90,9 +90,8 @@ protected Object resolveNamedValue(String name, MethodParameter parameter, Serve @Override protected void handleMissingValue(String name, MethodParameter parameter) { - String type = parameter.getNestedParameterType().getSimpleName(); - String reason = "Missing request attribute '" + name + "' of type " + type; - throw new ServerWebInputException(reason, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "request attribute", parameter); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java index b2069cde440..6b45326bee9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; /** * Resolves method arguments annotated with {@code @RequestHeader} except for @@ -89,9 +89,8 @@ protected Object resolveNamedValue(String name, MethodParameter parameter, Serve @Override protected void handleMissingValue(String name, MethodParameter parameter) { - String type = parameter.getNestedParameterType().getSimpleName(); - throw new ServerWebInputException("Missing request header '" + name + "' " + - "for method parameter of type " + type, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "header", parameter); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java index da8744c595b..2cb066640a9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,8 +28,8 @@ import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ValueConstants; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; /** * Resolver for method arguments annotated with @{@link RequestParam} from URI @@ -109,9 +109,8 @@ protected Object resolveNamedValue(String name, MethodParameter parameter, Serve @Override protected void handleMissingValue(String name, MethodParameter parameter, ServerWebExchange exchange) { - String type = parameter.getNestedParameterType().getSimpleName(); - String reason = "Required " + type + " parameter '" + name + "' is not present"; - throw new ServerWebInputException(reason, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "query parameter", parameter); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java index abf9a390931..e50fc5b86b2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestPartMethodArgumentResolver.java @@ -37,8 +37,8 @@ import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.reactive.BindingContext; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; /** * Resolver for {@code @RequestPart} arguments where the named part is decoded @@ -115,8 +115,8 @@ public Flux getPartValues( List list = map.get(name); if (CollectionUtils.isEmpty(list)) { if (isRequired) { - String reason = "Required request part '" + name + "' is not present"; - throw new ServerWebInputException(reason, parameter); + throw new MissingRequestValueException( + name, parameter.getParameterType(), "request part", parameter); } return Collections.emptyList(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java index 4ce30aa4a96..5c871a47ba5 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import org.springframework.util.Assert; import org.springframework.web.bind.annotation.SessionAttribute; import org.springframework.web.bind.annotation.ValueConstants; +import org.springframework.web.server.MissingRequestValueException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; /** * Resolves method arguments annotated with an @{@link SessionAttribute}. @@ -62,9 +62,8 @@ protected Mono resolveName(String name, MethodParameter parameter, Serve @Override protected void handleMissingValue(String name, MethodParameter parameter) { - String type = parameter.getNestedParameterType().getSimpleName(); - String reason = "Missing session attribute '" + name + "' of type " + type; - throw new ServerWebInputException(reason, parameter); + throw new MissingRequestValueException( + name, parameter.getNestedParameterType(), "session attribute", parameter); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java index e73d2468d89..65481bc9eae 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMappingTests.java @@ -53,7 +53,7 @@ import org.springframework.web.server.MethodNotAllowedException; import org.springframework.web.server.NotAcceptableStatusException; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.ServerWebInputException; +import org.springframework.web.server.UnsatisfiedRequestParameterException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.server.MockServerWebExchange; @@ -182,7 +182,7 @@ public void getHandlerTestMediaTypeNotAcceptable() { public void getHandlerTestRequestParamMismatch() { ServerWebExchange exchange = MockServerWebExchange.from(get("/params")); Mono mono = this.handlerMapping.getHandler(exchange); - assertError(mono, ServerWebInputException.class, ex -> { + assertError(mono, UnsatisfiedRequestParameterException.class, ex -> { assertThat(ex.getReason()).contains("[foo=bar]"); assertThat(ex.getReason()).contains("[bar=baz]"); }); @@ -212,7 +212,9 @@ public void getHandlerProducibleMediaTypesAttribute() { exchange = MockServerWebExchange.from(get("/content").accept(MediaType.APPLICATION_JSON)); this.handlerMapping.getHandler(exchange).block(); - assertThat(exchange.getAttributes().get(name)).as("Negated expression shouldn't be listed as producible type").isNull(); + assertThat(exchange.getAttributes().get(name)) + .as("Negated expression shouldn't be listed as producible type") + .isNull(); } @Test @@ -352,7 +354,9 @@ private void testHttpMediaTypeNotSupportedException(String url) { ServerWebExchange exchange = MockServerWebExchange.from(request); Mono mono = this.handlerMapping.getHandler(exchange); - assertError(mono, UnsupportedMediaTypeStatusException.class, ex -> assertThat(ex.getSupportedMediaTypes()).as("Invalid supported consumable media types").isEqualTo(Collections.singletonList(new MediaType("application", "xml")))); + assertError(mono, UnsupportedMediaTypeStatusException.class, ex -> assertThat(ex.getSupportedMediaTypes()) + .as("Invalid supported consumable media types") + .isEqualTo(Collections.singletonList(new MediaType("application", "xml")))); } private void testHttpOptions(String requestURI, Set allowedMethods, @Nullable MediaType acceptPatch) { @@ -382,7 +386,9 @@ private void testMediaTypeNotAcceptable(String url) { ServerWebExchange exchange = MockServerWebExchange.from(get(url).accept(MediaType.APPLICATION_JSON)); Mono mono = this.handlerMapping.getHandler(exchange); - assertError(mono, NotAcceptableStatusException.class, ex -> assertThat(ex.getSupportedMediaTypes()).as("Invalid supported producible media types").isEqualTo(Collections.singletonList(new MediaType("application", "xml")))); + assertError(mono, NotAcceptableStatusException.class, ex -> assertThat(ex.getSupportedMediaTypes()) + .as("Invalid supported producible media types") + .isEqualTo(Collections.singletonList(new MediaType("application", "xml")))); } private void handleMatch(ServerWebExchange exchange, String pattern) {