AssertTrue validation does not give right error message in jhi-alert-error - validation

I have added a validation in my DTO like this:
#AssertTrue(message = "validation.amountNot0RateAbove1" )
public boolean isAmountNot0RateAbove1() {
return amount == 0 ? true : rate != null && rate > 1;
}
this gives an error like this:
Resolved exception caused by Handler execution: org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<nl.tibi.sbys.service.dto.ProjectDTO> nl.tibi.sbys.web.rest.client.ClientProjectResource.updateProject(nl.tibi.sbys.service.dto.ProjectDTO) throws java.net.URISyntaxException, with 1 error(s): [Field error in object 'projectDTO' on field 'position[0].amountNot0RateAbove1': rejected value [false]; codes [AssertTrue.projectDTO.position[0].amountNot0RateAbove1,AssertTrue.projectDTO.position.amountNot0RateAbove1,AssertTrue.position[0].amountNot0RateAbove1,AssertTrue.position.amountNot0RateAbove1,AssertTrue.amountNot0RateAbove1,AssertTrue.boolean,AssertTrue]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [projectDTO.position[0].amountNot0RateAbove1,position[0].amountNot0RateAbove1]; arguments []; default message [position[0].amountNot0RateAbove1]]; default message [validation.amountNot0RateAbove1]]
the response is (type httpErrorResponse):
error:
fieldErrors:
Array(1)0:
field:"position[0].amountNot0RateAbove1"
message:"AssertTrue"
objectName:"projectDTO"
jhi-alert-error shows this message:
translation-not-found[error.AssertTrue]
what i would like to show is a custom error message so with amountNot0RateAbove1 key.
somehow the errors from the java code are not send to the front-end only the last key AssertTrue is send.
how should a change it?
i think it would be best if the first error from the java code would be send as message and not the last. so AssertTrue.projectDTO.position[0].amountNot0RateAbove1 instead or even better take the default message if set which is the message from the annotation:
#AssertTrue(message = "validation.amountNot0RateAbove1" )
any help?
as a not so nice workaround i tried to edit the respons setting the message in the line of this:
private onSaveError(res: HttpErrorResponse) {
console.log('res:', res);
res.error.fieldErrors[0].message = 'validation.amountNot0RateAbove1';
console.log('res:', res);
this.isSaving = false;
}
but although the code is executed it did not change the message.
this, src/main/webapp/app/shared/alert/alert-error.component.ts component makes the error message. i could do something there unless someone has a better solution :)
i think ExceptionTranslator is the place to be for improving the returned error message.
===========================edit==========================
here it can be changed. not sure what the effects will be for other errors:
#Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, #Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
.collect(Collectors.toList());
the f.getCode() will return the last code in the list which is the most general one. the first is the most specific.
seconde the message set in the AssertTrue annotation is found in the f.getDefaultMessage()
not sure if i should go for the first code or the default message

I still hope someone has a better solution but this is what i did. This code will keep the default way of working which is getting the most general message from the fielderror with getCode. But in rare cases you can overwrite it in this way:
ExceptionTranslator
public static final String USE_MESSAGE_AS_KEY = "USE_MESSAGE_AS_KEY:";
#Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, #Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = new ArrayList<>();
for (FieldError fieldError : result.getFieldErrors()) {
String errorCode = null;
if (StringUtils.startsWith(fieldError.getDefaultMessage(), USE_MESSAGE_AS_KEY)) {
errorCode = StringUtils.removeStart(fieldError.getDefaultMessage(), USE_MESSAGE_AS_KEY);
}else {
errorCode = fieldError.getCode();
}
fieldErrors.add(new FieldErrorVM(fieldError.getObjectName(), fieldError.getField(), errorCode));
}
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(defaultConstraintViolationStatus())
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", fieldErrors)
.build();
return create(ex, problem, request);
}
In a DTO you can now add this:
#AssertTrue(message = USE_MESSAGE_AS_KEY + "amountNot0RateAbove1")
public boolean isRate() {
return amount == 0 ? true : rate != null && rate > 0;
}
and it will use the message amountNot0RateAbove1 (prefixed with error.) as key to translate in the frontent to a nice error message.

I had similar issue - but I just wanted to output the #AssertTrue message in the response. I used this:
#Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, #Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(this::createFieldErrorVM)
.collect(Collectors.toList());
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(defaultConstraintViolationStatus())
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", fieldErrors)
.build();
return create(ex, problem, request);
}
private FieldErrorVM createFieldErrorVM(FieldError fieldError) {
String errorCode;
if (fieldError.getCode().equals("AssertTrue")) {
errorCode = fieldError.getDefaultMessage();
}else {
errorCode = fieldError.getCode();
}
return new FieldErrorVM(fieldError.getObjectName(), fieldError.getField(), errorCode);
}

Related

Get bean validation message from spring batch

I'm using Spring Batch and beanValidationItemProcessor() as defined in the documentation.
#Bean
#StepScope
public BeanValidatingItemProcessor<VendorDTO> beanValidatingItemProcessor() throws Exception {
BeanValidatingItemProcessor<VendorDTO> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
beanValidatingItemProcessor.setFilter(false);
return beanValidatingItemProcessor;
}
When a validation occurs a org.springframework.batch.item.validator.ValidationException is thrown and I'm able to see my field error like so.
Field error in object 'item' on field 'peid': rejected value []; codes
[Size.item.peid,Size.peid,Size.java.lang.String,Size]; arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [item.peid,peid]; arguments []; default message [peid],12,1];
default message [size must be between 1 and 12]
How do I get a simple message object resolving the field id and default message?
I've found I've been able to gain access to the field errors by casting the ValidationException getCause() to BindException where I then have access to the field errors.
#OnSkipInProcess
public void logSkippedEmail(VendorDTO vendorDTO, Throwable t) {
JobExecution jobExecution = stepExecution.getJobExecution();
if (t instanceof ValidationException) {
ValidationException e = (ValidationException) t;
if(e.getCause() instanceof BindException) {
BindException bindException = (BindException) e.getCause();
List<FieldError> fieldErrors = bindException.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
BatchValidation batchValidation = new BatchValidation();
batchValidation.setDataField(fieldError.getField());
batchValidation.setRejectedValue(String.valueOf(fieldError.getRejectedValue()));
batchValidation.setValidationMessage(fieldError.getDefaultMessage());
batchValidation.setJobInstanceId(jobExecution.getJobId());
batchValidation.setJobName(jobExecution.getJobInstance().getJobName());
batchValidationRepository.save(batchValidation);
}
}
}
}

How can I make sure exceptions during parsing lead to the same kind of response as the (custom) response returned for validation failures?

I'm using Spring to create an API, but I'm having some trouble introducing custom error reporting on (a part of) the validation of the request body.
When parsing/validation errors occur, I want to give a custom response back to the user.
This works well for fields annotated with #Valid along with validators like #javax.validation.constraints.NotNull by using a custom ResponseEntityExceptionHandler annotated with #ControllerAdvice.
It does not work however if an Exception is thrown while parsing the request body (before the validations even run). In that case I get an html error page with status 500 (Server Error)
How can I make sure the exceptions during parsing lead to the same kind of response as the (custom) one I return for validation failures?
My endpoint's code looks like this:
#RequestMapping(value= "/endpoint"
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(#Valid #RequestBody MyRequestBody requestData){
// ...
}
MyRequestBody class looks like this:
#Validated
public class MyRequestData {
#JsonProperty("stringValue")
private String stringValue = null;
#NotNull
#Valid
public String getStringValue() {
return stringValue;
}
// ...
public enum EnumValueEnum {
VALUE_1("value 1"),
VALUE_1("value 2");
private String value;
EnumValueEnum(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static EnumValueEnum fromValue(String text) {
if(text == null){
return null;
}
for (EnumValueEnum b : EnumValueEnum.values()){
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
}
}
#JsonProperty("enumValue")
private EnumValueEnum enumValue = null;
}
The custom validation error handling (and reporting) looks like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
}
}
In this code, if a user sends a request with an enum value that doesn't exist, an HttpMessageNotReadableException is thrown. I would like to catch that somewhere and replace it with a custom response that is consistent with the other exception handling I do. Where/How can I do that?
I found a solution to my own problem.
You can actually use Spring MVC's normal exception handling:
Annotating a method with #ExceptionHandler will make Spring try to use it for exception handling for the exception type specified (in the annotation's value field or the method's argument). This method can be placed in the controller or even in the ResponseEntityExceptionHandler I use for the other validation response handling.
#ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}
Mind which type of exception you handle:
The catch here was that the exception thrown while parsing is wrapped in (some subtype of) a JsonMappingException which in turn is wrapped again in a HttpMessageConversionException.
e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception
The #ExceptionHandler should therefor accept HttpMessageConversionException instead of the originally thrown exception (which in my case was HttpMessageNotReadableException)
It will not work if you write an #ExceptionHandler that only accepts your original Exception!

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Spring-MVC using a Converter to load object from path variable, but need to return 404 for unfound

TL;DR - Is there a way to throw an error from a registered type converter during the MVC databinding phase such that it will return a response with a specific HTTP status code? I.e. if my converter can't find an object from the conversion source, can I return a 404?
I have a POJO:
public class Goofball {
private String id = "new";
// others
public String getName () { ... }
public void setName (String name) { ... }
}
and am using a StringToGoofballConverter to create an empty object when "new".equals(id) or try to load a Goofball from the database if it exists:
public Goofball convert(String idOrNew) {
Goofball result = null;
log.debug("Trying to convert " + idOrNew + " to Goofball");
if ("new".equalsIgnoreCase(idOrNew))
{
result = new Goofball ();
result.setId("new");
}
else
{
try
{
result = this.repository.findOne(idOrNew);
}
catch (Throwable ex)
{
log.error (ex);
}
if (result == null)
{
throw new GoofballNotFoundException(idOrNew);
}
}
return result;
}
That converter is used by spring when the request matches this endpoint:
#RequestMapping(value = "/admin/goofballs/{goofball}", method=RequestMethod.POST)
public String createOrEditGoofball (#ModelAttribute("goofball") #Valid Goofball object, BindingResult result, Model model) {
// ... handle the post and save the goofball if there were no binding errors, then return the template string name
}
This all works quite well insofar as GET requests to /admin/goofballs/new and /admin/goofballs/1234 work smoothly in the controller for both creating new objects and editing existing ones. The hitch is that if I issue a request with a bogus id, one that isn't new and also doesn't exist in the database I want to return a 404. Currently the Converter is throwing a custom exception:
#ResponseStatus(value= HttpStatus.NOT_FOUND, reason="Goofball Not Found") //404
public class GoofballNotFoundException extends RuntimeException {
private static final long serialVersionUID = 422445187706673678L;
public GoofballNotFoundException(String id){
super("GoofballNotFoundException with id=" + id);
}
}
but I started with a simple IllegalArgumentException as recommended in the Spring docs. In either case, the result is that Spring is returning a response with an HTTP status of 400.
This makes me think I'm misusing the Converter interface but that approach appears to be recommended by the #ModelAttribute docs.
So, again the question: is there a way to throw an error from a registered type converter during the databinding phase such that it will return a response with a specific HTTP status code?
Answering my own question:
Change StringToGoofballConverter to simply return null for the unfound entity instead of throwing IllegalArgumentException or a custom exception. The #Controller method will then be given a Goofball object that has a null id (e.g. the id is not "new" nor the path element value). At that point I can throw a GoofballNotFoundException or any other #ResponseStatus exception from there, within the controller method to affect the response status code.

Spring Boot Rest Controller how to return different HTTP status codes?

I am using Spring Boot for a simple REST API and would like to return a correct HTTP statuscode if something fails.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
#ResponseBody
#ResponseStatus( HttpStatus.OK )
public RestModel create(#RequestBody String data) {
// code ommitted..
// how do i return a correct status code if something fails?
}
Being new to Spring and Spring Boot, the basic question is how do i return different status codes when something is ok or fails?
There are several options you can use. Quite good way is to use exceptions and class for handling called #ControllerAdvice:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
Also you can pass HttpServletResponse to controller method and just set response code:
public RestModel create(#RequestBody String data, HttpServletResponse response) {
// response committed...
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
Please refer to the this great blog post for details: Exception Handling in Spring MVC
NOTE
In Spring MVC using #ResponseBody annotation is redundant - it's already included in #RestController annotation.
One of the way to do this is you can use ResponseEntity as a return object.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
if(everything_fine) {
return new ResponseEntity<>(RestModel, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A nice way is to use Spring's ResponseStatusException
Rather than returning a ResponseEntityor similar you simply throw the ResponseStatusException from the controller with an HttpStatus and cause, for example:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cause description here");
This results in a response to the client containing the HTTP status:
{
"timestamp": "2020-07-09T04:43:04.695+0000",
"status": 400,
"error": "Bad Request",
"message": "Cause description here",
"path": "/test-api/v1/search"
}
Note: HttpStatus provides many different status codes for your convenience.
In case you want to return a custom defined status code, you can use the ResponseEntity as here:
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
int customHttpStatusValue = 499;
Foo foo = bar();
return ResponseEntity.status(customHttpStatusValue).body(foo);
}
The CustomHttpStatusValue could be any integer within or outside of standard HTTP Status Codes.
Try this code:
#RequestMapping(value = "/validate", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ErrorBean> validateUser(#QueryParam("jsonInput") final String jsonInput) {
int numberHTTPDesired = 400;
ErrorBean responseBean = new ErrorBean();
responseBean.setError("ERROR");
responseBean.setMensaje("Error in validation!");
return new ResponseEntity<ErrorBean>(responseBean, HttpStatus.valueOf(numberHTTPDesired));
}
There are different ways to return status code,
1 : RestController class should extends BaseRest class, in BaseRest class we can handle exception and return expected error codes.
for example :
#RestController
#RequestMapping
class RestController extends BaseRest{
}
#ControllerAdvice
public class BaseRest {
#ExceptionHandler({Exception.class,...})
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorModel genericError(HttpServletRequest request,
HttpServletResponse response, Exception exception) {
ErrorModel error = new ErrorModel();
resource.addError("error code", exception.getLocalizedMessage());
return error;
}
I think the easiest way is to make return type of your method as
ResponseEntity<WHATEVER YOU WANT TO RETURN>
and for sending any status code, just add return statement as
return ResponseEntity.status(HTTP STATUS).build();
For example, if you want to return a list of books,
public ResponseEntity<List<books>> getBooks(){
List<books> list = this.bookService.getAllBooks();
if(list.size() <= 0)
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
else
return ResponseEntity.of(Optional.of(list));
}

Resources