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:
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user