Skip to content

chore: 도메인 에러 코드 통일 및 Swagger 표기 - #169

Merged
chazy-d merged 14 commits into
mainfrom
refactor/domain-error-codes
Aug 12, 2026
Merged

chore: 도메인 에러 코드 통일 및 Swagger 표기#169
chazy-d merged 14 commits into
mainfrom
refactor/domain-error-codes

Conversation

@chazy-d

@chazy-d chazy-d commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🔗 관련 이슈 (Related Issue)

📝 작업 내용

도메인 에러 코드 체계를 실제로 지키게 만들고, 그 결과를 Swagger 문서에 옮겼습니다.


1. 에러 코드 통일 (커밋 1~3)

ProjectAccessValidator.getCurrentMemberOrThrow()완전히 같은 조건인데 COMMON403 을 던지던 15곳(VideoService 7, ShareLinkService 3, FeedbackService 2, FeedbackDetailService 2, ScheduleService 1)을 PROJECT403 으로 맞췄습니다.

새로 정의한 코드는 VIDEO409, VIDEO_REFERENCE_FILE409, FEEDBACK403, FEEDBACK_REPLY403, SCHEDULE403, RECRUITMENT403, APPLICATION403 입니다. CommonErrorCode.FORBIDDENCookieCsrfProtectionFilter 한 곳만 남겼습니다. CSRF 차단은 도메인 규칙이 아니라 전역 보안이라 공통 코드가 맞습니다.

2. @ApiErrorCodes (커밋 4)

@ApiErrorCodes({"PROJECT403", "PROJECT_ADMIN403", "PROJECT_COMPLETED409"})
@PatchMapping("/{projectId}")
public ApiResponse<ProjectResponse> updateProject(...)

codemessage 를 enum 에서 그대로 가져오므로 문서와 실제 응답이 갈라지지 않습니다. enum 이 아니라 문자열인 이유는 자바 애노테이션 배열에 서로 다른 enum 타입을 담을 수 없어서입니다(피드백처럼 프로젝트+공유링크 코드가 함께 나가는 엔드포인트가 실제로 있습니다). 오타는 문서 검증 테스트가 잡습니다.

커스터마이저는 하나만 등록했습니다. 처음엔 공통용/도메인용을 따로 등록하고 @Order 로 순서를 정했는데, springdoc 은 그 순서를 지키지 않아 도메인 쪽이 먼저 돌았습니다. 순서가 뒤집히면 공통 예시가 통째로 빠지는데 문서는 멀쩡해 보입니다. 그래서 공통 커스터마이저가 마지막에 DomainErrorResponses 를 직접 부르도록 해 순서를 코드에 적었습니다.

3. 엔드포인트 표기 (커밋 5~8)

71개 엔드포인트, 28개 코드입니다(PROJECT403 54, PROJECT404 33, SHARELINK410 10, SHARELINK403 8 …). 컨트롤러에서 서비스 호출을 따라가며 실제로 던지는 코드만 적었습니다. 판단이 갈렸던 곳:

  • 피드백 / 답글 — 멤버와 게스트가 같은 경로로 들어와 PROJECT403 · SHARELINK403 · SHARELINK410 을 함께 적었습니다.
  • 공유 링크 생성 / 토글 — 프로젝트 멤버 검증을 거치므로 PROJECT403 입니다. 토큰으로 들어오는 경로만 410 을 냅니다.
  • 404getProjectOrThrow() 를 실제로 부르는 곳에만 붙였습니다. 멤버십만 확인하는 경로는 403 만 냅니다.

프론트 영향은 사실상 없습니다. FE 에서 code 로 분기하는 곳은 InviteAcceptPagePROJECT_MEMBER409 한 곳이고 이 코드는 바뀌지 않습니다. 나머지는 message 를 그대로 표시하고, code 타입이 | (string & {}) 로 열려 있어 새 코드가 들어와도 깨지지 않습니다.

4. 문서 검증 테스트

@ApiErrorCodes 를 훑어 도는 테스트라 애노테이션이 한 곳도 없으면 훑을 것이 없어 전부 통과합니다. 그래서 표기가 존재한다는 것 자체를 먼저 단언하고, 이어서 ① 적힌 코드가 실재하는지 ② 문서에 해당 상태의 예시로 실리는지 ③ 예시 값이 실제 실패 응답과 같은지 ④ 도메인 예시를 얹은 뒤에도 공통 예시가 남아 있는지를 검사합니다. 전체 149개 테스트 통과했습니다.

