Ho un API RESTFul che consuma/restituisce JSON nel corpo della richiesta/risposta. Quando il client invia dati non validi (JSON valido ma valori non validi per i campi), desidero poter restituire una struttura JSON (oltre al codice 400+ pertinente).In Java, come posso restituire errori formattati JSON significativi dalla convalida Resteasy?
Questa struttura consentirebbe quindi al frontend di analizzare gli errori per ogni campo e rendere gli errori accanto ai campi di input.
E.g. Uscita ideale:
{
"errors":{
"name":["invalid chars","too long","etc"]
"otherfield":["etc"]
}
}
Sto usando Resteasy per l'API, e usare le eccezioni di violazione è abbastanza facile per farlo rendere errori JSON:
@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();
}
}
Tuttavia, i percorsi di errore (violation.getPath()
) sono proprietà -centrico piuttosto che XmlElement-name-centric.
E.g. le uscite di cui sopra:
{
"errors":{"createCampaign.arg1.name":["invalid chars","etc"]}
}
ho cercato di strippaggio l'indice di ritorno da l'ultimo punto per ottenere "nome", ma ci sono altri problemi con questo hack.
E.g. se il mio "nome" la proprietà non è "nome" non funziona:
@XmlElement(name="name")
@NotNull
private String somethingelse;
"somethingelse" sarà restituito al cliente, ma cliente non ha idea di cosa si tratta:
{
"errors":{"somethingelse":["cannot be null"]}
}
Il client vuole "nome" poiché è quello che il campo è stato chiamato quando lo hanno inviato.
mia risorsa:
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;
}
}
esempio 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;
}