Solucionado (ver solução)
Solucionado
(ver solução)
4
respostas

Duvida para exibir mensagem de erro de uma API

Olá pessoal, estou consumindo uma API com Webclient e estou com algumas duvidas de como exibir corretamente os erros listados pela API.

Em alguns tipos de erro como o 401 a API retornará o corpo abaixo, nesse caso consegui tratar este retorno normalmente no meu controller adivce.

{
  "timestamp": "2023-04-17T17:56:03.838+0000",
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized",
  "path": "/api/estudante/matricula"
}

Mas quando a API gera um erro 400 é retornado o corpo abaixo

"errors": [

     {
            "timestamp": "2023-04-18 13:28:05",
            "detalhe": "Não é permitido aluno sem matricula"
        },
        {
            "timestamp": "2023-04-18 13:28:05",
            "detalhe": "Não é permitido aluno sem CPF."
        },
        {
            "timestamp": "2023-04-18 13:28:05",
            "detalhe": "Não é permitido aluno sem Responsavel."
        }
]

Como posso fazer para retornar este corpo de resposta utilizando webclient? abaixo os codigos que estou utilizando:

webClientConfig.createWebClient()
                                                .post()
                                                .uri(uri)
                                                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                                                .header(HttpHeaders.AUTHORIZATION, recuperarToken())
                                                .accept(MediaType.APPLICATION_JSON)
                                                .retrieve()
                                                .onStatus(HttpStatus.UNAUTHORIZED::equals,response -> response.bodyToMono(UnauthorizedException.class))
                                                .onStatus(HttpStatus.FORBIDDEN::equals,response -> response.bodyToMono(ForbiddenException.class))
                                                .onStatus(HttpStatus.INTERNAL_SERVER_ERROR::equals,response -> response.bodyToMono(BusinessException.class))
                                                .bodyToMono(RetornoAluno.class)
                                                .block();

Controller Advice

@ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    public ErrorMessageAlunoDto unauthorizedException(UnauthorizedException ex, WebRequest request) {

        ErrorMessageAlunoDto erro = new ErrorMessageAlunoDto(LocalDateTime.now()
                                                                                        ,HttpStatus.UNAUTHORIZED.value()
                                                                                        ,ex.getMessage()
                                                                                        ,ex.getLocalizedMessage()
                                                                                        ,request.getDescription(false));                                                                                    





        return erro;
    }

