Validando listas em um controlador Spring

Validando listas em um controlador Spring

1. Introdução

A validação de entradas do usuário é um requisito comum em qualquer aplicativo. Neste tutorial, examinaremos as maneiras devalidate a List of objects as a parameter to a Spring controller.

Vamos adicionar validação na camada do controlador para garantir que os dados especificados pelo usuário satisfaçam as condições especificadas.

2. Adicionando restrições a um bean

Para nosso exemplo, usaremos um controlador Spring simples que gerencia um banco de dados de filmes. Vamos nos concentrar em um método que aceita uma lista de filmes e os adiciona ao banco de dados após realizar validações na lista.

Então, vamos começar poradding constraints on the Movie bean usandojavax validation:

public class Movie {

    private String id;

    @NotEmpty(message = "Movie name cannot be empty.")
    private String name;

    // standard setters and getters
}

3. Adicionando anotações de validação no controlador

Vamos dar uma olhada em nosso controlador. Primeiro, vamosadd the @Validated annotation to the controller class:

@Validated
@RestController
@RequestMapping("/movies")
public class MovieController {

    @Autowired
    private MovieService movieService;

    //...
}

A seguir, vamos escrever o método do controlador onde validaremos a lista de objetosMovie passados.

Vamosadd the @NotEmpty annotation to our list of movies para validar que deve haver pelo menos um elemento na lista. Ao mesmo tempo, adicionaremos a anotação@Valid para garantir que os próprios objetosMovie sejam válidos:

@PostMapping
public void addAll(
  @RequestBody
  @NotEmpty(message = "Input movie list cannot be empty.")
  List<@Valid Movie> movies) {
    movieService.addAll(movies);
}

Se chamarmos o método do controlador com uma entrada de listaMovie vazia, a validação falhará por causa da anotação@NotEmpty, e veremos a mensagem:

Input movie list cannot be empty.

A anotação@Valid garantirá que as restrições especificadas na classeMovie sejam avaliadas para cada objeto na lista. Portanto, se passarmosMovie com um nome vazio na lista, a validação falhará com a mensagem:

Movie name cannot be empty.

4. Validadores personalizados

Também podemos adicionarcustom constraint validators à lista de entrada.

Para o nosso exemplo, a restrição personalizada validará a condição de que o tamanho da lista de entrada seja restrito a um máximo de quatro elementos. Vamos criar esta anotação de restrição personalizada:

@Constraint(validatedBy = MaxSizeConstraintValidator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MaxSizeConstraint {
    String message() default "The input list cannot contain more than 4 movies.";
    Class[] groups() default {};
    Class[] payload() default {};
}

Agora, vamos criar um validador que aplicará a restrição acima:

public class MaxSizeConstraintValidator implements ConstraintValidator> {
    @Override
    public boolean isValid(List values, ConstraintValidatorContext context) {
        return values.size() <= 4
    }
}

Finalmente, vamos adicionar a anotação@MaxSizeConstraint ao nosso método de controle:

@PostMapping
public void addAll(
  @RequestBody
  @NotEmpty(message = "Input movie list cannot be empty.")
  @MaxSizeConstraint
  List<@Valid Movie> movies) {
    movieService.addAll(movies);
}

Aqui,@MaxSizeConstraint validará o tamanho da entrada. Portanto, se passarmos mais de quatro objetosMovie na lista de entrada, a validação falhará.

5. Manipulando a exceção

Se alguma das validações falhar,ConstraintViolationException é lançado. Agora, vamos ver como podemos adicionar um componenteexception handling para capturar essa exceção.

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity handle(ConstraintViolationException constraintViolationException) {
    Set> violations = constraintViolationException.getConstraintViolations();
    String errorMessage = "";
    if (!violations.isEmpty()) {
        StringBuilder builder = new StringBuilder();
        violations.forEach(violation -> builder.append(" " + violation.getMessage()));
        errorMessage = builder.toString();
    } else {
        errorMessage = "ConstraintViolationException occured.";
    }
    return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
 }

6. Testando a API

Agora, vamos testar nosso controlador com entradas válidas e inválidas.

Em primeiro lugar, vamos fornecer dados válidos para a API:

curl -v -d [{"name":"Movie1"}] -H "Content-Type: application/json" -X POST http://localhost:8080/movies

Nesse cenário, obteremos uma resposta HTTP status 200:

...
HTTP/1.1 200
...

A seguir, verificaremos nossa resposta da API quando passarmos entradas inválidas.

Vamos tentar uma lista vazia:

curl -d [] -H "Content-Type: application/json" -X POST http://localhost:8080/movies

Neste cenário, obteremos uma resposta HTTP status 400. Isso ocorre porque a entrada não satisfaz a restrição@NotEmpty.

Input movie list cannot be empty.

A seguir, vamos tentar passar cinco objetosMovie na lista:

curl -d [{"name":"Movie1"},{"name":"Movie2"},{"name":"Movie3"},{"name":"Movie4"},{"name":"Movie5"}]
  -H "Content-Type: application/json" -X POST http://localhost:8080/movies

Isso também resultará em uma resposta de status HTTP 400 porque falhamos na restrição@MaxSizeConstraint:

The input list cannot contain more than 4 movies.

7. Conclusão

Neste artigo rápido, aprendemos como validar uma lista de objetos no Spring.

Como sempre, o código-fonte completo dos exemplos éover on GitHub.