5. 성공 상태 코드 (커밋 9)

실제로 201 · 302 를 반환하는데 문서에는 200 으로 실린 엔드포인트가 4개 있었습니다(피드백 생성, 답글 생성, 회원가입, 구글 로그인 진입). 원인은 springdoc 이 @ResponseStatus 는 읽지만 ResponseEntity.status(...) 는 읽지 못한다는 것입니다. 201 을 반환하는 17곳 중 @ResponseStatus 를 쓴 14곳만 문서에 201 로 실려 개수가 정확히 갈립니다.

네 곳에 @ResponseStatus 를 더했습니다. 런타임 상태는 ResponseEntity 가 결정하므로 응답은 바뀌지 않고 문서만 맞춰집니다.

이 어긋남은 문서만 봐서는 잡을 수 없습니다. 문서의 성공 상태 코드 자체가 @ResponseStatus 에서 나오기 때문에 애노테이션을 지우면 문서도 따라 바뀌어 항상 통과합니다. 그래서 소스를 직접 읽어 ResponseEntity 로 상태를 지정한 핸들러는 @ResponseStatus 도 함께 갖는다는 규칙을 검사하는 테스트를 따로 뒀습니다.

6. 파라미터 설명 (커밋 10~12)

값 자체가 애노테이션뿐인 변경입니다. 런타임 영향은 없습니다.

  • 게스트 인증guestIdX-Guest-Token 이 게스트 등록 응답에서 함께 나온 짝이고 서버가 항상 대조한다는 점, 로그인 사용자는 둘 다 생략한다는 점을 적었습니다. 문서만 보면 남의 guestId 를 넣을 수 있는 것처럼 읽히던 부분입니다.
  • 공유 링크 토큰 — 토큰이 URL 경로에 드러나는 것이 의도된 설계임을 밝혔습니다. 링크를 아는 것 자체가 영상 한 편에 대한 열람 자격이고, UUID 이며 소유자가 끄거나 만료되면 SHARELINK410 으로 막힙니다.
  • 쿼리 파라미터cursor 는 이전 응답의 nextCursor 를 그대로 넣는 값인데 목록마다 기준이 다릅니다(공고 ID / 지원 ID / 관심 등록 ID, 피드백은 {재생지점초}_{피드백ID}). size 의 기본값·최댓값과 status · sort · 카테고리 · 지역 · 파트가 가질 수 있는 값도 함께 적었습니다.

남겨둔 것

리소스 부재는 공통 COMMON404 로 둡니다. 영상 / 피드백 / 채용 / 일정 / 알림의 "찾을 수 없음" 69곳은 URL 이 어떤 리소스인지 이미 말해주므로 도메인 코드를 더해도 얻는 정보가 없습니다. 하위 리소스가 프로젝트 자체의 부재와 겹치는 프로젝트 계열만 구분했습니다. 공고와 지원서가 한 요청에서 둘 다 404 가 될 수 있는 곳은 구분이 안 되지만, 둘 다 "없는 페이지" 로 처리되는 화면이라 두었습니다.

경로 변수가 없는 엔드포인트의 404 는 예시가 부족할 수 있습니다. 공통 404 는 경로 변수가 있을 때만 주입되는 규칙이라, projectId 를 본문으로 받는 POST /api/v1/schedules 같은 곳은 PROJECT404 만 실렸습니다. 이전에는 404 자체가 없었으므로 후퇴는 아닙니다.

✅ PR 체크리스트

  • PR 제목은 커밋 컨벤션을 따랐습니다.
  • 관련 이슈를 연결했습니다.
  • 변경 사항에 대한 테스트를 진행했습니다.

Summary by CodeRabbit

  • 문서화

    • Swagger API 문서에 엔드포인트별 오류 코드와 HTTP 상태 정보를 추가했습니다.
    • 커서, 페이지 크기, 검색어 등 요청 파라미터의 설명과 예시를 보강했습니다.
    • 게스트 인증, 토큰 쿠키, 공유 링크 사용 흐름을 명확히 안내합니다.
    • OpenAPI 문서에 도메인별 오류 응답 예시가 표시됩니다.
  • 개선

    • 피드백, 모집, 일정, 영상 관련 권한 및 중복 오류가 더욱 구체적인 코드로 제공됩니다.
    • 프로젝트 접근 권한과 작성자 전용 작업의 오류 안내가 명확해졌습니다.
  • 테스트

    • API 상태 코드와 OpenAPI 오류 문서의 정확성을 검증하는 테스트를 추가했습니다.

