package kr.itn.itnhub.config; import kr.itn.itnhub.seed.SeedParseException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 운영자의 흔한 실수를 서버 장애(500)가 아니라 4xx로 돌려준다. * *

{@link IllegalArgumentException}은 {@code OrganizationService.updateContact}와 * {@code ChannelProvisionService.provision}이 존재하지 않는 기관 id에 대해 던진다 - * URL에 오타가 있는 것뿐이므로 404가 맞다.

* *

{@link SeedParseException}은 비어 있거나, 형식이 틀렸거나, 시트가 잘못된 업로드에 * 대해 던진다 - 흔한 사용자 실수이므로 400과 함께 무엇이 잘못됐는지 알려줘야 한다.

* *

예외 메시지는 각 예외가 만든 그대로 내려준다(새로 지어내지 않는다) - 스택트레이스, * SQL, 파일 경로는 어차피 이 메시지들에 담기지 않으므로 그대로 노출해도 안전하다. * {@code server.error.include-message=NEVER} 기본값은 건드리지 않는다 - 여기서 직접 * {@link ApiError} 본문을 만들어 반환하므로 그 설정과는 무관하게 동작한다.

* *

여기에 {@code Exception.class} catch-all을 추가하지 말 것. 예상하지 못한 * 예외까지 4xx로 감싸버리면 진짜 버그가 조용히 묻힌다. 예상 밖 예외는 기본 500 처리 * 그대로 두는 것이 의도다.

*/ @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity handleNotFound(IllegalArgumentException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ApiError(e.getMessage())); } @ExceptionHandler(SeedParseException.class) public ResponseEntity handleSeedParse(SeedParseException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ApiError(e.getMessage())); } }