Skip to content

Conversation

@hyxklee
Copy link
Collaborator

@hyxklee hyxklee commented Jan 17, 2026

📌 PR 내용

예외처리 구조를 변경해 예외 추가시 자동으로 스웨거에 등록될 수 있도록 작업을 진행했습니다.

🔍 PR 세부사항

커스텀 어노테이션 생성

  • @ApiErrorCodeExample를 생성해 컨트롤러 클래스, 컨트롤러 메서드에 예외처리 문서화를 선언할 수 있게 설정
    • 리스트로 입력받아 여러 예외 클래스를 처리할 수 있도록 설정
  • @ExplainError를 생성해 예외코드 별로 어떤 상황, 이유에 의해 예외가 발생할 수 있는지 상세 설명을 추가

스웨거 설정

  • SwaggerConfig의 OperationCustomizer를 커스텀해 위 어노테이션이 정의된 부분의 예외 코드 정보를 가져와 스웨거 문서에 예시로 보여질 수 있도록 설정

예외 전용 컨트롤러 추가

  • 모든 도메인의 예외를 한 눈에 볼 수 있는 페이지가 있으면 좋을 듯 싶어 global/common/controller에 예외 전용 컨트롤러를 추가했습니당

📸 관련 스크린샷

image image

📝 주의사항

  • 앞으로 예외를 추가하는 경우 해당 구조에 맞게 추가해주세요.
  • PR을 보셨다면 아시겠지만, 현재는 한 도메인 내부에서 관리하는 모든 예외를 예시로 보여주고 있습니다. 즉 API 별로 발생할 수 있는, Possible한 예외는 따로 문서화를 하지 않고 있습니다. 해당 방식도 적용할 수 있으나 그렇게 되면 개발자가 수동으로 신경쓸 부분이 늘어나서 우선은 자동화에 집중했습니당

✅ 체크리스트

  • 리뷰어 설정
  • Assignee 설정
  • Label 설정
  • 제목 양식 맞췄나요? (ex. [WTH-01] PR 템플릿 수정)
  • 변경 사항에 대한 테스트

Summary by CodeRabbit

  • 새로운 기능

    • API 에러 코드 문서화 엔드포인트 추가 및 Swagger에 에러 코드 예제 자동 포함
  • 개선사항

    • 전 도메인 에러 응답 구조 통일로 사용자·클라이언트에게 일관된 오류 메시지 제공
    • API 문서에 각 도메인별 오류 설명과 예제가 추가되어 오류 확인 및 대응이 쉬워짐

✏️ Tip: You can customize this high-level summary in your review settings.

@hyxklee hyxklee requested review from jj0526 and seokjun01 January 17, 2026 15:17
@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

Walkthrough

에러 코드 열거형들을 도입하고 기존 하드코딩된 예외 생성자를 열거형 상수로 대체했으며, 컨트롤러에 @ApiErrorCodeExample을 추가하고 Swagger OperationCustomizer로 API 응답에 에러 코드 예제를 자동 첨부하도록 Swagger 설정과 문서용 컨트롤러를 추가했습니다.

Changes