chazy-d added 12 commits August 12, 2026 03:26
영상, 피드백, 공유 링크, 일정 서비스가 "프로젝트의 활성 멤버인가" 를
직접 검사하고 CommonErrorCode.FORBIDDEN 을 던지고 있었다.

같은 조건을 ProjectAccessValidator 는 PROJECT403 으로 던진다.
검사식까지 existsByProjectIdAndUserIdAndLeftAtIsNull 로 동일한데
응답 코드만 갈라져서, 호출하는 쪽에서는 같은 실패가 두 가지 코드로 온다.

도메인 접두사 체계를 따라 PROJECT403 으로 맞춘다.
HTTP 상태는 403 그대로이고 code 문자열과 메시지만 바뀐다.
영상, 피드백, 일정 도메인에는 에러 코드 enum 자체가 없어서
도메인 고유의 실패까지 CommonErrorCode 로 나가고 있었다.
다음 커밋에서 교체할 코드를 먼저 정의한다.

채용은 enum 이 있으나 403 이 없어 두 개를 덧붙인다.
공고 작성자 검증과 지원서 열람 권한은 조건이 달라 코드를 나눈다.
CommonErrorCode.FORBIDDEN / CONFLICT 로 나가던 14곳을 도메인 코드로 바꾼다.

- 피드백/답글 작성자: FEEDBACK403, FEEDBACK_REPLY403
- 일정 작성자: SCHEDULE403
- 공고 작성자: RECRUITMENT403
- 지원서 열람 권한: APPLICATION403
- 영상 중복 등록, 참고 자료 중복 연결: VIDEO409, VIDEO_REFERENCE_FILE409

CSRF 필터의 403 은 도메인이 아닌 전역 보안 응답이라 COMMON403 으로 남긴다.
이로써 서비스 계층에서 CommonErrorCode.FORBIDDEN / CONFLICT 를 던지는 곳은 없다.

기존 응답은 전부 "권한이 없습니다" 한 문장이었는데,
이제 어떤 권한이 왜 없는지가 메시지에 드러난다.
403, 409, 410, 429 는 도메인 규칙에서만 나오기 때문에 요청 모양으로 추론할 수
없다. 공통 에러처럼 자동으로 붙이면 실제로 나지 않는 상태 코드까지 문서에
실리므로, 엔드포인트가 직접 밝힌 코드만 싣는다.

애노테이션은 enum 이 아니라 코드 문자열을 받는다. 자바 애노테이션 배열에는 서로
다른 enum 을 섞을 수 없어 ProjectErrorCode 와 CommonErrorCode 를 함께 적을 수
없기 때문이다. 대신 ErrorCodeRegistry 가 BaseCode 구현을 훑어 코드 문자열로
되찾을 수 있게 하고, 오타는 컴파일러 대신 문서 검증 테스트가 잡는다.

커스터마이저는 하나로 둔다. 공통 응답이 먼저 깔린 뒤에 도메인 예시가 얹혀야
같은 상태 코드에서 공통 예시가 밀려나지 않는데, OperationCustomizer 를 둘로
나눠 등록하면 springdoc 이 부르는 순서를 @order 로 정할 수 없었다. 실제로
도메인 쪽이 먼저 돌아 404 의 공통 예시가 통째로 빠졌다.
컨트롤러에서 서비스 호출을 따라가며 실제로 던지는 403 / 409 / 429 만 적었다.
전역 주입 대상인 400 / 401 / 404 / 413 / 500 은 범위 밖이다.

문서 검증 테스트는 @ApiErrorCodes 를 훑어 돌기 때문에 애노테이션이 한 곳도
없으면 훑을 것이 없어 전부 통과한다. 표기가 사라져도 조용히 초록불이 되므로
표기가 존재한다는 것 자체를 먼저 단언한다.
공고 작성자만 되는 것과 지원 본인도 되는 것을 나눠 적었다. 지원자 목록과 상태
변경은 RECRUITMENT403, 지원 상세와 첨부 다운로드는 APPLICATION403 이다.

