initial commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package ru.kayashov.schedule.web;
|
||||
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import ru.kayashov.schedule.dto.ErrorResponse;
|
||||
|
||||
/**
|
||||
* Централизованная обработка ошибок для всех REST-контроллеров: клиент (Shortcuts,
|
||||
* фронт, навык Алисы) всегда получает понятный JSON со статусом и описанием причины,
|
||||
* а не голый 500 без деталей.
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* Ожидаемые бизнес-ошибки уровня "клиент сделал что-то не по порядку"
|
||||
* (например, /shift без предварительного /wake) — 400, без стектрейса в теле ответа.
|
||||
*/
|
||||
@ExceptionHandler(IllegalStateException.class)
|
||||
public ResponseEntity<ErrorResponse> handleIllegalState(IllegalStateException e) {
|
||||
log.warn("Некорректное состояние: {}", e.getMessage());
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.BAD_REQUEST)
|
||||
.body(ErrorResponse.of(400, "Bad Request", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ошибки валидации тела запроса (@Valid на WakeRequest/ShiftRequest) —
|
||||
* например, отсутствует обязательное поле wakeTime.
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException e) {
|
||||
String message = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(f -> f.getField() + ": " + f.getDefaultMessage())
|
||||
.findFirst()
|
||||
.orElse("Некорректные данные запроса");
|
||||
log.warn("Ошибка валидации: {}", message);
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.BAD_REQUEST)
|
||||
.body(ErrorResponse.of(400, "Validation Error", message));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<ErrorResponse> handleConstraintViolation(ConstraintViolationException e) {
|
||||
log.warn("Нарушение ограничений: {}", e.getMessage());
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.BAD_REQUEST)
|
||||
.body(ErrorResponse.of(400, "Validation Error", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Baikal недоступен, таймаут, неверные креды и т.п. — отдаём 502 Bad Gateway,
|
||||
* подчёркивая, что проблема во внешней зависимости, а не в самом сервисе.
|
||||
*/
|
||||
@ExceptionHandler(RestClientException.class)
|
||||
public ResponseEntity<ErrorResponse> handleCaldavFailure(RestClientException e) {
|
||||
log.error("Ошибка обращения к Baikal (CalDAV): {}", e.getMessage(), e);
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.BAD_GATEWAY)
|
||||
.body(ErrorResponse.of(502, "Bad Gateway", "Не удалось связаться с календарём: " + e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Всё остальное непредвиденное — не раскрываем внутренние детали (стектрейс, имена
|
||||
* классов) клиенту, только логируем полностью на сервере.
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleUnexpected(Exception e) {
|
||||
log.error("Непредвиденная ошибка: {}", e.getMessage(), e);
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(ErrorResponse.of(500, "Internal Server Error", "Внутренняя ошибка сервиса"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user