I have a RESTFul API consuming/returning JSON in the request/response body. When the client sends invalid data (valid JSON but invalid values for the fields) I want to be able to return a JSON structure (as well as the relevant 400+ code).
This structure would then allow the frontend to parse the errors on a per-field basis and render the errors alongside the input fields.
E.g. ideal output:
{
"errors":{
"name":["invalid chars","too long","etc"]
"otherfield":["etc"]
}
}
I am using Resteasy for the API, and using violation exceptions it's fairly easy to get it to render JSON errors:
@Provider
@Component
public class ValidationExceptionHandler implements ExceptionMapper<ResteasyViolationException> {
public Response toResponse(ResteasyViolationException exception) {
Multimap<String,String> errors = ArrayListMultimap.create();
Consumer<ResteasyConstraintViolation> consumer = (violation) -> {
errors.put(violation.getPath(), violation.getMessage());
};
exception.getParameterViolations().forEach(consumer);
Map<String, Map<String, Collection<String>>> top = new HashMap<>();
top.put("errors", errors.asMap());
return Response.status(Status.BAD_REQUEST).entity(top)
.build();
}
}
However, the error paths (violation.getPath()
) are property-centric rather than XmlElement-name-centric.
E.g. the above outputs:
{
"errors":{"createCampaign.arg1.name":["invalid chars","etc"]}
}
I have tried stripping the index back from the last dot to get "name" but there are other issues with that hack.
E.g. if my "name" property isn't "name" it doesn't work:
@XmlElement(name="name")
@NotNull
private String somethingelse;
"somethingelse" will be returned to client, but they client has no idea what that is:
{
"errors":{"somethingelse":["cannot be null"]}
}
The client wants "name" since that is what the field was called when they sent it.
My resource:
package com.foo.api;
import org.springframework.stereotype.Service;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.foo.dto.CarDTO;
@Service
@Path("/car")
public class CarResource {
@POST
@Produces({MediaType.APPLICATION_JSON})
@Consumes(MediaType.APPLICATION_JSON)
public CarDTO create(
@Valid CarDTO car
) {
//do some persistence
return car;
}
}
example dto:
package com.foo.dto;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.xml.bind.annotation.XmlElement;
public class CarDTO {
@Min(1)
@Max(10)
@NotNull
@XmlElement(name="gears")
private int cogs;
}
This article describes quite well what you need to do.
Basically you should implement an ExceptionMapper.
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
@Override
public Response toResponse(ValidationException exception) {
Response myResponse;
// build your Response based on the data provided by the exception
return myResponse;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With