samchun-gemini / utils /error_responses.py
JHyeok5's picture
Upload folder using huggingface_hub
0f3460d verified
"""
Standardized Error Response Models
ν†΅μΌλœ μ—λŸ¬ 응닡 ν˜•μ‹
λͺ¨λ“  API μ—”λ“œν¬μΈνŠΈμ—μ„œ μΌκ΄€λœ μ—λŸ¬ 응닡 제곡
"""
from typing import Optional, Any, Dict
from fastapi import HTTPException, status
from pydantic import BaseModel
class ErrorDetail(BaseModel):
"""
μ—λŸ¬ 상세 정보
Attributes:
code: μ—λŸ¬ μ½”λ“œ (예: "TOKEN_001", "VALIDATION_ERROR")
message: μ‚¬μš©μž μΉœν™”μ  μ—λŸ¬ λ©”μ‹œμ§€
field: μ—λŸ¬κ°€ λ°œμƒν•œ ν•„λ“œ (선택)
details: μΆ”κ°€ 정보 (선택)
"""
code: str
message: str
field: Optional[str] = None
details: Optional[Dict[str, Any]] = None
class ErrorResponse(BaseModel):
"""
ν‘œμ€€ μ—λŸ¬ 응닡
Attributes:
success: 항상 False
error: μ—λŸ¬ 상세 정보
"""
success: bool = False
error: ErrorDetail
def create_error_response(
code: str,
message: str,
status_code: int = status.HTTP_400_BAD_REQUEST,
field: Optional[str] = None,
details: Optional[Dict[str, Any]] = None
) -> HTTPException:
"""
ν‘œμ€€ μ—λŸ¬ 응닡 생성
Args:
code: μ—λŸ¬ μ½”λ“œ
message: μ—λŸ¬ λ©”μ‹œμ§€
status_code: HTTP μƒνƒœ μ½”λ“œ
field: μ—λŸ¬ ν•„λ“œ (선택)
details: μΆ”κ°€ 정보 (선택)
Returns:
HTTPException
"""
return HTTPException(
status_code=status_code,
detail=ErrorResponse(
error=ErrorDetail(
code=code,
message=message,
field=field,
details=details
)
).model_dump()
)
# 사전 μ •μ˜λœ μ—λŸ¬ 생성 ν•¨μˆ˜
def bad_request_error(
message: str,
code: str = "BAD_REQUEST",
field: Optional[str] = None
) -> HTTPException:
"""400 Bad Request μ—λŸ¬"""
return create_error_response(
code=code,
message=message,
status_code=status.HTTP_400_BAD_REQUEST,
field=field
)
def unauthorized_error(
message: str = "인증이 ν•„μš”ν•©λ‹ˆλ‹€",
code: str = "UNAUTHORIZED"
) -> HTTPException:
"""401 Unauthorized μ—λŸ¬"""
return create_error_response(
code=code,
message=message,
status_code=status.HTTP_401_UNAUTHORIZED
)
def forbidden_error(
message: str = "μ ‘κ·Ό κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€",
code: str = "FORBIDDEN"
) -> HTTPException:
"""403 Forbidden μ—λŸ¬"""
return create_error_response(
code=code,
message=message,
status_code=status.HTTP_403_FORBIDDEN
)
def not_found_error(
resource: str = "λ¦¬μ†ŒμŠ€",
code: str = "NOT_FOUND"
) -> HTTPException:
"""404 Not Found μ—λŸ¬"""
return create_error_response(
code=code,
message=f"{resource}λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€",
status_code=status.HTTP_404_NOT_FOUND
)
def conflict_error(
message: str,
code: str = "CONFLICT"
) -> HTTPException:
"""409 Conflict μ—λŸ¬"""
return create_error_response(
code=code,
message=message,
status_code=status.HTTP_409_CONFLICT
)
def rate_limit_error(
retry_after: int = 60,
code: str = "RATE_LIMIT_EXCEEDED"
) -> HTTPException:
"""429 Too Many Requests μ—λŸ¬"""
return create_error_response(
code=code,
message="μš”μ²­ ν•œλ„λ₯Ό μ΄ˆκ³Όν–ˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.",
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
details={"retry_after": retry_after}
)
def internal_server_error(
message: str = "μ„œλ²„ λ‚΄λΆ€ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€",
code: str = "INTERNAL_ERROR",
details: Optional[Dict[str, Any]] = None
) -> HTTPException:
"""500 Internal Server Error"""
return create_error_response(
code=code,
message=message,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
details=details
)
def service_unavailable_error(
message: str = "μ„œλΉ„μŠ€λ₯Ό μΌμ‹œμ μœΌλ‘œ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€",
code: str = "SERVICE_UNAVAILABLE"
) -> HTTPException:
"""503 Service Unavailable μ—λŸ¬"""
return create_error_response(
code=code,
message=message,
status_code=status.HTTP_503_SERVICE_UNAVAILABLE
)
# 도메인별 μ—λŸ¬ μ½”λ“œ
class ErrorCodes:
"""μ—λŸ¬ μ½”λ“œ μƒμˆ˜"""
# 인증/κΆŒν•œ
UNAUTHORIZED = "UNAUTHORIZED"
FORBIDDEN = "FORBIDDEN"
TOKEN_EXPIRED = "TOKEN_EXPIRED"
# 토큰 κ΄€λ ¨
TOKEN_BALANCE_FAILED = "TOKEN_001"
TOKEN_PRODUCT_NOT_FOUND = "TOKEN_002"
TOKEN_INSUFFICIENT = "TOKEN_003"
TOKEN_CHARGE_FAILED = "TOKEN_004"
TOKEN_BONUS_FAILED = "TOKEN_005"
PAYMENT_RECORD_FAILED = "TOKEN_006"
# μŠ€ν† λ¦¬ κ΄€λ ¨
STORY_NOT_FOUND = "STORY_001"
STORY_CREATE_FAILED = "STORY_002"
STORY_UPDATE_FAILED = "STORY_003"
# μ½”μŠ€ κ΄€λ ¨
COURSE_NOT_FOUND = "COURSE_001"
COURSE_CREATE_FAILED = "COURSE_002"
COURSE_GENERATION_FAILED = "COURSE_003"
# μ†Œμ…œ κ΄€λ ¨
LIKE_FAILED = "SOCIAL_001"
REVIEW_CREATE_FAILED = "SOCIAL_002"
REVIEW_DELETE_FAILED = "SOCIAL_003"
# 데이터 검증
VALIDATION_ERROR = "VALIDATION_ERROR"
INVALID_UUID = "INVALID_UUID"
INVALID_COORDINATES = "INVALID_COORDINATES"
# 일반
NOT_FOUND = "NOT_FOUND"
CONFLICT = "CONFLICT"
BAD_REQUEST = "BAD_REQUEST"
RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
INTERNAL_ERROR = "INTERNAL_ERROR"
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE"