DEV Community

guilhermegarcia86
guilhermegarcia86

Posted on • Originally published at programadev.com.br on

1

Validando requisições com Spring Boot

Erros ocorrem e sempre irão ocorrer, seja por mal requisito de negócio, seja por mal desenvolvimento ou qualquer outra razão. O fato é que erros acontecem e precisamos saber lidar com eles.

Mas como vamos apresentar o erro para o usuário ou cliente que chamam as nossas APIs?

O Spring já fornece todo um mecanismo para tratativa de erros que é muito válido e auxilia, e muito, o desenvolvimento mas aqui eu irei apresentar uma outra forma de realizar essa tratativa através de uma lib que descobri recentemente que facilita ainda mais o trabalho e padronização de erros e mensagens na resposta.

Projeto

Seguindo com os conteúdos dos artigos anteriores vamos reaproveitar o projeto que já foi usado pra Criar um endpoint com Spring Security e token JWT e vamos adicionar nele a validação.

Este projeto também está sendo usado nos artigos sobre o Kafka aqui do blog e nessa aplicação temos como premissa que seja enviado os dados de um contribuinte como nome, documento e email.

No nosso projeto hoje não há validação alguma se os dados enviados estão corretos, para esse exemplo queremos que o número do documento (CPF), seja um número válido e caso não seja deve retornar uma mensagem indicando isso.

Para isso iremos usar uma lib muito interessante que ajuda muito no desenvolvimento com Spring Boot , a Errors Spring Boot Starter é um projeto que visa facilitar ainda mais a manipulação de erros e validação de dados de entrada.

Adicionado ao Maven

O projeto register é um projeto Java que utiliza o Maven então basta adicionar a dependência no pom.xml:

<dependency>
    <groupId>me.alidg</groupId>
    <artifactId>errors-spring-boot-starter</artifactId>
    <version>1.4.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

E para auxiliar nas validações também incluir a dependência do spring-boot-starter-validation:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Criando annotation para validação

O nosso objeto de entrada no POST contém um campo documento que será onde usuário irá enviar o CPF:

{
    "name": "User",
    "document": "XXX.XXX.XXX-XX",
    "email": "email@fake.com"
}
Enter fullscreen mode Exit fullscreen mode

E a ideia seria que no momento do POST fosse realizada a validação e caso não seja um CPF válido retorna um erro informando isso.

Para isso será criado uma Annotation @Cpf que nos auxiliará:

