Spring list validation - get field from invalid object - java

I'm trying to understand if it's possible to get the index of invalids objects inside a list that is validated with #Valid.
I already have validation in place where I send a response like this
{
"status": "BAD_REQUEST",
"message": "Validation Error",
"detailedMessage": "There are errors in the entered data",
"timestamp": 1657896844868,
"validationErrors": [
{
"field": "items[0].name",
"message": "The name is mandatory"
},
{
"field": "items[1].surname",
"message": "The surname is mandatory"
}
]
}
The problem is that in the frontend I need to know exactly which objects in the array "items" have problems so I can highlight the corret input. What I'm doing right now is getting the index from the string "items[0].name" using a regex but I really dislike this kind of behavior and I would like to exctract the index of the invalid item and put it in the response.
Ideally I would like to not have the array index but a specific field of the invalid object.
What I mean is that every item has an "id" field and I would like to extract that one and send something like this in response
{
"status": "BAD_REQUEST",
"message": "Validation Error",
"detailedMessage": "There are errors in the entered data",
"timestamp": 1657896844868,
"validationErrors": [
{
"field": "items[12345].name",
"message": "The name is mandatory",
"itemId": 12345
},
{
"field": "items[12346].surname",
"message": "The surname is mandatory",
"itemId": 12346
}
]
}
In this way I would be able to know exactly which object is invalid in the frontend without having to rely on array indexes or regex to extract the index from a string. Of course having the array index as "itemIndex" field would also be better than what I have right now.
Following you can find my request class, that is used in the controller as #Valid #RequestBody, and my ControllerAdvice where I build my response when a MethodArgumentNotValidException happens.
Request
public class Request {
#Valid
#NotEmpty(message = "You must insert at least one item")
private List<#Valid #NotNull Item> items;
}
ControllerAdvice
#RestControllerAdvice
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST, "Validation Error", "There are errors in the entered data");
List<ValidationError> errors = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
ValidationError validationError = new ValidationError(fieldName, errorMessage);
errors.add(validationError);
});
errorResponse.setValidationErrors(errors);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}

