Skip to content

feat(filter): GraphQL 전용 예외 필터 (P1-3)#117

Merged
chanwoo7 merged 1 commit into
developfrom
refactor/gql-exception-filter
May 28, 2026
Merged

feat(filter): GraphQL 전용 예외 필터 (P1-3)#117
chanwoo7 merged 1 commit into
developfrom
refactor/gql-exception-filter

Conversation

@chanwoo7
Copy link
Copy Markdown
Member

@chanwoo7 chanwoo7 commented May 27, 2026

Summary

GraphQL 컨텍스트에서 발생한 에러에 extensions 를 부착해 FE 에서 분기·추적이 가능하게 한다.
NestJS 글로벌 필터는 host type 별로 1 회만 매칭되므로, 단일 진입점인 HttpExceptionFilter 에서 graphql context 시 신규 GraphQLExceptionFilter 에 위임한다.

FE 영향: 비파괴 확장 (협의 #4) — 기존 errors[].message 응답 형태는 그대로, extensions 필드만 추가.

Scope

  • 신규 GraphQLExceptionFilter (src/global/filters/graphql-exception.filter.ts)
    • format(exception, host): GraphQLError — HTTP 예외를 GraphQLError 로 변환하며 extensions 부착
    • mapStatusToCode(status) — HTTP status → GraphQL code 매핑
      • 400 → BAD_USER_INPUT / 401 → UNAUTHENTICATED / 403 → FORBIDDEN
      • 404 → NOT_FOUND / 그 외 → INTERNAL_SERVER_ERROR
    • extensions 부착 항목: code / statusCode / requestId / operation / fieldName
    • requestId 는 incoming x-request-id 헤더 우선, 없으면 신규 UUID
    • txError 로 구조화 로그 기록 (LogContext.GRAPHQL)
  • HttpExceptionFilter 수정
    • 생성자에 GraphQLExceptionFilter 의존성 추가
    • catch 진입 직후 host.getType<GqlContextType>() === 'graphql' 분기 → gqlFilter.format() 결과 반환
    • HTTP 경로 동작은 변경 없음 (REST 응답 동일)
  • main.ts: GraphQLExceptionFilter 인스턴스를 생성해 HttpExceptionFilter 에 주입
  • 신규/갱신 spec
    • graphql-exception.filter.spec.ts (16 cases — status→code 매핑 / 4xx 예외별 extensions / requestId / mutation / log)
    • global-exception.filter.spec.ts — 위임 분기 케이스 갱신 (graphql → gqlFilter.format, rpc → super.catch)

FE 협의 #4 — 액션 요청

GraphQL 에러 응답의 extensions 에 다음 필드가 추가 된다 (기존 필드 제거 X, 비파괴 확장).

Before / After

Before:

{
  "errors": [
    { "message": "Not Found" }
  ],
  "data": null
}

After:

{
  "errors": [
    {
      "message": "Not Found",
      "extensions": {
        "code": "NOT_FOUND",
        "statusCode": 404,
        "requestId": "9b1deb4d-...-43b6-9c4e-...",
        "operation": "query",
        "fieldName": "sellerProduct"
      }
    }
  ],
  "data": null
}

code 매핑표

HTTP status GraphQL code
400 BAD_USER_INPUT
401 UNAUTHENTICATED
403 FORBIDDEN
404 NOT_FOUND
그 외 (500/예상 외) INTERNAL_SERVER_ERROR

FE 액션 권장 (필수 아님)

  • extensions.code 기반 분기 (예: UNAUTHENTICATED → 재로그인 유도)
  • extensions.requestId 를 에러 UI/Sentry tag 등에 표시 → 백엔드 로그와 cross-reference
  • 기존 에러 처리는 그대로 작동 (비파괴 확장이므로 액션 불필요)

Impact

Test plan

  • graphql-exception.filter.spec (16 cases) 통과
    • status → code 매핑 6 케이스
    • 4xx 예외 (BadRequest/Unauthorized/Forbidden/NotFound) 4 케이스
    • 일반 Error 500 / 비 Error throw 500
    • x-request-id 헤더 우선 / fallback UUID
    • mutation operation
    • txError 구조화 로그
  • global-exception.filter.spec — 위임 분기 갱신, 기존 HTTP 케이스 그대로 통과
  • pre-push gate (yarn validate) 로컬 통과
  • CI 통과 확인
  • FE 에 협의 test: 이메일로 기존 계정을 찾지 않도록 upsertUserByOidcIdentity 테스트 수정 #4 안내 (머지 직전)

GraphQL 컨텍스트의 에러에 extensions 을 부착해 FE 에서 분기/추적 가능하게 한다.
협의 #4 (비파괴 확장) 대상.

- 신규 GraphQLExceptionFilter (helper class)
  - format(exception, host) → GraphQLError with extensions
  - extensions: code / statusCode / requestId / operation / fieldName
  - HTTP status → GraphQL code 매핑 (mapStatusToCode)
    - 400 BAD_USER_INPUT / 401 UNAUTHENTICATED / 403 FORBIDDEN
    - 404 NOT_FOUND / 그 외 INTERNAL_SERVER_ERROR
  - txError 로 구조화 로그(LogContext.GRAPHQL) 기록
- HttpExceptionFilter: graphql context 시 GraphQLExceptionFilter 에 위임
  - NestJS 글로벌 필터는 host type 별 1 회만 매칭되므로 단일 진입점에서 분기
- main.ts: GraphQLExceptionFilter 인스턴스를 HttpExceptionFilter 에 주입

FE 영향: 비파괴 확장. 기존 errors[].message 는 그대로, extensions 필드만 추가.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e03b6ca-4105-4321-a52e-205d8d8785ae

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/gql-exception-filter

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b854fa2ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +63 to +70
this.logger.txError({
userId,
requestId,
request: gqlRequest,
error: { statusCode: status, message, stack },
processingTimeInMs: duration,
context: LogContext.GRAPHQL,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid double-logging GraphQL resolver errors

For GraphQL errors thrown from resolvers/services, the globally registered GqlLoggingInterceptor already records a txError in its tap.error path before rethrowing, and then this filter records another txError for the same request/error here. That means ordinary resolver failures will produce duplicate transaction-error logs with the same requestId/field, which can inflate error counts and confuse alerting; either the interceptor or this filter should own error logging for that path.

Useful? React with 👍 / 👎.

@github-actions
Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.33% 3542/3639
🟢 Branches 93.53% 1128/1206
🟢 Functions 93.62% 646/690
🟢 Lines 97.56% 3242/3323

Test suite run success

1191 tests passing in 143 suites.

Report generated by 🧪jest coverage report action from 9b854fa

@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chanwoo7 chanwoo7 merged commit 19588e8 into develop May 28, 2026
10 checks passed
@chanwoo7 chanwoo7 deleted the refactor/gql-exception-filter branch May 28, 2026 11:04
chanwoo7 added a commit that referenced this pull request May 28, 2026
fix(filter): GraphQL 에러 중복 로깅 제거 (#117 follow-up)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant