-
Notifications
You must be signed in to change notification settings - Fork 1
[WTH-107] Swagger 예외 예시 자동 문서화 및 예외 관리 구조 개선 #235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
[WTH-107] Swagger 예외 예시 자동 문서화 및 예외 관리 구조 개선 #235
The head ref may contain hidden characters: "refactor/WTH-107-Weeth-\uC608\uC678-\uCF54\uB4DC-\uAD6C\uC870-\uB9AC\uD329\uD1A0\uB9C1-\uBC0F-\uC2A4\uC6E8\uAC70-\uBB38\uC11C\uD654-\uAC15\uD654"
Conversation
Walkthrough에러 코드 열거형들을 도입하고 기존 하드코딩된 예외 생성자를 열거형 상수로 대체했으며, 컨트롤러에 Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this 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(); // 기본값으로 메시지 사용 } }
src/main/java/leets/weeth/domain/schedule/application/exception/MeetingErrorCode.java
Show resolved
Hide resolved
| @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() { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| @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.
src/main/java/leets/weeth/global/config/swagger/SwaggerConfig.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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) { - }
| //todo: SAS 관련 예외도 추가 | ||
| @GetMapping("/Auth") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 메서드 레벨 어노테이션이 존재하는지 확인, 없으면 클래스 레벨 체크 | ||
| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -5Repository: 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 javaRepository: Leets-Makers/Weeth-BE
Length of output: 15781
🏁 Script executed:
# Find ApiErrorCodeExample annotation definition
rg "class ApiErrorCodeExample|interface ApiErrorCodeExample" -A 10 --type javaRepository: 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 -40Repository: 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 -20Repository: 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 -60Repository: 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메서드 레벨 어노테이션이 있다면 우선순위가 메서드 레벨이 더 우선이니까 클래스 레벨 있던말던 상관없지 않을까요? (토끼한테 하는말)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seokjun01, 네, 말씀하신 관점도 충분히 타당합니다!
두 가지 설계 방식이 있습니다:
- Override 방식 (현재 구현): 메서드 레벨 어노테이션이 있으면 클래스 레벨을 완전히 무시
- Merge 방식 (CodeRabbit 제안): 메서드 레벨과 클래스 레벨을 합쳐서 모두 표시
현재 코드베이스를 확인해보니 메서드 레벨 어노테이션을 사용하는 곳이 전혀 없고, 모두 클래스 레벨에만 적용되어 있습니다. 따라서 실제로는 어느 방식을 선택하든 현재는 문제가 없습니다.
Override 방식의 장점:
- 더 직관적이고 간단함
- 메서드별로 완전히 다른 에러 코드 세트를 지정할 수 있음
- 특정 메서드에서 클래스 레벨 에러를 제외하고 싶을 때 유용
Merge 방식의 장점:
- 클래스 레벨에 공통 에러 정의, 메서드 레벨에 추가 에러 정의 가능
- 중복 선언을 줄일 수 있음
말씀하신 대로 "메서드가 우선"이라는 의도라면 현재 구현이 맞습니다. 만약 나중에 "클래스 공통 에러 + 메서드별 추가 에러"를 표시하고 싶다면 그때 수정해도 늦지 않습니다.
현재 구조에서는 Override 방식이 더 심플하고 명확해 보입니다! 👍
| // 예외 예시를 스웨거 문서에 추가하기 위한 객체를 생성하는 메서드 | ||
| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.javaRepository: 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 2Repository: Leets-Makers/Weeth-BE
Length of output: 50377
🏁 Script executed:
# Find all usages of generateErrorCodeResponseExample method
rg "generateErrorCodeResponseExample" -A 3 -B 3Repository: 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 -5Repository: Leets-Makers/Weeth-BE
Length of output: 140
🏁 Script executed:
# Read ErrorCodeInterface definition
cat src/main/java/leets/weeth/global/common/exception/ErrorCodeInterface.javaRepository: Leets-Makers/Weeth-BE
Length of output: 672
🏁 Script executed:
# Find all implementations of ErrorCodeInterface
rg "implements ErrorCodeInterface" -A 1 | head -40Repository: 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 { | ||
|
|
There was a problem hiding this comment.
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(); | ||
| } |
There was a problem hiding this comment.
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(); | ||
| } |
There was a problem hiding this comment.
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 ""; | ||
| } |
There was a problem hiding this comment.
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, "어드민 유저만 접근 가능한 카테고리입니다"); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이제 적용되는 새로운 예외 컨벤션은 application/exception 계층에 위와같이 추가하고 예외클래스를 만들어 호출하면 되는 거 맞겠죠!?
seokjun01
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다!! 단순히 어떻게 사용하느냐 이전에, 어떤 원리로 구현했는지 이해하느라 조금 시간이 걸렸지만, , 구조 개선을 통해 체계적으로 예외를 관리할 수 있을 것 같아요!
다만 @retention RUNTIME 부분이 메모리같은 비용이 발생하는 것으로 알고 있는데, 주로 Swagger 문서 생성 시점이라는 점에서 단점보다 장점이 훨씬 많은 설계같습니다.
📌 PR 내용
예외처리 구조를 변경해 예외 추가시 자동으로 스웨거에 등록될 수 있도록 작업을 진행했습니다.
🔍 PR 세부사항
커스텀 어노테이션 생성
스웨거 설정
예외 전용 컨트롤러 추가
📸 관련 스크린샷
📝 주의사항
✅ 체크리스트
Summary by CodeRabbit
새로운 기능
개선사항
✏️ Tip: You can customize this high-level summary in your review settings.