How can I have validation errors printed on failure

做~自己de王妃 提交于 2019-12-10 20:40:16

问题


given the following dto and controller

public class PasswordCredentials implements AuthenticationProvider {

    @NotNull
    @NotEmpty
    @JsonProperty( access = JsonProperty.Access.WRITE_ONLY )
    private String user;

    @NotNull
    @NotEmpty
    @JsonProperty( access = JsonProperty.Access.WRITE_ONLY )
    private CharSequence pass;


    public void setPass( final CharSequence pass ) {
        this.pass = pass;
    }

    public void setUser( final String user ) {
        this.user = user;
    }

    @Override
    public Authentication toAuthentication() {
        return new UsernamePasswordAuthenticationToken( user, pass );
    }
}

@RestController
@RequestMapping( path = "authentication" )
class AuthenticationController {
    private final AuthenticationManager am;

    AuthenticationController( final AuthenticationManager am ) {
        this.am = am;
    }

    @RequestMapping( path = "password", method = RequestMethod.POST, consumes = {
        "!" + MediaType.APPLICATION_FORM_URLENCODED_VALUE
    } )
    ResponseEntity<?> login( @Valid @RequestBody final PasswordCredentials credentials ) {
        Authentication authenticate = am.authenticate( credentials.toAuthentication() );
        if ( authenticate.isAuthenticated() ) {
            return ResponseEntity.status( HttpStatus.NO_CONTENT ).build();
        }
        return ResponseEntity.status( HttpStatus.FORBIDDEN ).build();
    }

}

if for example pass is null there will be a validation error, and a 400 will happen without ever calling my controller, which is fine. That 400 however has no content, is there any way to have the controllers BindResults output as content so that the consumer of the API knows what caused the problem? Ideally I would not do this in the controller method, so that it would happen on all controllers?

I was able to get this behavior with spring data rest as follows, but I'd like it for all API controllers.

class RestConfig extends RepositoryRestConfigurerAdapter {

    @Bean
    Validator validator() {
        return new LocalValidatorFactoryBean();
    }


    @Override
    public void configureValidatingRepositoryEventListener(
            final ValidatingRepositoryEventListener validatingListener ) {
        Validator validator = validator();
        //bean validation always before save and create
        validatingListener.addValidator( "beforeCreate", validator );
        validatingListener.addValidator( "beforeSave", validator );
    }

    @Override
    public void configureRepositoryRestConfiguration( final RepositoryRestConfiguration config ) {
        config.setBasePath( "/v0" );
        config.setReturnBodyOnCreate( false );
        config.setReturnBodyOnUpdate( false );
    }

回答1:


Spring have @ControllerAdvice and @ExceptionHandler annotation to handle errors in controllers.

@ControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Error processValidationError(MethodArgumentNotValidException ex) {
         BindingResult result = ex.getBindingResult();
        .....
        return new Error();
    }

    // Other exceptions
}



回答2:


i want to improve the answer of Anton Novopashin: just return the error in response entity.

@ControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseEntity<String> processValidationError(MethodArgumentNotValidException ex) {
        return new ResponseEntity(ex.getMessage, HttpStatus.BAD_REQUEST);
    }

    // Other exceptions
}



回答3:


I'm not sure who or why downvoted the existing answers but they are both right - the best way to handle validation errors would be to declare a @ControllerAdvice and then handle the exceptions there. Here's a snippet of my global error handler, taken from an existing project:

@ControllerAdvice
@ResponseBody
public class RestfulErrorHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResponse methodValidationError(MethodArgumentNotValidException e) {
        final ErrorResponse response = new ErrorResponse();
        for (ObjectError error : e.getBindingResult().getAllErrors()) {
            if (error instanceof FieldError) {
                response.addFieldError((FieldError) error);
            } else {
                response.addGeneralError(error.getDefaultMessage());
            }
        }
        return response;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public ErrorResponse constraintViolationError(ConstraintViolationException e) {
        final ErrorResponse response = new ErrorResponse();
        for (ConstraintViolation<?> v : e.getConstraintViolations()) {
            response.addFieldError(v.getPropertyPath(), v.getMessage());
        }

        return response;
    }
}

You should also process ConstraintViolationExceptions since they too may be thrown. Here's a link to my ErrorResponse class, I'm including it as a Gist so as not to obscure the main point.

You should also probably process the RepositoryConstraintViolationException, I'm not sure if spring-data-rest handles them already.



来源:https://stackoverflow.com/questions/39166509/how-can-i-have-validation-errors-printed-on-failure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!