Mam RESTFul API spożywający/zwracający JSON w treści żądania/odpowiedzi. Kiedy klient wysyła nieprawidłowe dane (poprawny JSON, ale nieprawidłowe wartości dla pól), chcę mieć możliwość zwrócenia struktury JSON (a także odpowiedniego kodu 400+).W języku Java, w jaki sposób zwracać znaczące, sformatowane błędy JSON z walidacji Resteasy?
Ta struktura umożliwiłaby następnie frontendowi analizę błędów w poszczególnych polach i renderowanie błędów obok pól wejściowych.
E.g. idealne wyjście:
{
"errors":{
"name":["invalid chars","too long","etc"]
"otherfield":["etc"]
}
}
Używam Resteasy dla API i używając wyjątki naruszeń to dość łatwo dostać się to uczynić błędy 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();
}
}
Jednak ścieżki błędach (violation.getPath()
) stanowią własność -centryczne zamiast XmlElement-name-centric.
E.g. Powyższe wyjścia:
{
"errors":{"createCampaign.arg1.name":["invalid chars","etc"]}
}
próbowałem stripping indeks wstecz od ostatniego punktu, aby uzyskać „nazwa”, ale są też inne problemy z tym hack.
E.g. jeśli moja „nazwa” nieruchomość nie jest „nazwa” to nie działa:
@XmlElement(name="name")
@NotNull
private String somethingelse;
„somethingelse” zostaną zwrócone klientowi, ale klient nie ma pojęcia, co to jest:
{
"errors":{"somethingelse":["cannot be null"]}
}
Klient chce "nazwa", ponieważ to pole zostało wywołane podczas wysyłania.
Mój zasób:
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;
}
}
przykład 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;
}