package kr.itn.itnhub.config; import kr.itn.itnhub.contact.ContactNotFoundException; import kr.itn.itnhub.contact.InvalidContactException; import kr.itn.itnhub.feed.ChannelNotReadyException; import kr.itn.itnhub.feed.InvalidMessageException; import kr.itn.itnhub.org.OrgNotFoundException; import kr.itn.itnhub.seed.SeedParseException; 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 java.util.Map; import java.util.stream.Collectors; /** * 운영자의 흔한 실수를 서버 장애(500)가 아니라 4xx로 돌려준다. * *
{@link OrgNotFoundException}은 {@code OrganizationService.updateContact}와 * {@code ChannelProvisionService.provision}이 존재하지 않는 기관 id에 대해 던진다 - * URL에 오타가 있는 것뿐이므로 404가 맞다.
* *{@link MethodArgumentNotValidException}은 {@code @Valid} 대상 요청 바디의 필드가 * 비어있거나 형식이 틀렸을 때 Spring이 던진다 - 원본 필드명을 그대로 보여주는 대신 * 화면에 쓰는 한글 라벨로 바꿔서 알려준다.
* *{@link SeedParseException}은 비어 있거나, 형식이 틀렸거나, 시트가 잘못된 업로드에 * 대해 던진다 - 흔한 사용자 실수이므로 400과 함께 무엇이 잘못됐는지 알려줘야 한다.
* *예외 메시지는 각 예외가 만든 그대로 내려준다(새로 지어내지 않는다) - 스택트레이스, * SQL, 파일 경로는 어차피 이 메시지들에 담기지 않으므로 그대로 노출해도 안전하다. * {@code server.error.include-message=NEVER} 기본값은 건드리지 않는다 - 여기서 직접 * {@link ApiError} 본문을 만들어 반환하므로 그 설정과는 무관하게 동작한다.
* *{@link ChannelNotReadyException}은 {@code ChannelFeedService}가 존재하는 기관의 * 문정원/법률검토 채널이 아직 생성되지 않았거나 channel 파라미터가 잘못됐을 때 던진다 - * 기관은 있고(404 아님) 요청 자체도 잘못되지 않았으니(400 아님) "아직 처리할 수 없는 * 상태"라는 의미로 409를 쓴다.
* *{@link InvalidMessageException}은 채팅 탭에서 메시지 본문과 첨부파일이 둘 다 * 없는 채로 전송을 시도할 때 던진다 - 흔한 사용자 실수이므로 400을 쓴다.
* *{@link ContactNotFoundException}은 존재하지 않는 담당자 id로 조회/수정/삭제를 * 시도했을 때 던진다 - URL의 id가 단순히 틀린 것뿐이므로 404가 맞다.
* *{@link InvalidContactException}은 담당자 구분(category)이 APPLICANT/MJ/LAWYER/OPERATOR * 중 하나가 아니거나, 기관에 배정하려는 담당자의 구분이 그 역할과 맞지 않을 때 * 던진다 - 흔한 사용자 실수이므로 400을 쓴다.
* *여기에 {@code Exception.class} catch-all을 추가하지 말 것. 예상하지 못한 * 예외까지 4xx로 감싸버리면 진짜 버그가 조용히 묻힌다. 예상 밖 예외는 기본 500 처리 * 그대로 두는 것이 의도다.
*/ @RestControllerAdvice public class GlobalExceptionHandler { /** 검증 오류 메시지에 쓸 필드명 → 한글 라벨. 목록에 없는 필드는 원본 이름 그대로 보여준다. */ private static final Map