You can access information about objects that violated the constraint by unwrapping your ObjectError or FieldError to ConstraintViolation (https://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html).
Here is how you can access array index
Path nodes = error.unwrap(ConstraintViolation.class).getPropertyPath();
for (Path.Node node : nodes) {
if (node.isInIterable()) {
node.getIndex() // for items[12346].surname it will return 123456
}
}
Accessing an element that has thrown an error is also possible using unwrap to ConstraintViolation.
Item item = (Item) error.unwrap(ConstraintViolation.class).getLeafBean(); // don't use cast with no checks in your code
This will return an item which triggered constraint violation.

Related

Why its showing required request body in output

public String deleteProduct(#RequestBody String prodId ,HttpServletRequest request ) throws NotLoggedInException {
String userName = (String) request.getSession().getAttribute("user");
System.out.println(userName);
if (userName == null) {
throw new NotLoggedInException("You have not logged in");
}
String userRole = (String) request.getSession().getAttribute("role");
if (!userRole.equalsIgnoreCase("productmaster")) {
throw new AuthorizedUserRoleNotFoundException("you are not authorized to add the products");
}
if(pservice.deleteProduct(prodId))
{
return "Product deleted";
}
return "Product not deleted";
}
Output:
{
"timestamp": "2022-11-20T13:17:24.172+0000",
"status": 400,
"error": "Bad Request",
"message": "Required request body is missing: public java.lang.String"
}
Please tell someone why its showing like this
#Requestbody annotation requires you to pass some request body in json, map form. But you are just passing prodId. I think you should just change #RequestBody annotation.
(#Request Param prodId ,#Requestbody HHttpServletReques request)
The #RequestBody annotation comes with the required attribute defaulting to true. This means that the request should always contain a body, otherwise it will throw an exception. From the error message it appears that your request does not contain a body.
You need to either set the required attribute to false or always provide a body.

Adding additional field in Response Object

I am getting below response when I am calling an API.
Response postRequestResponse = ConnectionUtil.getwebTarget()
.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true)
.path("bots")
.path(ReadSkillID.readSkillId())
.path("dynamicEntities").path(dynamicEntityID)
.path("pushRequests").path(pushRequestID).path(operation)
.request()
.header("Authorization", "Bearer " + ConnectionUtil.getToken())
.get();
Below output I am getting.
{
"createdOn": "2020-08-17T12:19:13.541Z",
"updatedOn": "2020-08-17T12:19:23.421Z",
"id": "C84B058A-C8F9-41F5-A353-EC2CFE7A1BD9",
"status": "TRAINING",
"statusMessage": "Request Pushed into training, on user request"
}
I have to return this output to client with an additional field in the response. How can modify the above response and make it
{
"EntityName": "NewEntity", //New field
"createdOn": "2020-08-17T12:19:13.541Z",
"updatedOn": "2020-08-17T12:19:23.421Z",
"id": "C84B058A-C8F9-41F5-A353-EC2CFE7A1BD9",
"status": "TRAINING",
"statusMessage": "Request Pushed into training, on user request"
}
I am adding this additional field here
"EntityName": "NewEntity"
How can I do that. many things I tried but got exception.
get JSON from postRequestResponse (i have no idea what framework you are using, so you have to figer it out on your own, but the Response datatype will probably have a getResponseBody or similar method returing the JSON)
add EntityName
serialize it again to json.
class YourBean {
#Autowired
private ObjectMapper objectMapper;
public void yourMethod() {
// 1
final InputStream jsonFromResponse = ...
// 2
Map dataFromResponse = objectMapper.readValue(jsonFromResponse, Map.class);
dataFromResponse.put("EntityName", "NewEntity");
// 3
final String enrichedJson = objectMapper.writeValueAsString(dataFromResponse);
}
}
enrichedJson contains EntityName and whatever comes from the API.

Bean Validation + JAX-RS not reading custom ValidationMessages.properties

I am trying to use custom messages with Bean Validation but my custom messages are not being returned by the JAX-RS resource. What am I doing wrong?
ValidationMessages.properties
invoice.value.notnull=Invoice value must be informed.
The file is located at src/main/resources
InvoiceResource.java
#Path("/invoice")
public class InvoiceResource {
#POST
public void post(#Valid InvoiceRequest request) {
/* stuff */
}
}
InvoiceRequest.java
public class InvoiceRequest {
#NotNull(message = "invoice.value.notnull")
private Double value;
}
Found out that the problem was my declaration of the message in the bean param. The message ID must be forked between braces "{ ... }":
#NotNull(message = "{invoice.value.notnull}")
How we get the proper JSON response with the correct message:
{
"exception": null,
"fieldViolations": [],
"propertyViolations": [],
"classViolations": [],
"parameterViolations": [
{
"constraintType": "PARAMETER",
"path": "post.arg0.value",
"message": "Invoice value must be informed",
"value": ""
}
],
"returnValueViolations": []
}

Exception handler doesn't use the value in #ResponseStatus

I have the following controller advice to handle the exceptions within my app globally:
#ControllerAdvice
public class ExceptionHandlingController {
// Convert a predefined exception to an HTTP Status code
#ResponseStatus(value=HttpStatus.BAD_REQUEST) // 400
#ExceptionHandler(ConstraintViolationException.class)
public void ConstraintViolationExceptionHandler() {
//Nothing to do
}
}
The code below is the controller, which tries to save an object to the db (in the service layer). The class that object belongs to, has annotations that fail.
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void create(#RequestBody CustomUserDetails user, HttpServletResponse response) {
logger.debug("User signup attempt with username: " + username);
userDetailsServices.saveIfNotExists(user);
}
I expect the client to receive a 400 response when ConstraintViolationException is thrown.
When the method returns void , no response is returned. When I change it String and return a random text, I get 404 response back.
{
"timestamp": 1495489172770,
"status": 404,
"error": "Not Found",
"exception": "javax.validation.ConstraintViolationException",
"message": "Validation failed for classes [security.model.CustomUserDetails] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='must match \"^(?!.*\\..*\\..*)[A-Za-z]([A-Za-z0-9.]*[A-Za-z0-9]){8,15}$\"', propertyPath=username, rootBeanClass=class com.boot.cut_costs.security.model.CustomUserDetails, messageTemplate='{javax.validation.constraints.Pattern.message}'}\n]",
"path": "/signup"
}
How can I make this return a simple BAD REQUEST message as it is defined for the #ExceptionHandler.
Note: ConstraintViolationExceptionHandler is hit!

proper way to handle dynamic responses by retrofit 2

let's say I've got a REST API which I could get list of books by invoking following retrofit 2 request.
public interface AllRecordsFromRequestInterface {
#GET("books/all")
Call<List<TrackInfo>> operation(#Header("Authorization") String authentication_token);
}
and API response:
[
{
"id": "1",
"title": "The Catcher in the Rye",
"author":"J. D. Salinger"
},
{
"id": "2",
"title": "The Great Gatsby",
"author":"F. Scott Fitzgerald"
}
]
I use GsonConverterFactory to convert json to a Model. here is my model class
public class Book{
private int id;
private String title;
private String author;
}
I'm using a authentication token to authorize myself to API as it can be seen in my request. some times other response are received rather than above response because of token expiration or something else. for example:
{
"status": "error",
"message": "Expired token"
}
what is the proper way to handle dynamic responses (with known structure) in retrofit 2?
you have multiple choices:
1-change your API:(this one is standard)
change it like this for every response and if the user failed with authentication leave the result null or if authentication was successful put the list in the result.
{
"status" : "error/success"
"message" : ...
"result" : ....
}
2- you can give Object type to retrofit and after the response was successful you can cast it to one of your models, using "instance of" syntax.
public interface AllRecordsFromRequestInterface {
#GET("books/all")
Call<Object> operation(#Header("Authorization") String authentication_token);
}

Categories

Resources