feat: 보상 재시도 + TX 확정 대기 + 에러 포맷 통일 + 품질 고도화
- 보상 지급 실패 시 즉시 재시도(3회 backoff) + DB 기록 + 백그라운드 워커 재시도 - WaitForTx 폴링으로 블록체인 TX 확정 대기, SendTxAndWait 편의 메서드 - chain 트랜잭션 코드 중복 제거 (userTx/operatorTx 헬퍼, 50% 감소) - AppError 기반 에러 응답 포맷 통일 (8개 코드, 전 핸들러 마이그레이션) - TX 에러 분류 + 한국어 사용자 메시지 매핑 (11가지 패턴) - player 서비스 테스트 20개 + chain WaitForTx 테스트 10개 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package apperror
|
||||
import "fmt"
|
||||
|
||||
// AppError is a structured application error with an HTTP status code.
|
||||
// JSON response format: {"error": "<code>", "message": "<human-readable message>"}
|
||||
type AppError struct {
|
||||
Code string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
@@ -23,11 +24,36 @@ func Wrap(code string, message string, status int, cause error) *AppError {
|
||||
|
||||
// Common errors
|
||||
var (
|
||||
ErrBadRequest = &AppError{Code: "bad_request", Message: "잘못된 요청입니다", Status: 400}
|
||||
ErrUnauthorized = &AppError{Code: "unauthorized", Message: "인증이 필요합니다", Status: 401}
|
||||
ErrForbidden = &AppError{Code: "forbidden", Message: "권한이 없습니다", Status: 403}
|
||||
ErrNotFound = &AppError{Code: "not_found", Message: "리소스를 찾을 수 없습니다", Status: 404}
|
||||
ErrConflict = &AppError{Code: "conflict", Message: "이미 존재합니다", Status: 409}
|
||||
ErrBadRequest = &AppError{Code: "bad_request", Message: "잘못된 요청입니다", Status: 400}
|
||||
ErrInternal = &AppError{Code: "internal_error", Message: "서버 오류가 발생했습니다", Status: 500}
|
||||
ErrRateLimited = &AppError{Code: "rate_limited", Message: "요청이 너무 많습니다", Status: 429}
|
||||
ErrInternal = &AppError{Code: "internal_error", Message: "서버 오류가 발생했습니다", Status: 500}
|
||||
)
|
||||
|
||||
// BadRequest creates a 400 error with a custom message.
|
||||
func BadRequest(message string) *AppError {
|
||||
return &AppError{Code: "bad_request", Message: message, Status: 400}
|
||||
}
|
||||
|
||||
// Unauthorized creates a 401 error with a custom message.
|
||||
func Unauthorized(message string) *AppError {
|
||||
return &AppError{Code: "unauthorized", Message: message, Status: 401}
|
||||
}
|
||||
|
||||
// NotFound creates a 404 error with a custom message.
|
||||
func NotFound(message string) *AppError {
|
||||
return &AppError{Code: "not_found", Message: message, Status: 404}
|
||||
}
|
||||
|
||||
// Conflict creates a 409 error with a custom message.
|
||||
func Conflict(message string) *AppError {
|
||||
return &AppError{Code: "conflict", Message: message, Status: 409}
|
||||
}
|
||||
|
||||
// Internal creates a 500 error with a custom message.
|
||||
func Internal(message string) *AppError {
|
||||
return &AppError{Code: "internal_error", Message: message, Status: 500}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user