81 lines
4.2 KiB
Java
81 lines
4.2 KiB
Java
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", "Внутренняя ошибка сервиса"));
|
|
}
|
|
} |