Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Обработка ошибок

Сейчас запрос несуществующего поста возвращает 404 с пустым телом. Клиенту API полезнее получить объяснение: что не так и с чем. Для этого код бросает своё исключение, а обработчик исключений (exception mapper) превращает его в HTTP-ответ с нужным статусом и телом. Так логика ошибок собрана в одном месте, а ресурсы остаются короткими.

Своё исключение

// src/main/java/com/example/blog/PostNotFoundException.java
package com.example.blog;
 
public class PostNotFoundException extends RuntimeException {
 
    public PostNotFoundException(long id) {
        super("Пост " + id + " не найден");
    }
}

Исключение наследует RuntimeException, поэтому его не нужно объявлять в throws. Для тела ответа заведём запись:

// src/main/java/com/example/blog/ErrorMessage.java
package com.example.blog;
 
public record ErrorMessage(String error) {
}

Обработчик

В Quarkus REST обработчик — это метод с аннотацией @ServerExceptionMapper. Тип параметра говорит, какое исключение он ловит:

// src/main/java/com/example/blog/ExceptionMappers.java
package com.example.blog;
 
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
 
public class ExceptionMappers {
 
    @ServerExceptionMapper
    public RestResponse<ErrorMessage> mapNotFound(PostNotFoundException e) {
        return RestResponse.status(Response.Status.NOT_FOUND, new ErrorMessage(e.getMessage()));
    }
}

RestResponse<ErrorMessage> — типизированный ответ Quarkus REST: статус плюс объект, который станет JSON. Класс с обработчиками может лежать где угодно в проекте — Quarkus найдёт его при сборке.

Бросаем исключение в ресурсе

В PostResource замените NotFoundException на своё исключение:

// src/main/java/com/example/blog/PostResource.java
@GET
@Path("/{id}")
public Post get(@PathParam("id") long id) {
    return postService.findById(id)
            .orElseThrow(() -> new PostNotFoundException(id));
}
 
@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") long id) {
    if (!postService.delete(id)) {
        throw new PostNotFoundException(id);
    }
    return Response.noContent().build();
}
curl -i http://localhost:8080/posts/99
HTTP/1.1 404 Not Found
Content-Type: application/json;charset=UTF-8

{"error":"Пост 99 не найден"}

Стандартный вариант

В стандарте Jakarta REST обработчик — отдельный класс с аннотацией @Provider, реализующий ExceptionMapper. Quarkus поддерживает и его:

// src/main/java/com/example/blog/IllegalArgumentMapper.java
package com.example.blog;
 
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
 
@Provider
public class IllegalArgumentMapper implements ExceptionMapper<IllegalArgumentException> {
 
    @Override
    public Response toResponse(IllegalArgumentException e) {
        return Response.status(Response.Status.BAD_REQUEST)
                .entity(new ErrorMessage(e.getMessage()))
                .build();
    }
}

Теперь любое IllegalArgumentException из ресурса станет ответом 400 с JSON {"error":"..."}. Вариант с @ServerExceptionMapper короче, поэтому в Quarkus используют в основном его.

Какой обработчик сработает

Если подходят несколько обработчиков, Quarkus выбирает тот, чей тип ближе всего к брошенному исключению. Ошибки проверки данных и стандартные исключения вроде NotFoundException уже имеют встроенные обработчики.

Частая ошибка

Хочется написать один обработчик на всё — для Exception:

// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAll(Exception e) {
    return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, new ErrorMessage("Что-то пошло не так"));
}

Но он перехватит и встроенные исключения Jakarta REST:

curl -i http://localhost:8080/nothing-here
curl -i -X PUT http://localhost:8080/posts/1
HTTP/1.1 500 Internal Server Error
Content-Type: application/json;charset=UTF-8

{"error":"Что-то пошло не так"}

Оба запроса должны были вернуть 404 и 405, а получили 500. Делайте обработчики для своих конкретных исключений. Если общий обработчик всё же нужен, пропускайте стандартные ошибки с их собственным статусом:

// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAll(Exception e) {
    if (e instanceof WebApplicationException w) {
        return RestResponse.status(w.getResponse().getStatusInfo());
    }
    return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, new ErrorMessage("Что-то пошло не так"));
}

С такой проверкой (импорт jakarta.ws.rs.WebApplicationException) неизвестный адрес снова получает 404, а неверный метод — 405.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.