// Package apperror 定义 FuelLog API 统一错误类型与错误码。 // // 设计目标: // - Handler / Service / Repository 各层都能用 errors.Is / errors.As 判定错误 // - 序列化为 FuelLog 约定的 JSON 错误响应:{ "code": "...", "message": "..." } // - 错误码稳定、可被前端 i18n 映射 package apperror import ( "errors" "fmt" "net/http" ) // Code 是稳定的、可枚举的错误码。 type Code string const ( CodeInternal Code = "INTERNAL" // 兜底 500,未预期 CodeInvalidArg Code = "INVALID_ARGUMENT" // 参数校验失败 400 CodeNotFound Code = "NOT_FOUND" // 资源不存在 404 CodeConflict Code = "CONFLICT" // 资源冲突 409 CodeUnauthorized Code = "UNAUTHORIZED" // 401(预留) CodeForbidden Code = "FORBIDDEN" // 403(预留) CodeUnavailable Code = "UNAVAILABLE" // 503(预留) ) // Error 是 FuelLog 的统一错误类型。 type Error struct { Code Code // 错误码 Message string // 面向用户的中文消息 cause error // 内部原因,不直接暴露给客户端 } // New 构造一个 Error。 func New(code Code, message string) *Error { return &Error{Code: code, Message: message} } // Wrap 包装一个底层错误,保留链路。 func Wrap(code Code, message string, cause error) *Error { return &Error{Code: code, Message: message, cause: cause} } // Error 实现 error 接口。 func (e *Error) Error() string { if e.cause != nil { return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.cause) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } // Unwrap 让 errors.Is / errors.As 能穿透到 cause。 func (e *Error) Unwrap() error { return e.cause } // HTTPStatus 返回该错误对应的 HTTP 状态码。 func (e *Error) HTTPStatus() int { switch e.Code { case CodeInvalidArg: return http.StatusBadRequest case CodeNotFound: return http.StatusNotFound case CodeConflict: return http.StatusConflict case CodeUnauthorized: return http.StatusUnauthorized case CodeForbidden: return http.StatusForbidden case CodeUnavailable: return http.StatusServiceUnavailable default: return http.StatusInternalServerError } } // Cause 返回底层错误,可能为 nil。 func (e *Error) Cause() error { return e.cause } // As 是 errors.As 的便捷封装,用于在调用栈中提取 *Error。 func As(err error) (*Error, bool) { var e *Error if errors.As(err, &e) { return e, true } return nil, false } // IsCode 判定 err 链中是否包含指定错误码。 func IsCode(err error, code Code) bool { if e, ok := As(err); ok { return e.Code == code } return false }