@ExceptionHandler(BusinessException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorMessageAlunoDto  businessException(BusinessException ex, WebRequest request) {

        ErrorMessageAlunoDto  erro = new ErrorMessageAlunoDto (LocalDateTime.now()
                                                                                        ,HttpStatus.INTERNAL_SERVER_ERROR.value()
                                                                                        ,ex.getMessage()
                                                                                        ,ex.getLocalizedMessage().stripTrailing()
                                                                                        ,request.getDescription(false));                                                                                    




        return erro;
    }

    @ExceptionHandler(ForbiddenException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    public ErrorMessageAlunoDto forbiddenException(ForbiddenException ex, WebRequest request) {

        ErrorMessageAlunoDto erro = new ErrorMessageAlunoDto(LocalDateTime.now()
                                                                                        ,HttpStatus.FORBIDDEN.value()
                                                                                        ,ex.getMessage()
                                                                                        ,ex.getLocalizedMessage().stripTrailing()
                                                                                        ,request.getDescription(false)
                                                                                        );                                                                                    




        return erro;
    }
4 respostas

Olá Marcos, tudo bem?

Para lidar com o retorno do erro 400, você pode utilizar o método onStatus() do WebClient para verificar se o status da resposta é igual a HttpStatus.BAD_REQUEST e, em seguida, mapear o corpo da resposta para um objeto que represente o erro retornado pela API.

Por exemplo, você pode criar uma classe Erro com os atributos timestamp e detalhe e, em seguida, mapear o corpo da resposta para um List<Erro> utilizando o método bodyToFlux() do WebClient.

Segue um exemplo de como ficaria o código:

webClientConfig.createWebClient()
    .post()
    .uri(uri)
    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .header(HttpHeaders.AUTHORIZATION, recuperarToken())
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .onStatus(HttpStatus.UNAUTHORIZED::equals, response -> response.bodyToMono(UnauthorizedException.class))
    .onStatus(HttpStatus.FORBIDDEN::equals, response -> response.bodyToMono(ForbiddenException.class))
    .onStatus(HttpStatus.INTERNAL_SERVER_ERROR::equals, response -> response.bodyToMono(BusinessException.class))

    //tratamento erro 400:

    .onStatus(HttpStatus.BAD_REQUEST::equals, response -> response.bodyToFlux(Erro.class).flatMap(errors -> Mono.error(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Erro na requisição", new Throwable(errors.toString())))))

    .bodyToMono(RetornoAluno.class)
    .block();

No exemplo acima, caso o status da resposta seja igual a HttpStatus.BAD_REQUEST, o corpo da resposta será mapeado para um List<Erro> e, em seguida, será lançada uma exceção ResponseStatusException com o status HttpStatus.BAD_REQUEST e a mensagem "Erro na requisição", além de incluir a lista de erros no Throwable da exceção.

Espero ter ajudado e bons estudos!

oi Rodrigo, criei a classe MensagemErroDto

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MensagemErroDto {

    private String timestamp;
    private String detalhe;



}

e inclui a linha

.onStatus(HttpStatus.BAD_REQUEST::equals, response -> response.bodyToFlux(MensagemErroDto.class).flatMap(errors -> Mono.error(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Erro na requisição", new Throwable(errors.toString())))))

porém o código nao compila, fica com a seguinte mensagem:

Multiple markers at this line
    - ResponseStatusException cannot be resolved to a type
    - Mono cannot be resolved
    - Type mismatch: cannot convert from Flux<Object> to Mono<? extends Throwable>

Também devo criar um método no controller Advice como criei para os outros códigos de erro?

oi, Rodrigo eu criei outra classe para tentar fazer o corpo de resposta identico ao da APi que seria um array de erros.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MensagemErroBadRequestDto {


    private List<MensagemErroDto> errors;


}

tenho a exception customizada criada


@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {


    private static final long serialVersionUID = 1L;

    public BadRequestException(String msg) {
        super(msg);
    }

}

No webClient adicionei o seguinte trecho de código

.onStatus(HttpStatus.BAD_REQUEST::equals,(e)-> e.bodyToMono(MensagemErroBadRequestDto.class).handle((body,handler)->{


                                                    handler.error(new BadRequestException(body.getErrors().stream().map(f -> f.getDetalhe()).toList().toString()));

                                                }))

Dessa forma eu consigo capturar perfeitamente as mensagens de erro da API, porém ainda não consigo exibir corretamente na minha API com o corpo no formato da classe que havia criado.

essa seria o meu método no controller advice

@ExceptionHandler(BadRequestException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public MensagemErroBadRequestDto badRequestException(BadRequestException ex, WebRequest request) {

    }

como poderia realizar o tratamento para exibir o seguinte corpo de resposta

{
    errors: [
                {
                 "timestamp": "2023-04-19 08:12:32"
                 "detalhe": "Aluno sem RG"
                 },
                 {
                 "timestamp": "2023-04-19 08:12:32"
                 "detalhe": "Aluno sem CPF"
                 }
                ]
}
solução!

Consegui resolver o problema fazendo da seguinte forma: Na minha exception customizada ficou assim

@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {


    private static final long serialVersionUID = 1L;
    private List<MensagemErroDto> erros;

    public BadRequestException(String msg,List<MensagemErroDto> erroMensagem) {
        super(msg);
        this.erros = erroMensagem;
    }


    public BadRequestException(String msg) {
        super(msg);

    }


    public List<MensagemErroDto> getErros() {
        return erros;
    }


    public void setErros(List<MensagemErroDto> erros) {
        this.erros = erros;
    }




}

no meu controller adivce ficou assim:

@ExceptionHandler(BadRequestException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public MensagemErroBadRequestDto badRequestException(BadRequestException ex, WebRequest request) {

        MensagemErroBadRequestDto  mensagemErroBadRequestDto = new MensagemErroBadRequestDto();



        mensagemErroBadRequestDto.setErrors(ex.getErros());





        return mensagemErroBadRequestDto;
    }

e adicionei a seguinte linha no webclient

.onStatus(HttpStatus.BAD_REQUEST::equals,(e)-> e.bodyToMono(MensagemErroBadRequestDto.class).handle((body,handler)->{





                                                    handler.error(new BadRequestException("erro",body.getErrors()));


                                                }))

com isso consegui pegar o corpo de resposta correto.