Add WebInputException subclasses

Closes gh-28142
This commit is contained in:
rstoyanchev
2022-05-09 08:58:31 +01:00
parent 06e1cc2f9b
commit 5d0f49c2c8
15 changed files with 261 additions and 79 deletions

View File

@@ -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.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.util.pattern.PathPattern;
@@ -190,7 +191,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
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 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
}
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 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
public PartialMatchHelper(Set<RequestMappingInfo> 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 abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* 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<HttpMethod> 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 abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* match the "methods" condition.
*/
public Set<MediaType> 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 abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* match the "methods" and "consumes" conditions.
*/
public Set<MediaType> 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 abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
* match the "methods", "consumes", and "params" conditions.
*/
public List<Set<NameValueExpression<String>>> 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());
}
/**

View File

@@ -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 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
}
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;
}
/**

View File

@@ -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.WebDataBinder;
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 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
* @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);
}
/**

View File

@@ -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.http.HttpCookie;
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 @@ public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArg
@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);
}

View File

@@ -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.util.StringUtils;
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 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueSync
@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);
}

View File

@@ -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.lang.Nullable;
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 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSy
@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);
}
}

View File

@@ -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.core.convert.ConversionService;
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 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueSyncA
@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);
}

View File

@@ -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.lang.Nullable;
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 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr
@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);
}

View File

@@ -37,8 +37,8 @@ import org.springframework.util.CollectionUtils;
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 class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgu
List<Part> 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();
}

View File

@@ -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.core.ReactiveAdapterRegistry;
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 @@ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueAr
@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);
}
}

View File

@@ -53,7 +53,7 @@ import org.springframework.web.reactive.result.method.RequestMappingInfo.Builder
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 class RequestMappingInfoHandlerMappingTests {
public void getHandlerTestRequestParamMismatch() {
ServerWebExchange exchange = MockServerWebExchange.from(get("/params"));
Mono<Object> 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 class RequestMappingInfoHandlerMappingTests {
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 @@ public class RequestMappingInfoHandlerMappingTests {
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Object> 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<HttpMethod> allowedMethods, @Nullable MediaType acceptPatch) {
@@ -382,7 +386,9 @@ public class RequestMappingInfoHandlerMappingTests {
ServerWebExchange exchange = MockServerWebExchange.from(get(url).accept(MediaType.APPLICATION_JSON));
Mono<Object> 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) {