공유 링크 생성과 토글은 프로젝트 멤버 검증을 거치므로 SHARELINK403 이 아니라
PROJECT403 이 나간다. 토큰으로 들어오는 경로만 410 을 낸다.
피드백과 답글은 멤버와 게스트가 같은 경로로 들어온다. 어느 쪽으로 들어오느냐에
따라 PROJECT403 과 SHARELINK403 / SHARELINK410 이 갈리므로 셋을 함께 적었다.
수정과 삭제만 작성자 검증이 더 붙는다.

일정 비공개 메모는 개인 일정이면 작성자만, 프로젝트 일정이면 멤버만 접근한다.
한쪽만 적으면 나머지 경로가 문서에서 빠져 둘 다 적었다.
404 는 상태 코드만 문서화돼 있고 예시는 항상 COMMON404 였다.
실제로는 getProjectOrThrow 등이 PROJECT404 를 던져 프론트가 문서대로
분기하면 맞지 않는 엔드포인트가 30 개가 넘었다.

도메인 404 를 실제로 던지는 경로만 골라 표기한다. 서비스 메서드를
따라가 호출 여부를 확인했고, 프로젝트 조회를 거치지 않는 엔드포인트에는
붙이지 않았다.

404 는 공통 응답이 이미 깔려 있는 유일한 상태라서, 이번 표기로
공통 예시와 도메인 예시를 나란히 싣는 병합 경로가 실제 문서에서
처음으로 동작한다. 지금까지 단위 테스트로만 검증되던 경로다.
공통 예시가 밀려나면 example 과 examples 가 섞이지 않아 기존 검증을
빠져나가므로, 문서 테스트를 하나 추가해 잠근다.
springdoc 은 @ResponseStatus 만 읽고 ResponseEntity.status(...) 는 읽지 않는다.
그래서 실제로는 201 을 반환하는 생성 API 세 곳과 302 로 리다이렉트하는
구글 로그인 진입이 문서에는 200 으로 실려 있었다.

런타임 상태는 ResponseEntity 가 결정하므로 동작은 바뀌지 않는다.

생성된 문서로는 이 어긋남을 잡을 수 없다. 문서의 성공 상태 코드 자체가
@ResponseStatus 에서 나오기 때문에 둘을 비교하면 항상 통과한다.
소스를 읽어 ResponseEntity 로 상태를 지정한 핸들러에 애노테이션이
함께 있는지 확인하는 테스트를 따로 두었다.
공유 링크 토큰이 URL 경로에 노출되는 것이 의도된 설계임을 명시하고,
게스트 등록 -> sessionToken 발급 -> X-Guest-Token 사용 순서를 태그 설명에 적었다.
guestId 와 X-Guest-Token 이 게스트 등록 응답에서 온 짝이라는 점,
로그인 사용자는 둘 다 생략한다는 점을 파라미터 설명에 적었다.
cursor 는 이전 응답의 nextCursor 를 그대로 넣는 값이고 목록마다 기준
ID 가 다르다. size 의 기본값·최댓값과 status·sort·필터 enum 이 가질 수
있는 값을 문서에서 바로 확인할 수 있게 했다.
@chazy-d chazy-d self-assigned this Aug 12, 2026
@chazy-d chazy-d added documents 문서 수정 chore 빌드, 설정, 기타 작업 labels Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a7f8a124-4c1b-4562-ad7b-2197cb68480f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

인증, 프로젝트, 피드백, 모집, 일정, 공유 링크, 영상 API에 도메인 오류 코드와 Swagger 설명을 추가했습니다. OpenAPI 오류 응답 자동 생성과 오류 코드 레지스트리, 관련 검증 테스트도 추가했습니다.

Changes

도메인 오류 코드 및 OpenAPI 문서화

