BREAKING CHANGE: 相对 v0.0.1 的 API 变更 - 删除全部 Set* 方法,改为返回新值的 With*;错误值不可变,包级常量可并发使用 - 错误值不再持有 context,只保存语言标识(WithLang),渲染时再传 ctx - 删除兼容别名 Errf / ErrfKV / Msgf / NewLanguage / NewStruct 与空操作 Copy() - Msgf 改为 MsgWith,NewErrorf 改为 NewErrorWith;ErrorLanguage 不再实现 ErrorInterface 修复 - Copy 浅拷贝共享 format map,并发下 fatal error: concurrent map writes - 包级常量被 Set* 污染,请求之间互相改写错误文案 - langx.RegisterCode / RegisterTrans 覆盖注册表后,运行时错误码退化为默认码、文案退化为裸 key - 未注册 key 静默返回默认码 + 裸 key,现返回 500 + 未注册提示 - NewError(nil, ...) 与非 string 的 Accept-Language 触发 panic 新增 - Wrap / Unwrap / errors.Is 按 key 匹配、调用栈(惰性解析)、%+v 输出 cause 与栈 - 错误常量自带默认文案兜底 + errorx 侧常量注册表(不依赖 langx 可变状态) - 根 README、3 个可运行示例、17 个断言测试(语句覆盖率 97.2%)、基准测试 依赖 - github.com/yuninks/langx v0.0.7 -> v0.1.0
111 lines
3.1 KiB
Go
111 lines
3.1 KiB
Go
// Package main 演示 errorx 的基础用法。
|
|
//
|
|
// 运行:go run ./example
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/yuninks/errorx"
|
|
"github.com/yuninks/langx"
|
|
)
|
|
|
|
// 1) 定义错误常量:key + 业务码 + 默认文案。
|
|
// 占位符写成 #name#,渲染的时候再替换。
|
|
var (
|
|
ErrUserNotFound = errorx.New("user_not_found", 10001, "用户不存在")
|
|
ErrParamInvalid = errorx.New("param_invalid", 10002, "参数错误: #field#")
|
|
ErrDB = errorx.New("db_error", 50001, "数据库繁忙")
|
|
)
|
|
|
|
func init() {
|
|
// 2) 补充其它语言的文案。
|
|
// errorx.New 已经把默认文案注册进默认语言(langx 默认是 zh),这里只补 en。
|
|
langx.AppendTrans("en", map[string]string{
|
|
"user_not_found": "user not found",
|
|
"param_invalid": "invalid parameter: #field#",
|
|
"db_error": "database is busy",
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
demoBasic()
|
|
demoFormat()
|
|
demoI18n()
|
|
demoWrap()
|
|
demoStack()
|
|
}
|
|
|
|
func demoBasic() {
|
|
fmt.Println()
|
|
fmt.Println("=== 1. 定义与取值 ===")
|
|
|
|
e := ErrUserNotFound.Err() // Err() 返回 ErrorInterface,它本身就是 error
|
|
fmt.Println("error :", e)
|
|
fmt.Println("GetCode() :", e.GetCode())
|
|
fmt.Println("GetKey() :", e.GetKey())
|
|
|
|
// 错误常量本身也实现了 error,可以直接 return / 打印
|
|
var raw error = ErrUserNotFound
|
|
fmt.Println("常量当error:", raw)
|
|
}
|
|
|
|
func demoFormat() {
|
|
fmt.Println()
|
|
fmt.Println("=== 2. 占位符 ===")
|
|
|
|
fmt.Println("ErrKV :", ErrParamInvalid.ErrKV("field", "age"))
|
|
fmt.Println("ErrWith :", ErrParamInvalid.ErrWith(map[string]string{"field": "name"}))
|
|
fmt.Println("MsgKV :", ErrParamInvalid.MsgKV(context.Background(), "field", "phone"))
|
|
|
|
// 错误值是不可变的:派生不会影响错误常量,也不会影响别的请求
|
|
derived := ErrParamInvalid.ErrKV("field", "age")
|
|
fmt.Println("常量未被改:", ErrParamInvalid)
|
|
fmt.Println("派生值 :", derived)
|
|
}
|
|
|
|
func demoI18n() {
|
|
fmt.Println()
|
|
fmt.Println("=== 3. 多语言 ===")
|
|
|
|
ctx := context.Background()
|
|
enCtx := langx.SetCtxLang(ctx, "en") // 也可以由 HTTP 头 Accept-Language 决定
|
|
|
|
fmt.Println("zh :", ErrUserNotFound.Msg(ctx))
|
|
fmt.Println("en :", ErrUserNotFound.Msg(enCtx))
|
|
fmt.Println("固定 en :", ErrUserNotFound.Err().WithLang("en"))
|
|
fmt.Println("未注册语言回退:", ErrUserNotFound.Msg(langx.SetCtxLang(ctx, "ja")))
|
|
}
|
|
|
|
func demoWrap() {
|
|
fmt.Println()
|
|
fmt.Println("=== 4. 包装底层错误 ===")
|
|
|
|
sqlErr := errors.New("dial tcp 127.0.0.1:3306: connect: connection refused")
|
|
err := ErrDB.Wrap(sqlErr)
|
|
|
|
fmt.Println("对外文案 :", err) // 只暴露业务文案,不泄露底层细节
|
|
fmt.Println("errors.Is :", errors.Is(err, ErrDB))
|
|
fmt.Println("errors.Is :", errors.Is(err, sqlErr))
|
|
fmt.Println("底层错误 :", errors.Unwrap(err))
|
|
|
|
var ei errorx.ErrorInterface
|
|
if errors.As(err, &ei) {
|
|
fmt.Println("errors.As :", ei.GetCode())
|
|
}
|
|
}
|
|
|
|
func demoStack() {
|
|
fmt.Println()
|
|
fmt.Println("=== 5. 调用栈(错误日志输出)===")
|
|
err := loadConfig()
|
|
fmt.Printf("%+v", err)
|
|
fmt.Println()
|
|
}
|
|
|
|
func loadConfig() error {
|
|
return errorx.ErrInternal.Wrap(errors.New("config.yaml: no such file or directory"))
|
|
}
|