Refine ModelAttributeMethodProcessor Kotlin exception handling

This commit refines ModelAttributeMethodProcessor Kotlin exception
handling in order to throw a proper MethodArgumentNotValidException
instead of a NullPointerException when Kotlin null-safety constraints
are not fulfilled, translating to an HTTP error with 400 status code
(Bad Request) instead of 500 (Internal Server Error).

Closes gh-23846
This commit is contained in:
Sébastien Deleuze
2023-02-14 14:07:41 +01:00
parent 979118c1eb
commit 569df6eecc
3 changed files with 135 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -16,6 +16,7 @@
package org.springframework.web.bind;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -42,13 +43,18 @@ import org.springframework.web.ErrorResponse;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 3.1
*/
@SuppressWarnings("serial")
public class MethodArgumentNotValidException extends BindException implements ErrorResponse {
@Nullable
private final MethodParameter parameter;
@Nullable
private final Executable executable;
private final ProblemDetail body;
@@ -60,6 +66,19 @@ public class MethodArgumentNotValidException extends BindException implements Er
public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) {
super(bindingResult);
this.parameter = parameter;
this.executable = null;
this.body = ProblemDetail.forStatusAndDetail(getStatusCode(), "Invalid request content.");
}
/**
* Constructor for {@link MethodArgumentNotValidException}.
* @param executable the executable that failed validation
* @param bindingResult the results of the validation
*/
public MethodArgumentNotValidException(Executable executable, BindingResult bindingResult) {
super(bindingResult);
this.parameter = null;
this.executable = executable;
this.body = ProblemDetail.forStatusAndDetail(getStatusCode(), "Invalid request content.");
}
@@ -83,9 +102,16 @@ public class MethodArgumentNotValidException extends BindException implements Er
@Override
public String getMessage() {
StringBuilder sb = new StringBuilder("Validation failed for argument [")
.append(this.parameter.getParameterIndex()).append("] in ")
.append(this.parameter.getExecutable().toGenericString());
StringBuilder sb = new StringBuilder("Validation failed ");
if (this.parameter != null) {
sb.append("for argument [")
.append(this.parameter.getParameterIndex()).append("] in ")
.append(this.parameter.getExecutable().toGenericString());
}
else {
sb.append("in ")
.append(this.executable.toGenericString());
}
BindingResult bindingResult = getBindingResult();
if (bindingResult.getErrorCount() > 1) {
sb.append(" with ").append(bindingResult.getErrorCount()).append(" errors");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -36,6 +36,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -47,6 +48,7 @@ import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.ValidationAnnotationUtils;
@@ -329,7 +331,21 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
throw new MethodArgumentNotValidException(parameter, result);
}
return BeanUtils.instantiateClass(ctor, args);
try {
return BeanUtils.instantiateClass(ctor, args);
}
catch (BeanInstantiationException ex) {
Throwable cause = ex.getCause();
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) && cause instanceof NullPointerException) {
BindingResult result = binder.getBindingResult();
ObjectError error = new ObjectError(ctor.getName(), cause.getMessage());
result.addError(error);
throw new MethodArgumentNotValidException(ctor, result);
}
else {
throw ex;
}
}
}
/**