Layer / File(s) Summary
오류 코드 레지스트리와 OpenAPI 응답 처리
src/main/java/com/slatto/global/config/*, src/main/java/com/slatto/global/response/code/*, src/test/java/com/slatto/global/config/*
@ApiErrorCodes를 기반으로 도메인 오류 코드를 레지스트리에 등록하고 OpenAPI 응답 예시에 반영합니다. 관련 문서와 응답 일치 검증을 추가했습니다.
도메인 오류 코드와 서비스 예외 교체
src/main/java/com/slatto/domain/{feedback,recruitment,schedule,video}/exception/*, src/main/java/com/slatto/domain/{feedback,recruitment,schedule,sharelink,video}/service/*, src/test/java/com/slatto/domain/recruitment/service/*
작성자 권한, 프로젝트 접근, 지원서 접근, 영상 중복, 참조 파일 중복 오류를 도메인 전용 코드로 변경했습니다.
인증·피드백·공유 링크 API 문서화
src/main/java/com/slatto/domain/{auth,feedback,sharelink}/controller/*
응답 상태, 오류 코드, 게스트 인증, 쿠키, 커서 및 조회 크기 설명을 Swagger 문서에 추가했습니다.
프로젝트·모집·일정 API 문서화
src/main/java/com/slatto/domain/{project,recruitment,notification,schedule}/controller/*
프로젝트 관련 엔드포인트와 모집·지원·알림·최근 활동·일정 엔드포인트에 오류 코드와 요청 파라미터 설명을 추가했습니다.
사용자·영상 API 문서화 및 상태 검증
src/main/java/com/slatto/domain/{user,video}/controller/*, src/test/java/com/slatto/global/config/ControllerResponseStatusTest.java
사용자 및 영상 API 문서를 확장하고, 상태를 직접 설정하는 컨트롤러 매핑에 @ResponseStatus가 있는지 검증하는 테스트를 추가했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: fix

Suggested reviewers: sangwon02, guingguing, young0206, kohseoyoung

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 도메인 에러 코드 통일과 Swagger 표기라는 PR의 주요 변경 사항을 정확히 요약합니다.
Description check ✅ Passed 관련 이슈, 작업 내용, 테스트 결과와 체크리스트를 모두 포함해 템플릿을 충족합니다.
Linked Issues check ✅ Passed #168의 에러 코드 통일, Swagger 주입, 상태 코드 수정, 파라미터 문서화 및 검증 테스트 목표를 모두 반영합니다.
Out of Scope Changes check ✅ Passed 변경 사항은 #168의 도메인 에러 코드 통일, API 문서화 및 검증 테스트 범위에 포함됩니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java`:
- Around line 42-44: FeedbackController and FeedbackDetailController의 8개 피드백·답글
엔드포인트에 각각 작업별 설명을 담은 `@Operation`(description = ...)을 추가하십시오.
FeedbackController.java의 42-44, 60-62, 76-78, 93-95행과
FeedbackDetailController.java의 42-45, 61-63, 83-85, 100-102행이 모두 변경 대상이며, 기존
`@Tag`(description = ...)만으로 대체하지 마십시오.

In `@src/main/java/com/slatto/domain/project/controller/ProjectController.java`:
- Line 114: Update the `@ApiErrorCodes` declarations for ProjectController.java
lines 114-114, RecruitmentApplicationController.java lines 48-48, and
RecruitmentController.java lines 175-175 to include PROJECT_COMPLETION400,
APPLICATION_FILE_LINK400, and RECRUITMENT_CLOSED_EDIT400 respectively, so the
documented OpenAPI responses match the actual 400 errors.

In
`@src/main/java/com/slatto/domain/sharelink/controller/ShareLinkController.java`:
- Around line 39-42: Update the `@ApiErrorCodes` annotation on the
ShareLinkController share-link creation endpoint to include "SHARELINK400"
alongside the existing error codes, documenting the 400 response for invalid
expiredAt values.

In `@src/test/java/com/slatto/global/config/ControllerResponseStatusTest.java`:
- Around line 51-62: Refactor the annotation check around previousMapping and
the missing `@ResponseStatus` validation to inspect each controller handler’s AST
node, using only annotations directly attached to that method. Exclude
annotations from neighboring handlers, while preserving the `@Hidden` skip
behavior and existing missing-location reporting.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a4fb6b6b-28a3-45c9-8066-6553c699ea0c

📥 Commits

Reviewing files that changed from the base of the PR and between b9438b0 and ce42e2d.

📒 Files selected for processing (45)
  • src/main/java/com/slatto/domain/auth/controller/AuthController.java
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackDetailController.java
  • src/main/java/com/slatto/domain/feedback/exception/FeedbackErrorCode.java
  • src/main/java/com/slatto/domain/feedback/service/FeedbackDetailService.java
  • src/main/java/com/slatto/domain/feedback/service/FeedbackService.java
  • src/main/java/com/slatto/domain/notification/controller/NotificationController.java
  • src/main/java/com/slatto/domain/notification/controller/RecentActivityController.java
  • src/main/java/com/slatto/domain/project/controller/ProjectController.java
  • src/main/java/com/slatto/domain/project/controller/ProjectFileController.java
  • src/main/java/com/slatto/domain/project/controller/ProjectInvitationController.java
  • src/main/java/com/slatto/domain/project/controller/ProjectMemberController.java
  • src/main/java/com/slatto/domain/project/controller/ProjectNoticeController.java
  • src/main/java/com/slatto/domain/recruitment/controller/MyRecruitmentController.java
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentApplicationController.java
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentApplicationFileController.java
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentController.java
  • src/main/java/com/slatto/domain/recruitment/exception/RecruitmentErrorCode.java
  • src/main/java/com/slatto/domain/recruitment/service/RecruitmentApplicationFileService.java
  • src/main/java/com/slatto/domain/recruitment/service/RecruitmentApplicationService.java
  • src/main/java/com/slatto/domain/recruitment/service/RecruitmentService.java
  • src/main/java/com/slatto/domain/schedule/controller/ScheduleController.java
  • src/main/java/com/slatto/domain/schedule/exception/ScheduleErrorCode.java
  • src/main/java/com/slatto/domain/schedule/service/ScheduleService.java
  • src/main/java/com/slatto/domain/sharelink/controller/ShareLinkController.java
  • src/main/java/com/slatto/domain/sharelink/service/ShareLinkService.java
  • src/main/java/com/slatto/domain/user/controller/PortfolioController.java
  • src/main/java/com/slatto/domain/user/controller/UserController.java
  • src/main/java/com/slatto/domain/video/controller/VideoController.java
  • src/main/java/com/slatto/domain/video/controller/VideoReferenceFileController.java
  • src/main/java/com/slatto/domain/video/controller/YoutubeController.java
  • src/main/java/com/slatto/domain/video/exception/VideoErrorCode.java
  • src/main/java/com/slatto/domain/video/service/VideoReferenceFileService.java
  • src/main/java/com/slatto/domain/video/service/VideoService.java
  • src/main/java/com/slatto/global/config/ApiErrorCodes.java
  • src/main/java/com/slatto/global/config/DomainErrorResponses.java
  • src/main/java/com/slatto/global/config/ErrorResponseExamples.java
  • src/main/java/com/slatto/global/config/SwaggerErrorResponseCustomizer.java
  • src/main/java/com/slatto/global/response/code/ErrorCodeRegistry.java
  • src/test/java/com/slatto/domain/recruitment/service/RecruitmentApplicationDetailIntegrationTest.java
  • src/test/java/com/slatto/domain/recruitment/service/RecruitmentApplicationFileServiceTest.java
  • src/test/java/com/slatto/global/config/ControllerResponseStatusTest.java
  • src/test/java/com/slatto/global/config/OpenApiDocumentationTest.java
  • src/test/java/com/slatto/global/config/SwaggerErrorResponseCustomizerTest.java
  • src/test/java/com/slatto/global/response/code/ErrorCodeRegistryTest.java

Comment on lines +42 to 44
@ResponseStatus(HttpStatus.CREATED)
@ApiErrorCodes({"PROJECT403", "SHARELINK403", "SHARELINK410"})
@PostMapping("/videos/{videoId}/feedbacks")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(FeedbackController|FeedbackDetailController|OpenApiDocumentationTest)\.(java|kt)$|ApiErrorCodes'

printf '%s\n' '--- controller outlines ---'
for f in $(git ls-files | rg 'src/main/java/com/slatto/domain/feedback/controller/Feedback(Controller|DetailController)\.java$'); do
  echo "### $f"
  ast-grep outline "$f" 2>/dev/null || true
done

printf '%s\n' '--- relevant controller source ---'
for f in $(git ls-files | rg 'src/main/java/com/slatto/domain/feedback/controller/Feedback(Controller|DetailController)\.java$'); do
  echo "### $f"
  cat -n "$f"
done

printf '%s\n' '--- documentation test and annotation references ---'
rg -n -C 5 'OpenApiDocumentationTest|Operation\.description|description.*Operation|`@Operation`|`@Tag`|ApiErrorCodes' --glob '*.java' --glob '*.kt' .

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu

rg -n -C 8 'OpenApiDocumentationTest|Operation\.description|`@Operation`|`@Tag`|ApiErrorCodes' --glob '*.java' --glob '*.kt' . | head -n 500

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

for f in $(git ls-files | rg 'src/main/java/com/slatto/domain/feedback/controller/Feedback(Controller|DetailController)\.java$'); do
  echo "### $f"
  cat -n "$f"
done

rg -n -C 8 'OpenApiDocumentationTest|Operation\.description|`@Operation`|`@Tag`|ApiErrorCodes' \
  --glob '*.java' --glob '*.kt' .

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- feedback operation annotations ---'
python3 - <<'PY'
from pathlib import Path
import re

files = [
    Path("src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java"),
    Path("src/main/java/com/slatto/domain/feedback/controller/FeedbackDetailController.java"),
]
for path in files:
    text = path.read_text()
    print(f"### {path}")
    for match in re.finditer(r'`@Operation`\s*\((.*?)\)\s*(?:@\w+(?:\([^)]*\))?\s*)*@(?:Post|Patch|Delete|Get)Mapping\("([^"]+)"\)', text, re.S):
        args, mapping = match.groups()
        summary = re.search(r'\bsummary\s*=\s*"([^"]*)"', args)
        description = re.search(r'\bdescription\s*=', args)
        print({
            "mapping": mapping,
            "summary": summary.group(1) if summary else "",
            "has_operation_description": bool(description),
        })
PY

printf '%s\n' '--- exact documentation-test logic ---'
sed -n '60,110p' src/test/java/com/slatto/global/config/OpenApiDocumentationTest.java

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 2844


8개 피드백·답글 작업에 @Operation(description = ...)을 추가하십시오. @Tag(description = ...)은 작업 설명을 대체하지 않습니다. OpenApiDocumentationTest는 설명이 없으면 실패합니다.

📍 Affects 2 files
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java#L42-L44 (this comment)
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java#L60-L62
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java#L76-L78
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java#L93-L95
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackDetailController.java#L42-L45
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackDetailController.java#L61-L63
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackDetailController.java#L83-L85
  • src/main/java/com/slatto/domain/feedback/controller/FeedbackDetailController.java#L100-L102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/feedback/controller/FeedbackController.java`
around lines 42 - 44, FeedbackController and FeedbackDetailController의 8개 피드백·답글
엔드포인트에 각각 작업별 설명을 담은 `@Operation`(description = ...)을 추가하십시오.
FeedbackController.java의 42-44, 60-62, 76-78, 93-95행과
FeedbackDetailController.java의 42-45, 61-63, 83-85, 100-102행이 모두 변경 대상이며, 기존
`@Tag`(description = ...)만으로 대체하지 마십시오.

`title` 은 생성·수정 요청 모두 필수라 실제로는 `kind` 만 이 조건에 걸린다.
"""
)
@ApiErrorCodes({"PROJECT403", "PROJECT_ADMIN403", "PROJECT404", "PROJECT_COMPLETED409"})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

@Operation 설명과 @ApiErrorCodes를 실제 오류 응답 목록과 일치시켜야 합니다.

DomainErrorResponses@ApiErrorCodes에 선언된 값만 OpenAPI 응답으로 병합합니다. 따라서 아래 세 endpoint의 실제 400 응답이 현재 Swagger에서 누락됩니다.

  • src/main/java/com/slatto/domain/project/controller/ProjectController.java#L114-L114: PROJECT_COMPLETION400을 추가합니다.
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentApplicationController.java#L48-L48: APPLICATION_FILE_LINK400을 추가합니다.
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentController.java#L175-L175: RECRUITMENT_CLOSED_EDIT400을 추가합니다.
📍 Affects 3 files
  • src/main/java/com/slatto/domain/project/controller/ProjectController.java#L114-L114 (this comment)
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentApplicationController.java#L48-L48
  • src/main/java/com/slatto/domain/recruitment/controller/RecruitmentController.java#L175-L175
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/slatto/domain/project/controller/ProjectController.java` at
line 114, Update the `@ApiErrorCodes` declarations for ProjectController.java
lines 114-114, RecruitmentApplicationController.java lines 48-48, and
RecruitmentController.java lines 175-175 to include PROJECT_COMPLETION400,
APPLICATION_FILE_LINK400, and RECRUITMENT_CLOSED_EDIT400 respectively, so the
documented OpenAPI responses match the actual 400 errors.

Comment on lines 39 to 42
@Operation(summary = "공유 링크 생성", description = "영상당 1개만 생성 가능하며, 이미 있으면 409를 반환합니다.")
@ResponseStatus(HttpStatus.CREATED)
@ApiErrorCodes({"PROJECT403", "SHARELINK409"})
@PostMapping("/videos/{videoId}/share-links")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd 'ShareLinkErrorCode.java' src/main/java -x sed -n '1,180p' {}

rg -n -C 5 \
  'INVALID_EXPIRED_AT|createShareLink\(' \
  src/main/java/com/slatto/domain/sharelink

Repository: SLAT-TO/SLATE-TO-BE

Length of output: 7296


SHARELINK400 오류 코드를 문서화하십시오.

expiredAt이 현재 시각과 같거나 이전이면 INVALID_EXPIRED_AT400 응답을 반환합니다. ShareLinkController.java:41@ApiErrorCodes"SHARELINK400"을 추가하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/slatto/domain/sharelink/controller/ShareLinkController.java`
around lines 39 - 42, Update the `@ApiErrorCodes` annotation on the
ShareLinkController share-link creation endpoint to include "SHARELINK400"
alongside the existing error codes, documenting the 400 response for invalid
expiredAt values.

Comment on lines +51 to +62
String annotations = String.join("\n", lines.subList(previousMapping(lines, mapping - 1) + 1, mapping));

// 문서에 노출되지 않는 핸들러는 어긋날 문서가 없다.
if (annotations.contains("@Hidden")) {
continue;
}

String location = controller.getFileName() + ":" + (line + 1);
checked.add(location);

if (!annotations.contains("@ResponseStatus")) {
missing.add(location);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

핸들러별 애노테이션 범위를 분리하세요.

Line 51은 이전 @*Mapping과 현재 @*Mapping 사이의 전체 텍스트를 검사합니다. 이전 핸들러에서 매핑 뒤에 선언한 @ResponseStatus가 포함될 수 있습니다. 그러면 현재 핸들러에 @ResponseStatus가 없어도 Line 61이 통과합니다.

컨트롤러 메서드를 AST로 식별하고, 해당 메서드에 연결된 애노테이션만 검사하세요. 이 테스트가 놓치면 실제 상태 코드와 OpenAPI 성공 상태 코드의 불일치를 다시 허용합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/slatto/global/config/ControllerResponseStatusTest.java`
around lines 51 - 62, Refactor the annotation check around previousMapping and
the missing `@ResponseStatus` validation to inspect each controller handler’s AST
node, using only annotations directly attached to that method. Exclude
annotations from neighboring handlers, while preserving the `@Hidden` skip
behavior and existing missing-location reporting.

DomainErrorResponses 는 @ApiErrorCodes 에 선언된 코드만 문서에 병합한다.
설명에 코드 이름을 적어두고 애노테이션에 넣지 않은 5개 엔드포인트는
해당 응답 예시가 Swagger 에서 통째로 빠져 있었다.

- 프로젝트 수정: PROJECT_COMPLETION400
- 공고 지원: APPLICATION_FILE_LINK400
- 공고 수정: RECRUITMENT_CLOSED_EDIT400
- 공유 링크 생성: SHARELINK400
- 회원 탈퇴: USER_WITHDRAW_PASSWORD401

400·401 은 공통 응답과 겹치지만 공통 예시를 밀어내지 않는다.
겹침 처리는 도메인 404 에 쓰던 경로를 그대로 탄다.

애노테이션 문서가 403·409·410·429 만 적는 자리라고 못 박고 있어
이번 누락의 빌미가 됐다. 상태 코드로 갈리지 않는다고 고쳤다.
피드백과 답글 8개 엔드포인트에 summary 만 있고 설명이 없었다.
회원과 게스트가 함께 쓰는 경로라 식별 규칙, 접근 조건, 본인 확인처럼
호출하는 쪽이 알아야 할 제약이 가장 많은데 코드를 열어야만 알 수 있었다.

설명이 없으면 깨지도록 만든 검증이 이 8개를 놓치고 있었다.
인증이 선택인 엔드포인트에는 안내 문구가 자동으로 붙는데,
그 문구가 채워지면서 설명을 한 줄도 적지 않아도 통과했다.
문구를 걷어내고 남은 것만 설명으로 세도록 고쳤다.
@chazy-d
chazy-d merged commit 367054b into main Aug 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore 빌드, 설정, 기타 작업 documents 문서 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CHORE: 도메인 에러 코드 통일 및 Swagger 표기

2 participants