@Documented
@Constraint(validatedBy = CpfValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Cpf {

    String message() default "Documento Inválido";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

}
Enter fullscreen mode Exit fullscreen mode

Na annotation @Cpf é definida a mensagem que será devolvida para o usuário e também adicionamos @Constraint(validatedBy = CpfValidator.class) que é classe que contém a lógica para executar a validação.

É necessário criar a classe CpfValidator que contém a regra de validação:

public class CpfValidator implements ConstraintValidator<Cpf, String>{

    private final int[] PESO_CPF = { 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 };

    @Override
    public boolean isValid(String cpf, ConstraintValidatorContext context) {

        String cpfSomenteDigitos = cpf.replaceAll("\\D", "");

        if ((cpfSomenteDigitos == null) || (cpfSomenteDigitos.length() != 11) || cpfSomenteDigitos.equals("00000000000")
                || cpfSomenteDigitos.equals("11111111111") || cpfSomenteDigitos.equals("22222222222")
                || cpfSomenteDigitos.equals("33333333333") || cpfSomenteDigitos.equals("44444444444")
                || cpfSomenteDigitos.equals("55555555555") || cpfSomenteDigitos.equals("66666666666")
                || cpfSomenteDigitos.equals("77777777777") || cpfSomenteDigitos.equals("88888888888")
                || cpfSomenteDigitos.equals("99999999999")) {
            return false;
        }

        Integer digito1 = calcularDigito(cpfSomenteDigitos.substring(0, 9), PESO_CPF);
        Integer digito2 = calcularDigito(cpfSomenteDigitos.substring(0, 9) + digito1, PESO_CPF);

        return cpfSomenteDigitos.equals(cpfSomenteDigitos.substring(0, 9) + digito1.toString() + digito2.toString());
    }

    private int calcularDigito(String str, int[] peso) {
        int soma = 0;
        for (int indice = str.length() - 1, digito; indice >= 0; indice--) {
            digito = Integer.parseInt(str.substring(indice, indice + 1));
            soma += digito * peso[peso.length - str.length() + indice];
        }
        soma = 11 - soma % 11;
        return soma > 9 ? 0 : soma;
    }

}
Enter fullscreen mode Exit fullscreen mode

A primeira coisa a se notar nessa classe é que ela implementa a interface do javax.validation ConstraintValidator que recebe uma Annotation como parâmetro.

E aqui executamos o algoritmo para validarmos se um CPF é válido ou não.

Para que a validação tenha efeito é necessário adicionar no Controller que recebe o nosso objeto a anotação @Valid para que surja efeito.

public ResponseEntity<TaxpayerDTO> postTaxpayer(@Valid @RequestBody TaxpayerDTO taxpayer)
Enter fullscreen mode Exit fullscreen mode

Com isso nós podemos adicionar essa Annotation ao atributo document no DTO TaxpayerDTO.

@Cpf
private String document;
Enter fullscreen mode Exit fullscreen mode

Se tentarmos agora fazer um POST com um número que não seja um CPF válido será lançado o erro com status 400.

{
    "errors": [
        {
            "code": "Documento invalido",
            "message": null
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Já retorna uma mensagem de erro mais padronizada porém um tanto quanto estranha já que a message está com valor null e no campo code está com a mensagem que definimos na Annotation CPF.

Isso ocorre pois a nossa dependência de validação usa o mecanismo do Spring MessageSource para buscar as mensagens que serão exibidas, então precisamos criar o nosso arquivo messages.properties na pasta Resources do projeto e lá adicionamos as mensagens.

invalid.document=Documento invalido
Enter fullscreen mode Exit fullscreen mode

E alteramos também na annotation com chave da mensagem.

String message() default "invalid.document";
Enter fullscreen mode Exit fullscreen mode

Fazendo isso a tentando novamente com um CPF que é inválido o retorno será esse:

{
    "errors": [
        {
            "code": "invalid.document",
            "message": "Documento invalido"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Criando um Exception customizada

Outra feature interessante dessa biblioteca é a capacidade de poder fazer a tratativa dos erros nas Exceptions que criamos na aplicação.

Vamos criar uma Exception para simular um cenário e poder ficar mais claro, imaginemos que há uma regra na nossa aplicação onde não seja aceito pessoas com o nome Guilherme e vamos verificar isso na nossa classe que executa as regras de negócio:

    @Override
    public void send(CommonDTO taxpayerDTO) {


        TaxPayer taxPayer = TaxPayer.newBuilder().setName(((TaxpayerDTO) taxpayerDTO).getName())
                .setDocument(((TaxpayerDTO) taxpayerDTO).getDocument()).setSituation(false).setEmail(((TaxpayerDTO) taxpayerDTO).getEmail()).build();


        // Aqui está o lançamento da Exception
        if(taxPayer.getName().contains("Guilherme")) {
            throw new BadTaxpayerUser(taxPayer.getName());
        }


        producer.send(this.createProducerRecord(taxPayer), (rm, ex) -> {
            if (ex == null) {
                log.info("Data sent with success!!!");
            } else {
                log.error("Fail to send message", ex);
            }
        });

        producer.flush();

    }
Enter fullscreen mode Exit fullscreen mode

No trecho de código acima temos uma Exception que recebe o nome do taxpayer, e será nessa classe de Exception que será feita a manipulação do erro para o retorno da API :

@Getter
@ExceptionMapping(statusCode = HttpStatus.I_AM_A_TEAPOT, errorCode = "bad.user.message")
public class BadTaxpayerUser extends RuntimeException {

    @ExposeAsArg(value = 0, name = "user")
    private final String key;

    public BadTaxpayerUser(String key) {
        super(key);
        this.key = key;
    }

}
Enter fullscreen mode Exit fullscreen mode

Na BadTaxpayerUser bastou adicionarmos uma anotação @ExceptionMapping e nela passamos no campo statusCode o código HTTP que deve ser retornado e no campo errorCode é passado a chave da mensagem, que está em resources, que deve ser exibida.

Antes de vermos a anotação @ExposeAsArg iremos adicionar ao arquivo message.properties a mensagem que deve ser exibida e vamos customizá-la dessa forma:

bad.user.message=O user {user} nao pode!!!
Enter fullscreen mode Exit fullscreen mode

Com isso quando adicionarmos a anotação @ExposeAsArg no atributo key da Exception temos que informar que o primeiro argumento que está entre chaves, {user}, deve ser interpolado pelo valor a ser recebido na exceção.

Agora fazendo o teste e enviando um POST com essas informações, que foram geradas pelo Gerador de pessoas da 4Devs:

{
    "name": "Guilherme Paulo Carlos Eduardo Dias",
    "document": "893.475.166-51",
    "email": "guilhermepaulocarloseduardodias-89@3dmaker.com.br"
}
Enter fullscreen mode Exit fullscreen mode

Teremos como retorno o erro com status 418 I’m a teapot com o corpo da mensagem:

{
    "errors": [
        {
            "code": "bad.user.message",
            "message": "O user Guilherme Paulo Carlos Eduardo Dias nao pode!!!"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Bom espero nesse artigo ter apresentando um pouco mais sobre a biblioteca Errors Spring Boot Starter e suas facilidades. Lembrando que essa é apenas uma outra maneira para lidar com validações.

O código desse projeto pode ser encontrado no GitHub

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay