[FEAT] 모든 API의 service 클래스 에러 응답 통일#90
Conversation
📝 WalkthroughWalkthroughThis PR standardizes exception handling across the application by replacing ad-hoc runtime exceptions (primarily Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
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
🧪 Generate unit tests (beta)
Tip 🧪 Unit Test Generation v2 is now available!We have significantly improved our unit test generation capabilities. To enable: Add this to your reviews:
finishing_touches:
unit_tests:
enabled: trueTry it out by using the Have feedback? Share your thoughts on our Discord thread! 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.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
booklog/src/main/java/com/example/booklog/global/common/apiPayload/handler/GlobalExceptionHandler.java (1)
18-74:⚠️ Potential issue | 🔴 CriticalAdd
@ExceptionHandlerforGeneralExceptionand extendApiResponseto supportBaseErrorCode.The PR widely uses
GeneralExceptionacross services (20+ instances), butGlobalExceptionHandlerlacks a dedicated handler for it. Currently,GeneralExceptionfalls through to the genericExceptionhandler, returning500 INTERNAL_SERVER_ERRORregardless of the intended status code embedded inErrorStatus.Additionally, the proposed fix references
ApiResponse.onFailure(BaseErrorCode), butApiResponseonly provides overloads forAuthErrorCodeandGeneralErrorCode, notBaseErrorCode. This requires either:
- Adding a
BaseErrorCodeoverload toApiResponse.onFailure(), or- Manually constructing the response (note:
ApiResponseconstructor is private)🐛 Proposed fix: Add GeneralException handler + extend ApiResponse
Add to
ApiResponse.java:+ // 실패 응답 (BaseErrorCode 사용) + public static <T> ApiResponse<T> onFailure(BaseErrorCode errorCode, T data) { + return new ApiResponse<>(false, errorCode.getCode(), errorCode.getMessage(), data); + } + + // 실패 응답 (BaseErrorCode 사용, 데이터 없이) + public static <T> ApiResponse<T> onFailure(BaseErrorCode errorCode) { + return new ApiResponse<>(false, errorCode.getCode(), errorCode.getMessage(), null); + }Add to
GlobalExceptionHandler.java:+ /** + * GeneralException 처리 + */ + `@ExceptionHandler`(GeneralException.class) + public ResponseEntity<ApiResponse<Void>> handleGeneralException(GeneralException e) { + log.error("GeneralException: {}", e.getMessage(), e); + BaseErrorCode errorCode = e.getCode(); + + ApiResponse<Void> response = ApiResponse.onFailure(errorCode); + HttpStatus status = errorCode.getHttpStatus(); + + return ResponseEntity.status(status).body(response); + } + /** * 기타 모든 예외 처리 */ `@ExceptionHandler`(Exception.class)booklog/src/main/java/com/example/booklog/domain/users/service/MeProfileService.java (1)
49-57:⚠️ Potential issue | 🟠 MajorGuard the settings upsert against concurrent creates.
The find‑then‑save upsert can race if two profile updates arrive concurrently before settings exist. Both requests will see no existing record, attempt to insert, and the second will hit a
DataIntegrityViolationExceptionon theuserIdprimary key. Wrap the save in a try-catch to retry fetching on duplicate insert failure.+import org.springframework.dao.DataIntegrityViolationException;- UserSettings settings = userSettingsRepository.findById(userId) - .orElseGet(() -> userSettingsRepository.save( - UserSettings.builder() - .user(user) - .isShelfPublic(true) - .isPostPublic(true) - .build() - )); + UserSettings settings = userSettingsRepository.findById(userId) + .orElseGet(() -> { + try { + return userSettingsRepository.save( + UserSettings.builder() + .user(user) + .isShelfPublic(true) + .isPostPublic(true) + .build() + ); + } catch (DataIntegrityViolationException e) { + return userSettingsRepository.findById(userId) + .orElseThrow(() -> e); + } + });
🤖 Fix all issues with AI agents
In
`@booklog/src/main/java/com/example/booklog/domain/library/shelves/service/BookshelvesService.java`:
- Around line 31-32: In BookshelvesService replace the incorrect error mapping
when loading a user: change the
usersRepository.findById(userId).orElseThrow(...) to throw new
GeneralException(ErrorStatus.USER_NOT_FOUND) instead of
ErrorStatus.BOOK_NOT_FOUND so that the user-not-found case uses the correct
ErrorStatus constant (refer to Users usersRepository.findById in
BookshelvesService and the GeneralException/ErrorStatus types to implement the
change).
In
`@booklog/src/main/java/com/example/booklog/domain/library/shelves/service/UserBooksService.java`:
- Around line 185-188: The ownership check in UserBooksService currently throws
new GeneralException(ErrorStatus.SHELF_NOT_FOUND) when
shelf.getUser().getId().equals(userId) fails; change this to throw new
GeneralException(ErrorStatus.SHELF_NOT_OWNED) so the error status matches other
branches (locate the Bookshelves retrieval via bookshelvesRepository.findById
and the subsequent ownership check that throws GeneralException and replace the
ErrorStatus constant accordingly).
In
`@booklog/src/main/java/com/example/booklog/domain/users/service/MeAvatarService.java`:
- Around line 36-37: The code in MeAvatarService uses
usersRepository.findById(userId) and throws new
GeneralException(ErrorStatus.USER_BOOK_NOT_FOUND) on absence; change the thrown
error to the correct enum value ErrorStatus.USER_NOT_FOUND so the user-not-found
case for avatar operations maps to the right error; update the throw in the
MeAvatarService method (the line constructing GeneralException) to use
ErrorStatus.USER_NOT_FOUND and run tests to ensure error handling reflects the
new status.
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/generalStatus/GeneralSuccessCode.java`:
- Line 9: GeneralSuccessCode currently declares the same methods as
BaseSuccessCode but doesn't implement the interface; modify the enum declaration
for GeneralSuccessCode to implement BaseSuccessCode (i.e., add "implements
BaseSuccessCode") and ensure each enum method signature matches the interface
(add `@Override` where appropriate) so the enum conforms to the BaseSuccessCode
contract for polymorphism.
- Around line 9-19: ApiResponse is missing onSuccess factory overloads for
GeneralSuccessCode (only AuthSuccessCode variants exist), so add two static
methods in the ApiResponse class: public static <T> ApiResponse<T>
onSuccess(GeneralSuccessCode successCode, T data) and public static <T>
ApiResponse<T> onSuccess(GeneralSuccessCode successCode) that construct and
return new ApiResponse<>(true, successCode.getCode(), successCode.getMessage(),
dataOrNull); mirror the existing AuthSuccessCode implementations and follow how
GeneralErrorCode is handled to ensure consistency with
GeneralSuccessCode.getHttpStatus/getCode/getMessage.
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/status/ErrorStatus.java`:
- Around line 1-81: Update the HTTP status for the two validation error enums:
change ErrorStatus.DUPLICATE_SHELF_NAME (currently HttpStatus.NOT_FOUND) to
HttpStatus.BAD_REQUEST, and change ErrorStatus.UNSUPPORTED_CALENDAR_FORMAT
(currently HttpStatus.NOT_FOUND) to HttpStatus.BAD_REQUEST so they reflect
validation failures rather than missing resources.
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/status/SuccessStatus.java`:
- Around line 10-12: ApiResponse currently only supports AuthSuccessCode in its
onSuccess overloads, so SuccessStatus (which implements BaseSuccessCode) cannot
be used; add two overloaded onSuccess methods that accept BaseSuccessCode (or
specifically SuccessStatus) mirroring the existing AuthSuccessCode variants: one
generic method taking (BaseSuccessCode successCode, T data) that returns new
ApiResponse<>(true, successCode.getCode(), successCode.getMessage(), data) and
one taking (BaseSuccessCode successCode) that delegates to the former with null
data; update ApiResponse.onSuccess method signatures to include these overloads
so callers can pass SuccessStatus/BaseSuccessCode.
🧹 Nitpick comments (3)
booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/generalStatus/GeneralSuccessCode.java (1)
15-15: Code naming may cause confusion with HTTP status codes.
COMMON204is paired withHttpStatus.ACCEPTED(HTTP 202), but HTTP 204 is actually "No Content". While these are application-internal codes, the numeric suffix resembling HTTP codes could mislead developers. Consider either:
- Renaming to non-numeric codes (e.g.,
COMMON_ACCEPTED)- Aligning the numeric suffix with the actual HTTP status (e.g.,
COMMON202for ACCEPTED)booklog/src/main/java/com/example/booklog/domain/library/shelves/entity/UserBooks.java (1)
86-90: Architectural concern: Entity coupled to API-layer exception.Throwing
GeneralException(which carriesErrorStatuswith HTTP semantics) from an entity violates separation of concerns. Entities belong to the domain layer and should remain agnostic of API/presentation concerns.Consider one of these alternatives:
Keep validation in entity, throw domain exception: Create a domain-specific exception (e.g.,
InvalidPageCountException) that the service layer catches and translates.Move validation to service layer: Let the service validate before calling
updatePageCountSnapshot.♻️ Option 2: Move validation to service layer
public void updatePageCountSnapshot(Integer totalPage) { - if (totalPage == null || totalPage < 1) throw new GeneralException(ErrorStatus.TOTAL_PAGE_INVALID); this.pageCountSnapshot = totalPage; recalcProgressPercent(); }Then in the calling service:
if (totalPage == null || totalPage < 1) { throw new GeneralException(ErrorStatus.TOTAL_PAGE_INVALID); } userBooks.updatePageCountSnapshot(totalPage);booklog/src/main/java/com/example/booklog/global/common/apiPayload/exception/GeneralException.java (1)
4-13: Add exception message for better diagnostics.
Setting the RuntimeException message to the error code’s message improves logs and debugging.Suggested fix
-import lombok.AllArgsConstructor; import lombok.Getter; //프로젝트 exception을 의미한다 `@Getter` -@AllArgsConstructor public class GeneralException extends RuntimeException { private final BaseErrorCode code; + + public GeneralException(BaseErrorCode code) { + super(code.getMessage()); + this.code = code; + } }
| Users user = usersRepository.findById(userId) | ||
| .orElseThrow(() -> new IllegalArgumentException("유저 없음")); | ||
| .orElseThrow(() -> new GeneralException(ErrorStatus.BOOK_NOT_FOUND)); |
There was a problem hiding this comment.
User-not-found is mapped to the wrong error code.
findById(userId) currently throws BOOK_NOT_FOUND, which will mislead clients and breaks the unified error contract. This should be USER_NOT_FOUND.
✅ Suggested fix
- Users user = usersRepository.findById(userId)
- .orElseThrow(() -> new GeneralException(ErrorStatus.BOOK_NOT_FOUND));
+ Users user = usersRepository.findById(userId)
+ .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND));📝 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.
| Users user = usersRepository.findById(userId) | |
| .orElseThrow(() -> new IllegalArgumentException("유저 없음")); | |
| .orElseThrow(() -> new GeneralException(ErrorStatus.BOOK_NOT_FOUND)); | |
| Users user = usersRepository.findById(userId) | |
| .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); |
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/library/shelves/service/BookshelvesService.java`
around lines 31 - 32, In BookshelvesService replace the incorrect error mapping
when loading a user: change the
usersRepository.findById(userId).orElseThrow(...) to throw new
GeneralException(ErrorStatus.USER_NOT_FOUND) instead of
ErrorStatus.BOOK_NOT_FOUND so that the user-not-found case uses the correct
ErrorStatus constant (refer to Users usersRepository.findById in
BookshelvesService and the GeneralException/ErrorStatus types to implement the
change).
| Bookshelves shelf = bookshelvesRepository.findById(shelfId) | ||
| .orElseThrow(() -> new IllegalArgumentException("서재 없음")); | ||
| if (!shelf.getUser().getId().equals(userId)) throw new IllegalStateException("내 서재가 아닙니다."); | ||
| .orElseThrow(() -> new GeneralException(ErrorStatus.SHELF_NOT_FOUND)); | ||
| if (!shelf.getUser().getId().equals(userId)) throw new GeneralException(ErrorStatus.SHELF_NOT_FOUND); | ||
|
|
There was a problem hiding this comment.
Use a consistent error status for shelf ownership failures.
This branch throws SHELF_NOT_FOUND on ownership mismatch, but other branches use SHELF_NOT_OWNED. That inconsistency can break clients expecting a stable error code. Consider aligning it with the rest of the service.
✅ Suggested fix
- if (!shelf.getUser().getId().equals(userId)) throw new GeneralException(ErrorStatus.SHELF_NOT_FOUND);
+ if (!shelf.getUser().getId().equals(userId)) throw new GeneralException(ErrorStatus.SHELF_NOT_OWNED);📝 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.
| Bookshelves shelf = bookshelvesRepository.findById(shelfId) | |
| .orElseThrow(() -> new IllegalArgumentException("서재 없음")); | |
| if (!shelf.getUser().getId().equals(userId)) throw new IllegalStateException("내 서재가 아닙니다."); | |
| .orElseThrow(() -> new GeneralException(ErrorStatus.SHELF_NOT_FOUND)); | |
| if (!shelf.getUser().getId().equals(userId)) throw new GeneralException(ErrorStatus.SHELF_NOT_FOUND); | |
| Bookshelves shelf = bookshelvesRepository.findById(shelfId) | |
| .orElseThrow(() -> new GeneralException(ErrorStatus.SHELF_NOT_FOUND)); | |
| if (!shelf.getUser().getId().equals(userId)) throw new GeneralException(ErrorStatus.SHELF_NOT_OWNED); |
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/library/shelves/service/UserBooksService.java`
around lines 185 - 188, The ownership check in UserBooksService currently throws
new GeneralException(ErrorStatus.SHELF_NOT_FOUND) when
shelf.getUser().getId().equals(userId) fails; change this to throw new
GeneralException(ErrorStatus.SHELF_NOT_OWNED) so the error status matches other
branches (locate the Bookshelves retrieval via bookshelvesRepository.findById
and the subsequent ownership check that throws GeneralException and replace the
ErrorStatus constant accordingly).
| Users user = usersRepository.findById(userId) | ||
| .orElseThrow(() -> new IllegalArgumentException("USER_NOT_FOUND")); | ||
| .orElseThrow(() -> new GeneralException(ErrorStatus.USER_BOOK_NOT_FOUND)); |
There was a problem hiding this comment.
User-not-found is mapped to the wrong error code.
findById(userId) throws USER_BOOK_NOT_FOUND, which is misleading for avatar updates. This should be USER_NOT_FOUND.
✅ Suggested fix
- Users user = usersRepository.findById(userId)
- .orElseThrow(() -> new GeneralException(ErrorStatus.USER_BOOK_NOT_FOUND));
+ Users user = usersRepository.findById(userId)
+ .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND));🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/domain/users/service/MeAvatarService.java`
around lines 36 - 37, The code in MeAvatarService uses
usersRepository.findById(userId) and throws new
GeneralException(ErrorStatus.USER_BOOK_NOT_FOUND) on absence; change the thrown
error to the correct enum value ErrorStatus.USER_NOT_FOUND so the user-not-found
case for avatar operations maps to the right error; update the throw in the
MeAvatarService method (the line constructing GeneralException) to use
ErrorStatus.USER_NOT_FOUND and run tests to ensure error handling reflects the
new status.
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum GeneralSuccessCode { |
There was a problem hiding this comment.
Missing implements BaseSuccessCode.
The enum defines the same methods as BaseSuccessCode but doesn't implement the interface. This defeats the purpose of having the base interface for polymorphism and standardization.
🔧 Proposed fix
+import com.example.booklog.global.common.apiPayload.code.BaseSuccessCode;
+
`@Getter`
`@RequiredArgsConstructor`
-public enum GeneralSuccessCode {
+public enum GeneralSuccessCode implements BaseSuccessCode {📝 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.
| public enum GeneralSuccessCode { | |
| import com.example.booklog.global.common.apiPayload.code.BaseSuccessCode; | |
| `@Getter` | |
| `@RequiredArgsConstructor` | |
| public enum GeneralSuccessCode implements BaseSuccessCode { |
🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/generalStatus/GeneralSuccessCode.java`
at line 9, GeneralSuccessCode currently declares the same methods as
BaseSuccessCode but doesn't implement the interface; modify the enum declaration
for GeneralSuccessCode to implement BaseSuccessCode (i.e., add "implements
BaseSuccessCode") and ensure each enum method signature matches the interface
(add `@Override` where appropriate) so the enum conforms to the BaseSuccessCode
contract for polymorphism.
| public enum GeneralSuccessCode { | ||
|
|
||
| OK(HttpStatus.OK, "COMMON200", "요청이 성공적으로 처리되었습니다."), | ||
| CREATED(HttpStatus.CREATED, "COMMON201", "리소스가 성공적으로 생성되었습니다."), | ||
| UPDATED(HttpStatus.OK, "COMMON202", "리소스가 성공적으로 수정되었습니다."), | ||
| DELETED(HttpStatus.OK, "COMMON203", "리소스가 성공적으로 삭제되었습니다."), | ||
| ACCEPTED(HttpStatus.ACCEPTED, "COMMON204", "요청이 접수되었습니다."); | ||
|
|
||
| private final HttpStatus httpStatus; // ex: 200 OK | ||
| private final String code; // ex: COMMON200 | ||
| private final String message; // ex: "요청이 성공적으로 처리되었습니다." |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "onSuccess.*GeneralSuccessCode|onSuccess.*BaseSuccessCode" --type javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 57
🏁 Script executed:
# Also find the ApiResponse class to understand its current structure
fd -t f "ApiResponse.java"Repository: Project-BookLog/BookLog-BackEnd
Length of output: 158
🏁 Script executed:
# Search for all onSuccess method definitions in ApiResponse
rg -n "onSuccess" --type java -A 3 | head -100Repository: Project-BookLog/BookLog-BackEnd
Length of output: 6824
🏁 Script executed:
cat -n booklog/src/main/java/com/example/booklog/global/common/apiPayload/ApiResponse.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 2835
🏁 Script executed:
# Check if BaseSuccessCode exists and how it's defined
fd -t f "BaseSuccessCode.java"Repository: Project-BookLog/BookLog-BackEnd
Length of output: 167
🏁 Script executed:
# Also check for SuccessCode interface or parent class
rg "interface.*SuccessCode|abstract.*SuccessCode|class.*SuccessCode" --type javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 202
🏁 Script executed:
cat -n booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/BaseSuccessCode.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 366
🏁 Script executed:
# Check if GeneralSuccessCode implements BaseSuccessCode
rg -A 1 "public enum GeneralSuccessCode" --type javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 328
🏁 Script executed:
# Search for any usage of GeneralSuccessCode in the codebase
rg "GeneralSuccessCode" --type javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 217
🏁 Script executed:
cat -n booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/generalStatus/GeneralSuccessCode.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 968
🏁 Script executed:
# Check if AuthSuccessCode implements BaseSuccessCode for comparison
cat -n booklog/src/main/java/com/example/booklog/global/auth/exception/AuthSuccessCode.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 659
Add onSuccess factory methods for GeneralSuccessCode to ApiResponse.
Currently, ApiResponse only provides onSuccess overloads for AuthSuccessCode. To use GeneralSuccessCode (which has all required fields: HttpStatus, code, and message), corresponding factory methods must be added to ApiResponse for consistency with how GeneralErrorCode is handled.
public static <T> ApiResponse<T> onSuccess(GeneralSuccessCode successCode, T data) {
return new ApiResponse<>(true, successCode.getCode(), successCode.getMessage(), data);
}
public static <T> ApiResponse<T> onSuccess(GeneralSuccessCode successCode) {
return new ApiResponse<>(true, successCode.getCode(), successCode.getMessage(), null);
}🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/generalStatus/GeneralSuccessCode.java`
around lines 9 - 19, ApiResponse is missing onSuccess factory overloads for
GeneralSuccessCode (only AuthSuccessCode variants exist), so add two static
methods in the ApiResponse class: public static <T> ApiResponse<T>
onSuccess(GeneralSuccessCode successCode, T data) and public static <T>
ApiResponse<T> onSuccess(GeneralSuccessCode successCode) that construct and
return new ApiResponse<>(true, successCode.getCode(), successCode.getMessage(),
dataOrNull); mirror the existing AuthSuccessCode implementations and follow how
GeneralErrorCode is handled to ensure consistency with
GeneralSuccessCode.getHttpStatus/getCode/getMessage.
| package com.example.booklog.global.common.apiPayload.code.status; | ||
|
|
||
| import com.example.booklog.global.common.apiPayload.code.BaseErrorCode; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum ErrorStatus implements BaseErrorCode { | ||
|
|
||
| // ========================= | ||
| // [User / Profile] | ||
| // ========================= | ||
| USER_NOT_FOUND(HttpStatus.NOT_FOUND, "U001", "유저 없음"), | ||
| NICKNAME_EMPTY(HttpStatus.BAD_REQUEST, "U002", "닉네임은 필수입니다."), | ||
| NICKNAME_TOO_LONG(HttpStatus.BAD_REQUEST, "U003", "닉네임은 50자 이하여야 합니다."), | ||
|
|
||
| // ========================= | ||
| // [File / Upload] | ||
| // ========================= | ||
| FILE_REQUIRED(HttpStatus.BAD_REQUEST, "F001", "파일이 필요합니다."), | ||
| FILE_TOO_LARGE(HttpStatus.PAYLOAD_TOO_LARGE, "F002", "파일 용량이 너무 큽니다."), | ||
| UNSUPPORTED_IMAGE_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "F003", "지원하는 파일 양식이 아닙니다."), | ||
|
|
||
| // ========================= | ||
| // [Author / Enrichment / Search] | ||
| // ========================= | ||
| AUTHOR_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "AU001", "authorName is required"), | ||
| AUTHOR_NOT_FOUND(HttpStatus.NOT_FOUND, "AU002", "Author not found"), | ||
| SEARCH_KEYWORD_REQUIRED(HttpStatus.BAD_REQUEST, "SRCH001", "검색어는 필수입니다."), | ||
| SEARCH_KEYWORD_TOO_LONG(HttpStatus.BAD_REQUEST, "SRCH002", "검색어는 100자 이내로 입력해주세요."), | ||
| PAGE_NUMBER_INVALID(HttpStatus.BAD_REQUEST, "SRCH003", "페이지 번호는 1 이상이어야 합니다."), | ||
| PAGE_SIZE_INVALID(HttpStatus.BAD_REQUEST, "SRCH004", "페이지 크기는 1~100 사이어야 합니다."), | ||
| SORT_INVALID(HttpStatus.BAD_REQUEST, "SRCH005", "유효하지 않은 정렬 기준입니다."), | ||
|
|
||
| // ========================= | ||
| // [Tag Validation - Booklog] | ||
| // ========================= | ||
| TAG_MIN_ONE_REQUIRED(HttpStatus.BAD_REQUEST, "T001", "태그는 최소 1개 이상 선택해야 합니다."), | ||
| TAG_NOT_FOUND_INCLUDED(HttpStatus.BAD_REQUEST, "T002", "존재하지 않는 태그가 포함되어 있습니다."), | ||
| MOOD_TAG_COUNT_INVALID(HttpStatus.BAD_REQUEST, "T003", "MOOD 태그는 1~2개 선택해야 합니다."), | ||
| STYLE_TAG_COUNT_INVALID(HttpStatus.BAD_REQUEST, "T004", "STYLE 태그는 1~2개 선택해야 합니다."), | ||
| IMMERSION_TAG_COUNT_INVALID(HttpStatus.BAD_REQUEST, "T005", "IMMERSION 태그는 1개 선택해야 합니다."), | ||
|
|
||
| // ========================= | ||
| // [Booklog Post] | ||
| // ========================= | ||
| POST_NOT_FOUND(HttpStatus.NOT_FOUND, "BL001", "게시글을 찾을 수 없습니다."), | ||
| POST_DELETE_FORBIDDEN(HttpStatus.FORBIDDEN, "BL002", "삭제 권한이 없습니다."), | ||
| POST_ALREADY_DELETED_OR_CANNOT_DELETE(HttpStatus.BAD_REQUEST, "BL003", "이미 삭제되었거나 삭제할 수 없습니다."), | ||
| IMAGE_MAX_8(HttpStatus.BAD_REQUEST, "BL004", "이미지는 최대 8장까지 가능합니다."), | ||
|
|
||
| // ========================= | ||
| // [Shelf] | ||
| // ========================= | ||
| SHELF_NOT_FOUND(HttpStatus.NOT_FOUND, "S001", "서재 없음"), | ||
| SHELF_NOT_FOUND_OR_NOT_OWNED(HttpStatus.NOT_FOUND, "S002", "서재 없음 또는 내 서재 아님"), | ||
| SHELF_NOT_OWNED(HttpStatus.NOT_FOUND, "S003", "내 서재 아님"), | ||
| DUPLICATE_SHELF_NAME(HttpStatus.NOT_FOUND, "S004", "중복된 서재 이름"), | ||
|
|
||
| // ========================= | ||
| // [UserBooks / Library] | ||
| // ========================= | ||
| BOOK_NOT_FOUND(HttpStatus.NOT_FOUND, "B001", "책 없음"), | ||
| USER_BOOK_NOT_FOUND(HttpStatus.NOT_FOUND, "LB001", "저장 도서 없음"), | ||
| USER_BOOK_NOT_FOUND_OR_FORBIDDEN(HttpStatus.NOT_FOUND, "LB002", "저장 도서 없음/권한 없음"), | ||
| TOTAL_PAGE_INVALID(HttpStatus.BAD_REQUEST, "LB003", "총 페이지는 1 이상이어야 합니다."), | ||
|
|
||
| // ========================= | ||
| // [Reading Logs] | ||
| // ========================= | ||
| READING_LOG_NOT_FOUND_OR_FORBIDDEN(HttpStatus.NOT_FOUND, "R001", "독서 기록 없음/권한 없음"), | ||
| READING_LOG_UPDATED_FETCH_FAILED(HttpStatus.NOT_FOUND, "R002", "수정된 로그 조회 실패"), | ||
| UNSUPPORTED_CALENDAR_FORMAT(HttpStatus.NOT_FOUND, "R003", "month 형식이 올바르지 않습니다. 예) 2026-01"); | ||
|
|
||
|
|
||
| private final HttpStatus httpStatus; | ||
| private final String code; | ||
| private final String message; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check for usages of DUPLICATE_SHELF_NAME and UNSUPPORTED_CALENDAR_FORMAT
rg "DUPLICATE_SHELF_NAME|UNSUPPORTED_CALENDAR_FORMAT" --type javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 776
🏁 Script executed:
# Verify HttpStatus.CONFLICT availability in Spring
rg "HttpStatus\." booklog/src/main/java/com/example/booklog --type java | head -20Repository: Project-BookLog/BookLog-BackEnd
Length of output: 3453
🏁 Script executed:
# Search for other similar error patterns to understand the convention
rg "HttpStatus\." booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/status/ErrorStatus.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 2676
🏁 Script executed:
# Check if there are any API tests or documentation
find booklog -type f \( -name "*Test.java" -o -name "*test.java" -o -name "*Controller.java" \) | head -10Repository: Project-BookLog/BookLog-BackEnd
Length of output: 1026
🏁 Script executed:
# Check GlobalExceptionHandler to see how errors are mapped
cat -n booklog/src/main/java/com/example/booklog/global/common/apiPayload/handler/GlobalExceptionHandler.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 3848
🏁 Script executed:
# Check the context in BookshelvesService where DUPLICATE_SHELF_NAME is thrown
rg -B 5 -A 5 "DUPLICATE_SHELF_NAME" booklog/src/main/java/com/example/booklog/domain/library/shelves/service/BookshelvesService.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 637
🏁 Script executed:
# Check the context in ReadingCalendarService where UNSUPPORTED_CALENDAR_FORMAT is thrown
rg -B 5 -A 5 "UNSUPPORTED_CALENDAR_FORMAT" booklog/src/main/java/com/example/booklog/domain/users/service/ReadingCalendarService.javaRepository: Project-BookLog/BookLog-BackEnd
Length of output: 342
🏁 Script executed:
# Verify HttpStatus.CONFLICT is available in Spring
grep -r "CONFLICT" booklog/src/main/java --include="*.java" || echo "CONFLICT not found in codebase"Repository: Project-BookLog/BookLog-BackEnd
Length of output: 105
Correct HTTP status codes for validation errors.
DUPLICATE_SHELF_NAME and UNSUPPORTED_CALENDAR_FORMAT are incorrectly mapped to NOT_FOUND. These represent validation failures, not missing resources. Map both to BAD_REQUEST to align with the codebase pattern for validation errors (e.g., TAG_NOT_FOUND_INCLUDED, PAGE_SIZE_INVALID, SEARCH_KEYWORD_TOO_LONG).
🔧 Suggested status adjustments
- DUPLICATE_SHELF_NAME(HttpStatus.NOT_FOUND, "S004", "중복된 서재 이름"),
+ DUPLICATE_SHELF_NAME(HttpStatus.BAD_REQUEST, "S004", "중복된 서재 이름"),
@@
- UNSUPPORTED_CALENDAR_FORMAT(HttpStatus.NOT_FOUND, "R003", "month 형식이 올바르지 않습니다. 예) 2026-01");
+ UNSUPPORTED_CALENDAR_FORMAT(HttpStatus.BAD_REQUEST, "R003", "month 형식이 올바르지 않습니다. 예) 2026-01");🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/status/ErrorStatus.java`
around lines 1 - 81, Update the HTTP status for the two validation error enums:
change ErrorStatus.DUPLICATE_SHELF_NAME (currently HttpStatus.NOT_FOUND) to
HttpStatus.BAD_REQUEST, and change ErrorStatus.UNSUPPORTED_CALENDAR_FORMAT
(currently HttpStatus.NOT_FOUND) to HttpStatus.BAD_REQUEST so they reflect
validation failures rather than missing resources.
| public enum SuccessStatus implements BaseSuccessCode { | ||
| OK(HttpStatus.OK, "S000", "요청이 성공했습니다."), | ||
| CREATED(HttpStatus.CREATED, "S001", "생성되었습니다."); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if AuthSuccessCode extends BaseSuccessCode
echo "=== Checking AuthSuccessCode definition ==="
fd "AuthSuccessCode.java" --exec cat {}
echo ""
echo "=== Checking BaseSuccessCode definition ==="
fd "BaseSuccessCode.java" --exec cat {}
echo ""
echo "=== Checking ApiResponse class for onSuccess methods ==="
fd "ApiResponse.java" --exec cat {}Repository: Project-BookLog/BookLog-BackEnd
Length of output: 3236
Add BaseSuccessCode support to ApiResponse.
SuccessStatus implements BaseSuccessCode, but ApiResponse.onSuccess() only accepts AuthSuccessCode. Add overloaded onSuccess() methods that accept BaseSuccessCode (or specifically SuccessStatus) to make this enum functional:
public static <T> ApiResponse<T> onSuccess(BaseSuccessCode successCode, T data) {
return new ApiResponse<>(true, successCode.getCode(), successCode.getMessage(), data);
}
public static <T> ApiResponse<T> onSuccess(BaseSuccessCode successCode) {
return new ApiResponse<>(true, successCode.getCode(), successCode.getMessage(), null);
}🤖 Prompt for AI Agents
In
`@booklog/src/main/java/com/example/booklog/global/common/apiPayload/code/status/SuccessStatus.java`
around lines 10 - 12, ApiResponse currently only supports AuthSuccessCode in its
onSuccess overloads, so SuccessStatus (which implements BaseSuccessCode) cannot
be used; add two overloaded onSuccess methods that accept BaseSuccessCode (or
specifically SuccessStatus) mirroring the existing AuthSuccessCode variants: one
generic method taking (BaseSuccessCode successCode, T data) that returns new
ApiResponse<>(true, successCode.getCode(), successCode.getMessage(), data) and
one taking (BaseSuccessCode successCode) that delegates to the former with null
data; update ApiResponse.onSuccess method signatures to include these overloads
so callers can pass SuccessStatus/BaseSuccessCode.
#89
에러응답 통일
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.