99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package domain
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
)
|
||
|
||
// ErrorCode 是跨层稳定的结构化错误码,HTTP 中间件据此统一映射状态码与文案。
|
||
type ErrorCode string
|
||
|
||
const (
|
||
CodeAuthExpired ErrorCode = "AUTH_EXPIRED"
|
||
CodeAdminAuthRequired ErrorCode = "ADMIN_AUTH_REQUIRED"
|
||
CodeRateLimited ErrorCode = "RATE_LIMITED"
|
||
CodeNotFound ErrorCode = "NOT_FOUND"
|
||
CodeDriverError ErrorCode = "DRIVER_ERROR"
|
||
CodeValidation ErrorCode = "VALIDATION"
|
||
CodeNotImplement ErrorCode = "NOT_IMPLEMENT"
|
||
CodePermissionDenied ErrorCode = "PERMISSION_DENIED"
|
||
CodeInternal ErrorCode = "INTERNAL"
|
||
)
|
||
|
||
type codeMeta struct {
|
||
http int
|
||
msg string
|
||
}
|
||
|
||
var codeTable = map[ErrorCode]codeMeta{
|
||
CodeAuthExpired: {401, "账号认证已失效,请重新授权"},
|
||
CodeAdminAuthRequired: {401, "需要管理员权限"},
|
||
CodeRateLimited: {429, "请求过于频繁,请稍后重试"},
|
||
CodeNotFound: {404, "文件不存在"},
|
||
CodeDriverError: {502, "网盘服务异常"},
|
||
CodeValidation: {400, "参数错误"},
|
||
CodeNotImplement: {501, "该操作不支持"},
|
||
CodePermissionDenied: {403, "权限不足"},
|
||
CodeInternal: {500, "服务内部错误"},
|
||
}
|
||
|
||
// AppError 是带错误码的应用错误,可被 errors.As / errors.AsType 提取。
|
||
type AppError struct {
|
||
Code ErrorCode
|
||
Message string
|
||
Details map[string]any
|
||
Err error
|
||
}
|
||
|
||
// WithDetails 附加结构化细节(用于 API 响应 details 字段),返回自身便于链式调用。
|
||
func (e *AppError) WithDetails(d map[string]any) *AppError {
|
||
e.Details = d
|
||
return e
|
||
}
|
||
|
||
func (e *AppError) Error() string {
|
||
if e.Err != nil {
|
||
return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err)
|
||
}
|
||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||
}
|
||
|
||
func (e *AppError) Unwrap() error { return e.Err }
|
||
|
||
// HTTPStatus 返回错误码对应的 HTTP 状态码。
|
||
func (e *AppError) HTTPStatus() int {
|
||
if m, ok := codeTable[e.Code]; ok {
|
||
return m.http
|
||
}
|
||
return 500
|
||
}
|
||
|
||
func newAppErr(code ErrorCode, msg string, err error) *AppError {
|
||
if msg == "" {
|
||
if m, ok := codeTable[code]; ok {
|
||
msg = m.msg
|
||
}
|
||
}
|
||
return &AppError{Code: code, Message: msg, Err: err}
|
||
}
|
||
|
||
// Errf 构造带默认中文文案的错误。
|
||
func Errf(code ErrorCode) *AppError { return newAppErr(code, "", nil) }
|
||
|
||
// Errorf 构造自定义文案的错误。
|
||
func Errorf(code ErrorCode, format string, a ...any) *AppError {
|
||
return newAppErr(code, fmt.Sprintf(format, a...), nil)
|
||
}
|
||
|
||
// Wrap 用错误码包裹底层错误,保留可 Unwrap 的链路。
|
||
func Wrap(code ErrorCode, err error) *AppError { return newAppErr(code, "", err) }
|
||
|
||
// AsAppError 从错误链提取 AppError(Go 1.26 亦可用 errors.AsType[*AppError])。
|
||
func AsAppError(err error) (*AppError, bool) {
|
||
var ae *AppError
|
||
if errors.As(err, &ae) {
|
||
return ae, true
|
||
}
|
||
return nil, false
|
||
}
|