Cohort / File(s) Summary
공통 인터페이스 및 어노테이션
src/main/java/leets/weeth/global/common/exception/ErrorCodeInterface.java, src/main/java/leets/weeth/global/common/exception/ExplainError.java, src/main/java/leets/weeth/global/common/exception/ApiErrorCodeExample.java, src/main/java/leets/weeth/global/common/exception/ExampleHolder.java
에러 코드 표준 인터페이스, 설명 어노테이션, 컨트롤러/메서드용 어노테이션 및 예제 홀더 추가
비즈니스 예외 기반 변경
src/main/java/leets/weeth/global/common/exception/BusinessLogicException.java
errorCode 필드 추가 및 BusinessLogicException(ErrorCodeInterface) 생성자 추가(기존 생성자는 유지)
Swagger/OpenAPI 구성 및 문서 엔드포인트
src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java, src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java
OperationCustomizer 추가: @ApiErrorCodeExample을 읽어 응답에 에러 코드 예제 주입; 예외 문서용 엔드포인트 추가
도메인별 ErrorCode 열거형 (신규)
src/main/java/leets/weeth/domain/*/application/exception/*ErrorCode.java (Account, Attendance, Board, Notice, Post, Comment, Penalty, Schedule(Event/Meeting), User, Jwt 등)
각 도메인별 ErrorCode enum 추가(코드, HttpStatus, 메시지, @ExplainError)
예외 클래스: 하드코딩 → Enum 사용으로 전환
src/main/java/leets/weeth/domain/**/application/exception/*.java (다수: Account*, Attendance*, Board*, Comment*, Penalty*, Schedule*, User*, Jwt* 등)
기존에 하드코딩한 (int, message) 방식의 super 호출들을 대응 도메인 ErrorCode enum 상수로 교체
컨트롤러에 @ApiErrorCodeExample 추가
src/main/java/leets/weeth/domain/**/presentation/*.java (Account, Attendance, Board, Comment, Penalty, Schedule, User 등, 일부 컨트롤러 다수)
클래스 레벨에 @ApiErrorCodeExample(...) 추가하여 OpenAPI에 에러 코드 예제 노출
JWT 관련 예외/코드 추가 및 변경
src/main/java/leets/weeth/global/auth/jwt/exception/JwtErrorCode.java, 관련 예외들
JwtErrorCode enum 추가 및 관련 예외들(super 호출) 변경

Sequence Diagram(s)

sequenceDiagram
  participant OC as OperationCustomizer
  participant CT as ControllerClass (with `@ApiErrorCodeExample`)
  participant EC as ErrorCodeEnum
  participant OP as OpenAPI Operation

  rect rgba(100,150,240,0.5)
  Note over OC,CT: 문서화 시 OperationCustomizer가 컨트롤러/메서드 애노테이션을 조회
  end

  OC->>CT: find `@ApiErrorCodeExample`
  CT-->>OC: provides ErrorCode classes
  OC->>EC: load enum constants (getCode/getStatus/getMessage/getExplainError)
  EC-->>OC: returns metadata
  OC->>OP: attach Examples to responses (application/json)
  OP-->>OC: Operation updated with error-code examples
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

documentation

Suggested reviewers

  • jj0526
  • seokjun01

Poem

🐰 에러들이 줄서 노래하네,
enum 깃발 아래 하나로 모여,
컨트롤러는 문서를 빛내주고,
Swagger는 예시를 살포시 걸어주네 ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 반영하고 있습니다. 'WTH-107' 형식을 준수하며, 'Swagger 예외 예시 자동 문서화 및 예외 관리 구조 개선'이 정확히 PR의 핵심 내용을 설명합니다.
Description check ✅ Passed PR 설명이 제공된 템플릿의 대부분의 필수 섹션을 충족합니다. 'PR 내용', 'PR 세부사항', '관련 스크린샷', '주의사항', '체크리스트'가 모두 포함되어 있으며, 변경사항에 대한 상세한 설명이 제공되었습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hyxklee hyxklee self-assigned this Jan 18, 2026
@hyxklee hyxklee changed the title [WTH-107] Swagger 에러 응답 예시 자동화 및 도메인별 예외 코드 통합 [WTH-107] Swagger 예외 예시 자동 문서화 및 예외 관리 구조 개선 Jan 18, 2026
@hyxklee hyxklee marked this pull request as ready for review January 18, 2026 02:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/main/java/leets/weeth/domain/board/presentation/PostController.java`:
- Line 27: The Swagger docs miss the UserErrorCode responses because
PostController's class-level `@ApiErrorCodeExample` only lists BoardErrorCode and
PostErrorCode; add UserErrorCode to the annotation so the UserNotMatchException
thrown by update and delete (methods update/delete) is documented. Update the
`@ApiErrorCodeExample` on PostController to include UserErrorCode (so
UserErrorCode.USER_NOT_MATCH (403) is exposed in Swagger).

In
`@src/main/java/leets/weeth/domain/schedule/application/exception/MeetingErrorCode.java`:
- Around line 11-12: Add the missing `@ExplainError` annotation to the
MEETING_NOT_FOUND enum constant in MeetingErrorCode so it matches other domain
error enums (e.g., BoardErrorCode, JwtErrorCode); locate the MEETING_NOT_FOUND
entry in the MeetingErrorCode enum and annotate it with `@ExplainError` (using the
same annotation parameters/format used by other enums) to enable consistent
Swagger exception auto-documentation.

In
`@src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java`:
- Around line 26-67: The controller uses inconsistent endpoint casing; change
all `@GetMapping` paths to a consistent lowercase kebab-case naming convention
(e.g., "/account", "/attendance", "/board", "/penalty", "/schedule", "/user",
"/auth") by updating the mappings on the methods accountErrorCodes,
attendanceErrorCodes, boardErrorCodes, penaltyErrorCodes, scheduleErrorCodes,
userErrorCodes, and authErrorCodes so the API paths are uniform; also search for
any external references to the old PascalCase routes and update them accordingly
(documentation/tests/consumers) to avoid breaking links.

In `@src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java`:
- Line 66: Fix the typo in the comment inside the SwaggerConfig class: change
the Korean word "스웨서" to "스웨거" in the comment that currently reads "스웨서 문서를
커스텀하기 위한 설정" so the comment becomes "스웨거 문서를 커스텀하기 위한 설정".
🧹 Nitpick comments (11)
src/main/java/leets/weeth/global/common/exception/ApiErrorCodeExample.java (1)

8-11: @Documented 어노테이션 추가를 고려해보세요.

이 어노테이션이 Javadoc에도 표시되도록 @Documented를 추가하면 API 문서화 목적에 더 부합합니다.

♻️ 제안된 수정
+import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;

+@Documented
 `@Target`({ElementType.METHOD, ElementType.TYPE})
 `@Retention`(RetentionPolicy.RUNTIME)
 public `@interface` ApiErrorCodeExample {
     Class<? extends ErrorCodeInterface>[] value();
 }
src/main/java/leets/weeth/domain/attendance/presentation/AttendanceAdminController.java (1)

25-26: JwtErrorCode 포함 여부 확인 필요.

UserAdminController@ApiErrorCodeExample({UserErrorCode.class, JwtErrorCode.class})를 사용하여 JWT 관련 에러 코드도 포함하고 있습니다. 이 Admin 컨트롤러도 인증이 필요한 엔드포인트이므로 일관성을 위해 JwtErrorCode.class를 추가하는 것이 좋을 수 있습니다.

-@ApiErrorCodeExample(AttendanceErrorCode.class)
+@ApiErrorCodeExample({AttendanceErrorCode.class, JwtErrorCode.class})
src/main/java/leets/weeth/global/common/exception/ExplainError.java (1)

8-11: LGTM!

에러 코드 설명을 위한 어노테이션이 잘 정의되었습니다. RUNTIME 보존 정책으로 리플렉션을 통한 런타임 접근이 가능하고, FIELD 타겟으로 열거형 상수에 적용할 수 있습니다.

선택적으로 @Documented 어노테이션을 추가하면 Javadoc에도 이 어노테이션 정보가 포함됩니다:

♻️ Optional enhancement
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.annotation.Documented;
 
+@Documented
 `@Target`(ElementType.FIELD)
 `@Retention`(RetentionPolicy.RUNTIME)
 public `@interface` ExplainError {
src/main/java/leets/weeth/domain/board/application/exception/BoardErrorCode.java (1)

9-24: LGTM! 에러 코드 구조가 잘 정의되었습니다.

ErrorCodeInterface 구현 및 @ExplainError 어노테이션을 통한 Swagger 문서화 패턴이 일관성 있게 적용되었습니다.

사소한 사항: Line 20의 메시지 끝에 마침표가 누락되어 있습니다 ("어드민 유저만 접근 가능한 카테고리입니다"). 다른 메시지들과의 일관성을 위해 추가를 고려해주세요.

src/main/java/leets/weeth/global/auth/jwt/exception/JwtErrorCode.java (1)

19-20: HTTP 상태 코드 선택 검토 권장

TOKEN_NOT_FOUND에 404 NOT_FOUND를 사용하셨는데, 인증 헤더 누락의 경우 의미론적으로 401 UNAUTHORIZED가 더 적합할 수 있습니다. 기존 API 규약이 있다면 현재 구현을 유지해도 무방합니다.

src/main/java/leets/weeth/domain/schedule/application/exception/EventErrorCode.java (1)

11-13: @ExplainError 어노테이션이 누락되었습니다.

다른 도메인의 에러 코드 enum들(PostErrorCode, NoticeErrorCode, UserErrorCode, PenaltyErrorCode)과 달리 EVENT_NOT_FOUND@ExplainError 어노테이션이 없습니다. Swagger 문서화 일관성을 위해 추가를 권장합니다.

♻️ 수정 제안
 public enum EventErrorCode implements ErrorCodeInterface {

+    `@ExplainError`("요청한 일정 ID에 해당하는 일정이 없을 때 발생합니다.")
     EVENT_NOT_FOUND(404, HttpStatus.NOT_FOUND, "존재하지 않는 일정입니다.");
src/main/java/leets/weeth/domain/penalty/application/exception/PenaltyErrorCode.java (1)

16-17: 메시지 끝에 마침표가 누락되었습니다.

다른 에러 메시지들과의 일관성을 위해 메시지 끝에 마침표를 추가하는 것을 권장합니다.

♻️ 수정 제안
     `@ExplainError`("시스템에 의해 자동 부여된 패널티를 수동으로 삭제하려 할 때 발생합니다.")
-    AUTO_PENALTY_DELETE_NOT_ALLOWED(400, HttpStatus.BAD_REQUEST, "자동 생성된 패널티는 삭제할 수 없습니다");
+    AUTO_PENALTY_DELETE_NOT_ALLOWED(400, HttpStatus.BAD_REQUEST, "자동 생성된 패널티는 삭제할 수 없습니다.");
src/main/java/leets/weeth/domain/account/application/exception/AccountErrorCode.java (1)

11-24: code 필드와 HttpStatus.status.value() 간의 중복 가능성

code 필드(예: 404)와 HttpStatus 값이 별도로 관리되어, 두 값이 불일치할 위험이 있습니다. 예를 들어 실수로 (400, HttpStatus.NOT_FOUND, ...)처럼 입력하면 불일치가 발생할 수 있습니다.

code 필드를 제거하고 getCode() 메서드에서 status.value()를 반환하도록 리팩토링하면 이런 위험을 방지할 수 있습니다.

♻️ 제안된 리팩토링
 `@Getter`
-@AllArgsConstructor
 public enum AccountErrorCode implements ErrorCodeInterface {

     `@ExplainError`("요청한 회비 장부 ID가 존재하지 않을 때 발생합니다.")
-    ACCOUNT_NOT_FOUND(404, HttpStatus.NOT_FOUND, "존재하지 않는 장부입니다."),
+    ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 장부입니다."),

     `@ExplainError`("이미 존재하는 장부를 중복 생성하려고 할 때 발생합니다.")
-    ACCOUNT_EXISTS(400, HttpStatus.BAD_REQUEST, "이미 생성된 장부입니다."),
+    ACCOUNT_EXISTS(HttpStatus.BAD_REQUEST, "이미 생성된 장부입니다."),

     `@ExplainError`("요청한 영수증 내역이 존재하지 않을 때 발생합니다.")
-    RECEIPT_NOT_FOUND(404, HttpStatus.NOT_FOUND, "존재하지 않는 내역입니다.");
+    RECEIPT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 내역입니다.");

-    private final int code;
     private final HttpStatus status;
     private final String message;
+
+    AccountErrorCode(HttpStatus status, String message) {
+        this.status = status;
+        this.message = message;
+    }
+
+    `@Override`
+    public int getCode() {
+        return status.value();
+    }
 }
src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java (1)

62-62: TODO 주석 확인

SAS 관련 예외 추가가 필요하다고 표시되어 있습니다. 이 작업을 추적하기 위해 이슈를 생성하시겠습니까?

src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java (2)

47-48: 사용되지 않는 빈 생성자

ApplicationContext 파라미터를 받지만 사용하지 않는 빈 생성자입니다. 이 코드는 불필요하므로 제거하는 것이 좋습니다.

♻️ 빈 생성자 제거
     `@Value`("${weeth.jwt.refresh.header}")
     private String refreshHeader;
-
-    public SwaggerConfig(ApplicationContext applicationContext) {
-    }

91-105: 스트림 내 예외 처리 개선 필요

NoSuchFieldException을 잡아서 RuntimeException으로 감싸면 원래 예외의 컨텍스트가 손실됩니다. 또한 스트림 내부에서 체크 예외를 처리하는 것은 가독성을 해칩니다.

getExplainError()가 체크 예외를 던지지 않도록 리팩토링하거나, 예외 발생 시 기본값을 반환하는 방식을 고려해 보세요.

♻️ 예외 처리 개선 제안
                         .map(errorCode -> {
-                            try {
-                                String enumName = ((Enum<?>) errorCode).name();
-
-                                return ExampleHolder.builder()
-                                        .holder(getSwaggerExample(errorCode.getExplainError(), errorCode))
-                                        .code(errorCode.getStatus().value())
-                                        .name("[" + enumName + "] " + errorCode.getMessage())
-                                        .build();
-                            } catch (NoSuchFieldException e) {
-                                throw new RuntimeException(e);
-                            }
+                            String enumName = ((Enum<?>) errorCode).name();
+                            String description = getExplainErrorSafe(errorCode);
+
+                            return ExampleHolder.builder()
+                                    .holder(getSwaggerExample(description, errorCode))
+                                    .code(errorCode.getStatus().value())
+                                    .name("[" + enumName + "] " + errorCode.getMessage())
+                                    .build();
                         })

ErrorCodeInterface.getExplainError() 메서드가 체크 예외를 던지지 않도록 수정하거나, 헬퍼 메서드를 추가하세요:

private String getExplainErrorSafe(ErrorCodeInterface errorCode) {
    try {
        return errorCode.getExplainError();
    } catch (NoSuchFieldException e) {
        return errorCode.getMessage(); // 기본값으로 메시지 사용
    }
}

Comment on lines 26 to 67
@GetMapping("/Account")
@Operation(summary = "Account 도메인 에러 코드 목록")
@ApiErrorCodeExample(AccountErrorCode.class)
public void accountErrorCodes() {
}

@GetMapping("/Attendance")
@Operation(summary = "Attendance 도메인 에러 코드 목록")
@ApiErrorCodeExample(AttendanceErrorCode.class)
public void attendanceErrorCodes() {
}

@GetMapping("/Board")
@Operation(summary = "Board 도메인 에러 코드 목록")
@ApiErrorCodeExample({BoardErrorCode.class, NoticeErrorCode.class, PostErrorCode.class, CommentErrorCode.class})
public void boardErrorCodes() {
}

@GetMapping("/Penalty")
@Operation(summary = "Penalty 도메인 에러 코드 목록")
@ApiErrorCodeExample(PenaltyErrorCode.class)
public void penaltyErrorCodes() {
}

@GetMapping("/Schedule")
@Operation(summary = "Schedule 도메인 에러 코드 목록")
@ApiErrorCodeExample({EventErrorCode.class, MeetingErrorCode.class})
public void scheduleErrorCodes() {
}

@GetMapping("/user")
@Operation(summary = "User 도메인 에러 코드 목록")
@ApiErrorCodeExample(UserErrorCode.class)
public void userErrorCodes() {
}

//todo: SAS 관련 예외도 추가
@GetMapping("/auth")
@Operation(summary = "인증/인가 에러 코드 목록")
@ApiErrorCodeExample({JwtErrorCode.class})
public void authErrorCodes() {
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

URL 경로 네이밍 일관성 부족

일부 엔드포인트는 PascalCase(/Account, /Board, /Schedule)를 사용하고, 일부는 소문자(/user, /auth)를 사용합니다. REST API 관례상 소문자 케밥 케이스(lowercase kebab-case)가 일반적이며, 최소한 일관성을 유지하는 것이 좋습니다.

♻️ 소문자로 통일하는 제안
-    `@GetMapping`("/Account")
+    `@GetMapping`("/account")
     `@Operation`(summary = "Account 도메인 에러 코드 목록")
     `@ApiErrorCodeExample`(AccountErrorCode.class)
     public void accountErrorCodes() {
     }

-    `@GetMapping`("/Attendance")
+    `@GetMapping`("/attendance")
     `@Operation`(summary = "Attendance 도메인 에러 코드 목록")
     `@ApiErrorCodeExample`(AttendanceErrorCode.class)
     public void attendanceErrorCodes() {
     }

-    `@GetMapping`("/Board")
+    `@GetMapping`("/board")
     `@Operation`(summary = "Board 도메인 에러 코드 목록")
     `@ApiErrorCodeExample`({BoardErrorCode.class, NoticeErrorCode.class, PostErrorCode.class, CommentErrorCode.class})
     public void boardErrorCodes() {
     }

-    `@GetMapping`("/Penalty")
+    `@GetMapping`("/penalty")
     `@Operation`(summary = "Penalty 도메인 에러 코드 목록")
     `@ApiErrorCodeExample`(PenaltyErrorCode.class)
     public void penaltyErrorCodes() {
     }

-    `@GetMapping`("/Schedule")
+    `@GetMapping`("/schedule")
     `@Operation`(summary = "Schedule 도메인 에러 코드 목록")
     `@ApiErrorCodeExample`({EventErrorCode.class, MeetingErrorCode.class})
     public void scheduleErrorCodes() {
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/Account")
@Operation(summary = "Account 도메인 에러 코드 목록")
@ApiErrorCodeExample(AccountErrorCode.class)
public void accountErrorCodes() {
}
@GetMapping("/Attendance")
@Operation(summary = "Attendance 도메인 에러 코드 목록")
@ApiErrorCodeExample(AttendanceErrorCode.class)
public void attendanceErrorCodes() {
}
@GetMapping("/Board")
@Operation(summary = "Board 도메인 에러 코드 목록")
@ApiErrorCodeExample({BoardErrorCode.class, NoticeErrorCode.class, PostErrorCode.class, CommentErrorCode.class})
public void boardErrorCodes() {
}
@GetMapping("/Penalty")
@Operation(summary = "Penalty 도메인 에러 코드 목록")
@ApiErrorCodeExample(PenaltyErrorCode.class)
public void penaltyErrorCodes() {
}
@GetMapping("/Schedule")
@Operation(summary = "Schedule 도메인 에러 코드 목록")
@ApiErrorCodeExample({EventErrorCode.class, MeetingErrorCode.class})
public void scheduleErrorCodes() {
}
@GetMapping("/user")
@Operation(summary = "User 도메인 에러 코드 목록")
@ApiErrorCodeExample(UserErrorCode.class)
public void userErrorCodes() {
}
//todo: SAS 관련 예외도 추가
@GetMapping("/auth")
@Operation(summary = "인증/인가 에러 코드 목록")
@ApiErrorCodeExample({JwtErrorCode.class})
public void authErrorCodes() {
}
`@GetMapping`("/account")
`@Operation`(summary = "Account 도메인 에러 코드 목록")
`@ApiErrorCodeExample`(AccountErrorCode.class)
public void accountErrorCodes() {
}
`@GetMapping`("/attendance")
`@Operation`(summary = "Attendance 도메인 에러 코드 목록")
`@ApiErrorCodeExample`(AttendanceErrorCode.class)
public void attendanceErrorCodes() {
}
`@GetMapping`("/board")
`@Operation`(summary = "Board 도메인 에러 코드 목록")
`@ApiErrorCodeExample`({BoardErrorCode.class, NoticeErrorCode.class, PostErrorCode.class, CommentErrorCode.class})
public void boardErrorCodes() {
}
`@GetMapping`("/penalty")
`@Operation`(summary = "Penalty 도메인 에러 코드 목록")
`@ApiErrorCodeExample`(PenaltyErrorCode.class)
public void penaltyErrorCodes() {
}
`@GetMapping`("/schedule")
`@Operation`(summary = "Schedule 도메인 에러 코드 목록")
`@ApiErrorCodeExample`({EventErrorCode.class, MeetingErrorCode.class})
public void scheduleErrorCodes() {
}
`@GetMapping`("/user")
`@Operation`(summary = "User 도메인 에러 코드 목록")
`@ApiErrorCodeExample`(UserErrorCode.class)
public void userErrorCodes() {
}
//todo: SAS 관련 예외도 추가
`@GetMapping`("/auth")
`@Operation`(summary = "인증/인가 에러 코드 목록")
`@ApiErrorCodeExample`({JwtErrorCode.class})
public void authErrorCodes() {
}
🤖 Prompt for AI Agents
In
`@src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java`
around lines 26 - 67, The controller uses inconsistent endpoint casing; change
all `@GetMapping` paths to a consistent lowercase kebab-case naming convention
(e.g., "/account", "/attendance", "/board", "/penalty", "/schedule", "/user",
"/auth") by updating the mappings on the methods accountErrorCodes,
attendanceErrorCodes, boardErrorCodes, penaltyErrorCodes, scheduleErrorCodes,
userErrorCodes, and authErrorCodes so the API paths are uniform; also search for
any external references to the old PascalCase routes and update them accordingly
(documentation/tests/consumers) to avoid breaking links.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java`:
- Around line 62-63: The file ExceptionDocController contains a TODO comment
("SAS 관련 예외도 추가") near the `@GetMapping`("/Auth") in the ExceptionDocController
class; either convert that TODO into a tracked issue (create a ticket in your
issue tracker, include the new issue ID and owner in the comment) or
implement/clean it now: update ExceptionDocController to add the SAS-related
exceptions handling or remove the TODO before release, and if you defer keep a
one-line comment referencing the created issue ID and expected scope/PR so the
intent is traceable.

In `@src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java`:
- Around line 86-108: In generateErrorCodeResponseExample, guard against
type.getEnumConstants() returning null (which happens when type is not an enum)
to avoid Arrays.stream(null) NPE: after ErrorCodeInterface[] errorCodes =
type.getEnumConstants(); add a null check (e.g., if (errorCodes == null) throw
new IllegalArgumentException("type must be an enum implementing
ErrorCodeInterface: " + type); or alternatively return silently), so the method
never calls Arrays.stream on null; this ensures the later code that assumes enum
behavior (and ErrorCodeInterface methods that cast to Enum) is protected when a
non-enum Class is passed.
- Around line 70-79: The current logic replaces class-level ApiErrorCodeExample
with the method-level one; change it to merge both: retrieve ApiErrorCodeExample
from handlerMethod.getMethodAnnotation(ApiErrorCodeExample.class) and from
handlerMethod.getBeanType().getAnnotation(ApiErrorCodeExample.class), build a
combined unique list/array of Class<? extends ErrorCodeInterface> (deduplicate),
and then iterate that merged list calling
generateErrorCodeResponseExample(operation.getResponses(), type); keep
references to handlerMethod, ApiErrorCodeExample, and
generateErrorCodeResponseExample so you update the existing block rather than
replacing surrounding code.
♻️ Duplicate comments (1)
src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java (1)

26-64: 엔드포인트 경로 케이스를 통일해 주세요.

현재 PascalCase와 혼용되어 있어 외부 공개 경로 기준 일관성이 떨어집니다. 소문자 케밥케이스로 통일하는 편이 좋습니다.

♻️ 소문자 통일 제안
-    `@GetMapping`("/Account")
+    `@GetMapping`("/account")
...
-    `@GetMapping`("/Attendance")
+    `@GetMapping`("/attendance")
...
-    `@GetMapping`("/Board")
+    `@GetMapping`("/board")
...
-    `@GetMapping`("/Penalty")
+    `@GetMapping`("/penalty")
...
-    `@GetMapping`("/Schedule")
+    `@GetMapping`("/schedule")
...
-    `@GetMapping`("/User")
+    `@GetMapping`("/user")
...
-    `@GetMapping`("/Auth")
+    `@GetMapping`("/auth")
🧹 Nitpick comments (1)
src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java (1)

21-48: 사용되지 않는 ApplicationContext 주입은 제거 고려

현재 생성자 인자를 사용하지 않아 불필요한 의존성만 추가됩니다. 특별한 의도가 없다면 제거하는 편이 깔끔합니다.

♻️ 제거 제안
-import org.springframework.context.ApplicationContext;
-
-    public SwaggerConfig(ApplicationContext applicationContext) {
-    }

Comment on lines +62 to +63
//todo: SAS 관련 예외도 추가
@GetMapping("/Auth")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

TODO 항목은 이슈로 분리하거나 릴리즈 전 정리 필요합니다.

SAS 관련 예외 추가 TODO가 남아 있어 추적성이 떨어집니다. 이슈로 분리하거나 주석 제거/처리 방안을 결정해주세요.

원하시면 이슈 템플릿 정리나 구현 방향 정리도 도와드릴게요.

🤖 Prompt for AI Agents
In
`@src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java`
around lines 62 - 63, The file ExceptionDocController contains a TODO comment
("SAS 관련 예외도 추가") near the `@GetMapping`("/Auth") in the ExceptionDocController
class; either convert that TODO into a tracked issue (create a ticket in your
issue tracker, include the new issue ID and owner in the comment) or
implement/clean it now: update ExceptionDocController to add the SAS-related
exceptions handling or remove the TODO before release, and if you defer keep a
one-line comment referencing the created issue ID and expected scope/PR so the
intent is traceable.

Comment on lines +70 to +79
// 메서드 레벨 어노테이션이 존재하는지 확인, 없으면 클래스 레벨 체크
ApiErrorCodeExample apiErrorCodeExample = handlerMethod.getMethodAnnotation(ApiErrorCodeExample.class);
if (apiErrorCodeExample == null) {
apiErrorCodeExample = handlerMethod.getBeanType().getAnnotation(ApiErrorCodeExample.class);
}

if (apiErrorCodeExample != null) {
for (Class<? extends ErrorCodeInterface> type : apiErrorCodeExample.value()) {
generateErrorCodeResponseExample(operation.getResponses(), type);
}
Copy link

@coderabbitai coderabbitai bot Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's locate the ApiErrorCodeExample annotation definition
find . -type f -name "*.java" | xargs grep -l "class ApiErrorCodeExample\|interface ApiErrorCodeExample" | head -5

Repository: Leets-Makers/Weeth-BE

Length of output: 186


🏁 Script executed:

# Find the SwaggerConfig.java file and get more context around lines 70-79
find . -type f -name "SwaggerConfig.java"

Repository: Leets-Makers/Weeth-BE

Length of output: 133


🏁 Script executed:

# Search for usage patterns of `@ApiErrorCodeExample` in the codebase
rg "@ApiErrorCodeExample" -A 2 -B 2 --type java

Repository: Leets-Makers/Weeth-BE

Length of output: 15781


🏁 Script executed:

# Find ApiErrorCodeExample annotation definition
rg "class ApiErrorCodeExample|interface ApiErrorCodeExample" -A 10 --type java

Repository: Leets-Makers/Weeth-BE

Length of output: 382


🏁 Script executed:

# Get more context around the SwaggerConfig.java lines 70-79
cat -n src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java | sed -n '60,90p'

Repository: Leets-Makers/Weeth-BE

Length of output: 1580


🏁 Script executed:

# Search for cases where both class-level and method-level `@ApiErrorCodeExample` might be used
rg -A 20 "@ApiErrorCodeExample" --type java | grep -E "(`@ApiErrorCodeExample`|public.*class|public.*void)" | head -40

Repository: Leets-Makers/Weeth-BE

Length of output: 5010


🏁 Script executed:

# Check if there are any methods (method-level) with `@ApiErrorCodeExample` annotation
rg "@ApiErrorCodeExample" -B 5 --type java | grep -E "(public|private|protected).*(void|ResponseEntity|Mono|Flux)" | head -20

Repository: Leets-Makers/Weeth-BE

Length of output: 651


🏁 Script executed:

# Look for the complete ExceptionDocController to see if there are both class and method level annotations
cat -n src/main/java/leets/weeth/global/common/controller/ExceptionDocController.java | head -60

Repository: Leets-Makers/Weeth-BE

Length of output: 2934


메서드/클래스 어노테이션 병합 로직 검토 필요

현재 구현은 메서드 레벨 @ApiErrorCodeExample이 있을 경우 클래스 레벨 어노테이션을 완전히 무시합니다. 어노테이션이 배열 형태로 여러 에러 코드를 지원하는 점을 고려하면, 메서드와 클래스 레벨 어노테이션을 모두 적용하려는 의도라면 두 값을 병합하는 로직이 필요합니다.

현재 코드베이스에서는 두 수준의 어노테이션을 함께 사용하는 사례가 없으므로 실제 문제는 발생하지 않지만, 추후 메서드 레벨 어노테이션을 추가할 때 클래스 레벨 정의가 의도치 않게 무시될 수 있습니다.

🤖 Prompt for AI Agents
In `@src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java` around
lines 70 - 79, The current logic replaces class-level ApiErrorCodeExample with
the method-level one; change it to merge both: retrieve ApiErrorCodeExample from
handlerMethod.getMethodAnnotation(ApiErrorCodeExample.class) and from
handlerMethod.getBeanType().getAnnotation(ApiErrorCodeExample.class), build a
combined unique list/array of Class<? extends ErrorCodeInterface> (deduplicate),
and then iterate that merged list calling
generateErrorCodeResponseExample(operation.getResponses(), type); keep
references to handlerMethod, ApiErrorCodeExample, and
generateErrorCodeResponseExample so you update the existing block rather than
replacing surrounding code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 레벨 어노테이션이 있다면 우선순위가 메서드 레벨이 더 우선이니까 클래스 레벨 있던말던 상관없지 않을까요? (토끼한테 하는말)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seokjun01, 네, 말씀하신 관점도 충분히 타당합니다!

두 가지 설계 방식이 있습니다:

  1. Override 방식 (현재 구현): 메서드 레벨 어노테이션이 있으면 클래스 레벨을 완전히 무시
  2. Merge 방식 (CodeRabbit 제안): 메서드 레벨과 클래스 레벨을 합쳐서 모두 표시

현재 코드베이스를 확인해보니 메서드 레벨 어노테이션을 사용하는 곳이 전혀 없고, 모두 클래스 레벨에만 적용되어 있습니다. 따라서 실제로는 어느 방식을 선택하든 현재는 문제가 없습니다.

Override 방식의 장점:

  • 더 직관적이고 간단함
  • 메서드별로 완전히 다른 에러 코드 세트를 지정할 수 있음
  • 특정 메서드에서 클래스 레벨 에러를 제외하고 싶을 때 유용

Merge 방식의 장점:

  • 클래스 레벨에 공통 에러 정의, 메서드 레벨에 추가 에러 정의 가능
  • 중복 선언을 줄일 수 있음

말씀하신 대로 "메서드가 우선"이라는 의도라면 현재 구현이 맞습니다. 만약 나중에 "클래스 공통 에러 + 메서드별 추가 에러"를 표시하고 싶다면 그때 수정해도 늦지 않습니다.

현재 구조에서는 Override 방식이 더 심플하고 명확해 보입니다! 👍

Comment on lines +86 to +108
// 예외 예시를 스웨거 문서에 추가하기 위한 객체를 생성하는 메서드
private void generateErrorCodeResponseExample(ApiResponses responses, Class<? extends ErrorCodeInterface> type) {
ErrorCodeInterface[] errorCodes = type.getEnumConstants();

Map<Integer, List<ExampleHolder>> statusWithExampleHolders =
Arrays.stream(errorCodes)
.map(errorCode -> {
try {
String enumName = ((Enum<?>) errorCode).name();

return ExampleHolder.builder()
.holder(getSwaggerExample(errorCode.getExplainError(), errorCode))
.code(errorCode.getStatus().value())
.name("[" + enumName + "] " + errorCode.getMessage()) // 한글로된 드롭다운을 만들기 위해 예외 메시지를 이름으로 사용
.build();
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
})
.collect(groupingBy(ExampleHolder::getCode));

addExamplesToResponses(responses, statusWithExampleHolders);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's read the SwaggerConfig.java file to see the full method and surrounding code
cat -n src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java

Repository: Leets-Makers/Weeth-BE

Length of output: 8004


🏁 Script executed:

# Also search for `@ApiErrorCodeExample` annotation definition
find . -type f -name "*.java" | xargs rg "@ApiErrorCodeExample|ApiErrorCodeExample" -A 5 -B 2

Repository: Leets-Makers/Weeth-BE

Length of output: 50377


🏁 Script executed:

# Find all usages of generateErrorCodeResponseExample method
rg "generateErrorCodeResponseExample" -A 3 -B 3

Repository: Leets-Makers/Weeth-BE

Length of output: 1572


🏁 Script executed:

# Find ErrorCodeInterface definition
find . -type f -name "*.java" -path "*exception*" | xargs grep -l "interface ErrorCodeInterface" | head -5

Repository: Leets-Makers/Weeth-BE

Length of output: 140


🏁 Script executed:

# Read ErrorCodeInterface definition
cat src/main/java/leets/weeth/global/common/exception/ErrorCodeInterface.java

Repository: Leets-Makers/Weeth-BE

Length of output: 672


🏁 Script executed:

# Find all implementations of ErrorCodeInterface
rg "implements ErrorCodeInterface" -A 1 | head -40

Repository: Leets-Makers/Weeth-BE

Length of output: 2597


generateErrorCodeResponseExample 메서드에 null 타입 체크 추가 필요

type.getEnumConstants()는 enum이 아닌 클래스에 대해 null을 반환하므로, 이를 검증하지 않으면 Arrays.stream(null) 호출 시 NPE가 발생합니다. 타입 시스템이 enum 구현을 강제하지 않음에도 불구하고, ErrorCodeInterface의 getExplainError() 메서드가 ((Enum<?>) this).name()으로 명시적으로 enum 타입을 가정하고 있어 방어 로직이 필수입니다.

🛡️ 제안된 방어 로직
 private void generateErrorCodeResponseExample(ApiResponses responses, Class<? extends ErrorCodeInterface> type) {
+    if (!type.isEnum()) {
+        throw new IllegalArgumentException("@ApiErrorCodeExample는 enum 타입만 지원합니다: " + type.getName());
+    }
     ErrorCodeInterface[] errorCodes = type.getEnumConstants();
+    if (errorCodes == null || errorCodes.length == 0) {
+        return;
+    }
🤖 Prompt for AI Agents
In `@src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java` around
lines 86 - 108, In generateErrorCodeResponseExample, guard against
type.getEnumConstants() returning null (which happens when type is not an enum)
to avoid Arrays.stream(null) NPE: after ErrorCodeInterface[] errorCodes =
type.getEnumConstants(); add a null check (e.g., if (errorCodes == null) throw
new IllegalArgumentException("type must be an enum implementing
ErrorCodeInterface: " + type); or alternatively return silently), so the method
never calls Arrays.stream on null; this ensures the later code that assumes enum
behavior (and ErrorCodeInterface methods that cast to Enum) is protected when a
non-enum Class is passed.

@RequestMapping("/api/v1/exceptions")
@Tag(name = "Exception Document", description = "API 에러 코드 문서")
public class ExceptionDocController {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 파일이 예외를 한 번에 모아서 볼 수 있는 컨트롤러군요!
도메인 별 에러코드 클래스별로 다 나열해주기 위해 @ApiErrorCodeExample을 각각 작성해주신게 인상적이네요....👍🏻

@Retention(RetentionPolicy.RUNTIME)
public @interface ApiErrorCodeExample {
Class<? extends ErrorCodeInterface>[] value();
}
Copy link
Collaborator

@seokjun01 seokjun01 Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자세히 보니, interface가 아니라 @interface군요! 위 방식을 통해서 어노테이션을 커스텀화해서 정의하는군요 ! 배우고갑니다!

Field field = this.getClass().getField(((Enum<?>) this).name());
ExplainError annotation = field.getAnnotation(ExplainError.class);
return Objects.nonNull(annotation) ? annotation.value() : getMessage();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터페이스에서는 원래 구현은 넣지 않는 것으로 알고 있었는데
default로 선언하신게 특이하여 알아보니 , 규격을 강제하는 것 뿐만 아니라 공통적인 동작까지도 제공하는 군요!
도메인은 다다르겠지만 , 메소드 호출 한 줄로 단일화 되겠네요! 짱입니다🙌

@Retention(RetentionPolicy.RUNTIME)
public @interface ExplainError {
String value() default "";
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errorCode.getExplainError() 를 하면 같이 읽어와서 설명을 해주는 군요!


@ExplainError("일반 유저가 어드민 전용 카테고리에 접근하려 할 때 발생합니다.")
CATEGORY_ACCESS_DENIED(403, HttpStatus.FORBIDDEN, "어드민 유저만 접근 가능한 카테고리입니다");

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이제 적용되는 새로운 예외 컨벤션은 application/exception 계층에 위와같이 추가하고 예외클래스를 만들어 호출하면 되는 거 맞겠죠!?

Copy link
Collaborator

@seokjun01 seokjun01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!! 단순히 어떻게 사용하느냐 이전에, 어떤 원리로 구현했는지 이해하느라 조금 시간이 걸렸지만, , 구조 개선을 통해 체계적으로 예외를 관리할 수 있을 것 같아요!

다만 @retention RUNTIME 부분이 메모리같은 비용이 발생하는 것으로 알고 있는데, 주로 Swagger 문서 생성 시점이라는 점에서 단점보다 장점이 훨씬 많은 설계같습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants