1

Ich erstelle eine Anwendung mit Spring Boot, Spring Daten REST, Spring HATEOAS, Hibernate, Spring Validierung.Leere Nachrichten mit Validierungsfehlern in Spring Data REST

Ich habe meine eigene Validierung zur Unterstützung von SpEL nach this guide erstellt.

So habe ich meine Validator:

public class SpELClassValidator implements ConstraintValidator<ValidateClassExpression, Object> { 
    private Logger log = LogManager.getLogger(); 

    private ValidateClassExpression annotation; 
    private ExpressionParser parser = new SpelExpressionParser(); 

    public void initialize(ValidateClassExpression constraintAnnotation) { 
     annotation = constraintAnnotation; 
     parser.parseExpression(constraintAnnotation.value()); 
    } 

    public boolean isValid(Object value, ConstraintValidatorContext context) { 
     try {   
      StandardEvaluationContext spelContext = new StandardEvaluationContext(value); 
      return (Boolean) parser.parseExpression(annotation.value()).getValue(spelContext); 
     } catch (Exception e) { 
      log.error("", e); 
      return false; 
     } 

    } 
} 

und meine Anmerkung:

@Target({ java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE }) 
@Retention(RetentionPolicy.RUNTIME) 
@Constraint(validatedBy = { SpELClassValidator.class }) 
@Documented 
@Repeatable(ValidateClassExpressions.class) 
public @interface ValidateClassExpression { 

    String message() default "{expression.validation.message}"; 

    Class<?>[] groups() default {}; 

    Class<? extends Payload>[] payload() default {}; 

    String value(); 

} 

Konfiguration des Prüfers:

@Bean 
public MessageSource messageSource() { 
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 
    messageSource.setBasenames("classpath:/i18n/messages"); 
    // messageSource.setDefaultEncoding("UTF-8"); 
    // set to true only for debugging 
    messageSource.setUseCodeAsDefaultMessage(false); 
    messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1)); 
    messageSource.setFallbackToSystemLocale(false); 
    return messageSource; 
} 

/** 
* Enable Spring bean validation 
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation 
* 
* @return 
*/ 
@Bean 
public LocalValidatorFactoryBean validator() { 
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); 
    factoryBean.setValidationMessageSource(messageSource()); 
    return factoryBean; 
} 

@Bean 
public MethodValidationPostProcessor methodValidationPostProcessor() { 
    MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor(); 
    methodValidationPostProcessor.setValidator(validator()); 
    return methodValidationPostProcessor; 
} 

..und definiert Validator für REST-Repositories:

das ist meine Bohne:

@Entity 
// Validate the number of seats if the bus is a minibus 
@ValidateClassExpression(value = "#this.isMiniBus() == true ? #this.getSeats()<=17 : true", message = "{Expression.licenseplate.validminibus}") 
public class LicensePlate extends AbstractEntity { 
    private static final long serialVersionUID = -6871697166535810224L; 

    @NotEmpty 
    @ColumnTransformer(read = "UPPER(licensePlate)", write = "UPPER(?)") 
    @Column(nullable = false, unique = true) 
    private String licensePlate; 

    // The engine euro level (3,4,5,6) 
    @Range(min = 0, max = 6) 
    @NotNull 
    @Column(nullable = false, columnDefinition = "INTEGER default 0") 
    private int engineEuroLevel = 0; 

    @NotNull(message = "{NotNull.licenseplate.enginetype}") 
    @Enumerated(EnumType.STRING) 
    @Column(nullable = false) 
    private EngineType engineType = EngineType.DIESEL; 

    // If the bus has the particulate filter 
    @NotNull(message = "{NotNull.licenseplate.particulatefilter}") 
    @Column(nullable = false, columnDefinition = "BOOLEAN default false") 
    private boolean particulateFilter = false; 

    // Number of seats 
    @NotNull 
    @Range(min = 1, max = 99) 
    @Column(nullable = false, columnDefinition = "INTEGER default 50") 
    private int seats = 50; 

    // If the vehicle is a minibus 
    @NotNull 
    @Column(nullable = false, columnDefinition = "BOOLEAN default false") 
    private boolean miniBus = false; 

    @NotNull(message = "{NotNull.licenseplate.country}") 
    // The country of the vehicle 
    @ManyToOne(fetch = FetchType.LAZY, optional = false) 
    private Country country; 

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) 
    private List<Note> notes = new ArrayList<>(); 

    public LicensePlate() { 
    } 

    public String getLicensePlate() { 
     return licensePlate; 
    } 

    public void setLicensePlate(String licensePlate) { 
     this.licensePlate = licensePlate; 
    } 

    public int getEngineEuroLevel() { 
     return engineEuroLevel; 
    } 

    public void setEngineEuroLevel(int engineEuroLevel) { 
     this.engineEuroLevel = engineEuroLevel; 
    } 

    public int getSeats() { 
     return seats; 
    } 

    public void setSeats(int seats) { 
     this.seats = seats; 
    } 

    public boolean isMiniBus() { 
     return miniBus; 
    } 

    public void setMiniBus(boolean miniBus) { 
     this.miniBus = miniBus; 
    } 

    public EngineType getEngineType() { 
     return engineType; 
    } 

    public void setEngineType(EngineType engineType) { 
     this.engineType = engineType; 
    } 

    public boolean isParticulateFilter() { 
     return particulateFilter; 
    } 

    public void setParticulateFilter(boolean particulateFilter) { 
     this.particulateFilter = particulateFilter; 
    } 

    public Country getCountry() { 
     return country; 
    } 

    public void setCountry(Country country) { 
     this.country = country; 
    } 

    @Override 
    public String toString() { 
     return "LicensePlate [licensePlate=" + licensePlate + ", engineEuroLevel=" + engineEuroLevel + ", engineType=" 
       + engineType + ", particulateFilter=" + particulateFilter + ", seats=" + seats + ", miniBus=" + miniBus 
       + "]"; 
    } 

    public List<Note> getNotes() { 
     return notes; 
    } 

    public void setNotes(List<Note> notes) { 
     this.notes = notes; 
    } 

} 

