I have a RESTFul API that uses / returns JSON in the body of a request / response. When a client sends invalid data (valid JSON, but invalid values ββfor fields), I want to be able to return a JSON structure (as well as the corresponding 400 + code).
This structure will then allow the interface to analyze errors based on each field and display errors along with input fields.
eg. ideal output:
{ "errors":{ "name":["invalid chars","too long","etc"] "otherfield":["etc"] } }
I use Resteasy for the API and using exceptions for exceptions, it's pretty easy to get it to handle 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, error paths ( violation.getPath() ) have a centricity property, not an XmlElement-name-centric property.
eg. outputs:
{ "errors":{"createCampaign.arg1.name":["invalid chars","etc"]} }
I tried to remove the index from the last point to get the "name", but there are other problems with this hack.
eg. if my "name" property is not a "name", it does not work:
@XmlElement(name="name") @NotNull private String somethingelse;
"somethingelse" will be returned to the client, but they do not know what it is:
{ "errors":{"somethingelse":["cannot be null"]} }
The client wants a "name", as this is what was called up by the field when it was sent.
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 ) {
Dto example:
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; }