Auf Konfiguration habe ich auch diese Klasse:

@RestControllerAdvice 
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler { 

    @Override 
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, 
      HttpHeaders headers, HttpStatus status, WebRequest request) { 
     throw new RuntimeException(ex); 
    } 

    @Override 
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, 
      HttpHeaders headers, HttpStatus status, WebRequest request) { 
     throw new RuntimeException(ex); 
    } 

    @Override 
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, 
      HttpHeaders headers, HttpStatus status, WebRequest request) { 
     throw new RuntimeException(ex); 
    } 

} 

mein Repository verwenden:

@Transactional 
@RepositoryRestResource(excerptProjection = LicensePlateProjection.class) 
@PreAuthorize("isAuthenticated()") 
public interface LicensePlateRepository 
     extends PagingAndSortingRepository<LicensePlate, Long>, RevisionRepository<LicensePlate, Long, Integer> { 

    public LicensePlate findByLicensePlate(String licencePlate); 

Swagger Mit Ich bin ein tun POST dieses JSON:

{"licensePlate":"asdfg","engineEuroLevel":"4","particulateFilter":true,"seats":18,"miniBus":true,"country":"http://localhost:8080/api/v1/countries/1"} 

Weil ich die Gültigkeitsregel haben, die prüfen, ein Kleinbus weniger als 17 Sitze hat, sollte ich einen Fehler bei der Überprüfung sehen, instad Ich sehe dies:

{ 
    "errors": [] 
} 

mit einem HTTP-400-Fehler (dieser Return-Code rechts).

Ich habe darauf hinweisen, dass ich JUnit-Testfälle erstellt, und ich sehe die richtige Botschaft:

@Test 
@WithMockUser(roles = "ADMIN") 
public void validateMinibusWithMoreThan17SeatsFails() { 
    assertEquals(1, countryRepository.count()); 

    LicensePlate plate = new LicensePlate(); 
    plate.setLicensePlate("AA123BB"); 
    plate.setEngineEuroLevel(3); 
    plate.setMiniBus(true); 
    plate.setSeats(18); 
    plate.setCountry(countryRepository.findFirstByOrderByIdAsc()); 

    Set<ConstraintViolation<LicensePlate>> constraintViolations = validator.validate(plate); 
    assertEquals(1, constraintViolations.size()); 
    ConstraintViolation<LicensePlate> constraintViolation = constraintViolations.iterator().next(); 
    assertEquals("I veicoli di tipo minibus possono avere al massimo 17 posti (16 passeggeri più il conducente).", 
      constraintViolation.getMessage()); 
} 

Also ich denke, das Problem auf dem REST/MVC Teil ist. Ich debuggte die Anfrage und ich überprüfte die Klasse org.springframework.data.rest.core.RepositoryConstraintViolationException; im Konstruktor ich sehe meine Fehler sind richtig, und ich kann die Fehlermeldung und die richtige Struktur sehen:

org.springframework.data.rest.core.ValidationErrors: 1 errors 
Error in object 'LicensePlate': codes [ValidateClassExpression.LicensePlate,ValidateClassExpression]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [LicensePlate.,]; arguments []; default message [],org.springframework.valid[email protected]520b6a25]; default message [I veicoli di tipo minibus possono avere al massimo 17 posti (16 passeggeri più il conducente).] 

Ich kann nicht sehen, wo ich den Fehler mache. Mit anderen (auch) benutzerdefinierten Validatoren sehe ich die richtige Nachricht. Ich jemand auch, um mich in die richtige Richtung zu bringen, um das Problem zu lösen?

+0

Hallo!) Hast du dich registriert (https: //docs.spring.io/spring-data/rest/docs/current/referenz/html/# validation) Ihr Validator? Willst du nicht meine SDR Validierung sehen [Beispiel] (https://github.com/Cepr0/sdr-validation-demo)? .. – Cepr0

+0

@ Cepr0 Ja, tat ich. Ich habe meine Frage aktualisiert. Ich habe dein Beispiel überprüft und es scheint, dass ich dasselbe mache. – drenda

+0

Wie ich sehen kann, haben Sie nur Standard-Validator ('@Autowired private Validator validator;') registriert, aber nicht benutzerdefiniert: 'validatingListener.addValidator (" beforeSave ", neuer SpELClassValidator());'. Oder ich irre mich? .. – Cepr0

Antwort

0

Ich denke, dass Spring MVC nicht weiß, wo die Fehlermeldung angezeigt wird, da die Einschränkung der Einschränkung der Einschränkung auf Klassenebene keine bestimmte Eigenschaft angibt.

HVs @ScriptAssert bietet das Attribut reportOn() zum Angeben einer Eigenschaft zum Melden des Fehlers an.

Für Ihre benutzerdefinierte Integritätsbedingung können Sie dasselbe tun, indem Sie eine benutzerdefinierte Integritätsbedingungsverletzung und einen Eigenschaftspfad mithilfe der API erstellen, die über ConstraintValidatorContext verfügbar gemacht wird.

+0

Sie haben Recht. Ich vermutete auch ohne eine spezifische Eigenschaft Spring mvc konnte CoinstraintValidation Fehler anzeigen. BTW Ich musste Hibernate Validator auf Version> = 5.4 aktualisieren, da reportOn() nur von dieser Version verfügbar ist. – drenda

Verwandte Themen