统一错误目录
从 v0.10.0-preview 起,业务可以只返回逻辑错误标识,框架从项目目录取得 HTTP 状态、对外业务码与公开提示。目录不依赖具体业务、数据库、身份模型或网络层,也不定义固定返回体。
基本规则
key/Definitions的 map key 是唯一逻辑标识,例如USERNAME_INVALID、user.notFound。API 新目录允许项目自己的标识命名;旧error (...)声明保持原有大写标识规则。code是对外业务码,支持字符串、整数,同一个码可以由多个不同标识使用。JSON/YAML 和.api读取整数时保留 int64 精度。DefaultStatus/defaultStatus只为目录中未写状态的条目提供默认值,省略时为 400。项目可设为 200;单项Status/status优先。- 目录条目允许有响应体的最终 HTTP 状态:200..599,排除 204、205、304。成功输出与错误输出分开;HTTP 200 并不把错误变成成功。
- 数字码类型与范围遵守选中响应的
codeType(string/int32/int64;integer 是旧别名)。不同路由可以选择不同类型的公共响应与目录。 - 新目录声明完整公开提示;每条错误的业务码和提示优先于统一
errorCode/errorMessage兜底。旧的同标识codes/messages若与目录冲突,初始化或契约编译报错;相同值允许保留,便于逐步迁移。
手写项目
import (
omni "git.yuntuo.hk/k2safe/go-omni"
omnierrors "git.yuntuo.hk/k2safe/go-omni/errors"
"git.yuntuo.hk/k2safe/go-omni/response"
)
catalog := omnierrors.Catalog{
DefaultStatus: 200,
Definitions: map[string]omnierrors.Definition{
"USERNAME_INVALID": {Code: 400002, Message: "用户名格式不正确"},
"PASSWORD_INVALID": {Code: 400002, Message: "密码格式不正确"},
"FORBIDDEN": {Status: 403, Code: 403001, Message: "没有权限"},
},
}
app := omni.New(
omni.WithErrors(catalog),
omni.WithResponse(response.Config{
CodeType: "int32", SuccessCode: 0, ErrorCode: 9999,
MessageField: "msg", DataMode: "always",
}),
)
Service 返回 omnierrors.ByCode("USERNAME_INVALID"),或 .WithCause(err)。业务无需接收 App/HTTP Context。目录属于 App,初始化时校验并复制;后续修改原 map 不影响正在服务的应用。配置快照也不暴露内部 map。
NewRegistry(catalog) / MustRegistry(catalog) 可在 CLI/任务消费者等非 HTTP 入口使用;registry.Resolve(err) 返回独立的 *APIError,包含逻辑 Code、对外 BusinessCode、Status、Message 与内部 Cause。每次调用都创建新对象;不要自行将一个可变错误对象保存为共享单例再调用 WithCause。
原有 New(status, key, message) 等明确错误也能被同标识目录条目覆盖;未在目录中的明确错误沿用原状态与提示。未配置目录时,原有行为保持。Go 目录允许 Definition.Code == nil 以继续使用旧 Codes/errorCode 映射;新 .api errorCatalog 要求显式 code。
文件配置
下面是生成项目的 etc/config.yaml 结构;直接 config.LoadFile 加载框架配置时去掉外面的 server。手写 main 需显式将加载结果传入 WithConfig。
server:
errors:
defaultStatus: 200
definitions:
USERNAME_INVALID:
code: 400002
message: 用户名格式不正确
FORBIDDEN:
status: 403
code: 403001
message: 没有权限
response:
codeType: int32
successCode: 0
errorCode: 9999
messageField: msg
dataMode: always
重复 JSON/YAML key、未知字段、非法状态、非整数数值及类型/范围错误会拒绝加载。目录不支持运行时热更新;修改后重新启动,避免请求与文档在运行期间漂移。
.api:定义一次,生成调用与文档
完整示例见 errors.api。语法:
errorCatalog CommonErrors (
defaultStatus: 200
errors: {
"UsernameInvalid": {"key": "USERNAME_INVALID", "code": 400002, "message": "用户名格式不正确"},
"PasswordInvalid": {"key": "PASSWORD_INVALID", "code": 400002, "message": "密码格式不正确"}
}
)
目录可放进公共 .api 再 import;也可以拆成多个命名目录,各自设置默认状态。生成器合并定义时检查目录名、函数名、逻辑标识冲突。Catalog 是生成包的保留函数名。目录对象的键是生成的 UpperCamelCase 函数名,条目的 key 是运行时逻辑标识,二者用途不同。
在 @server 的 errors 或路由 @errors 中引用函数名,响应仍通过原有 response 声明选择:
@server (
response: CommonResponse
errors: UsernameInvalid, PasswordInvalid
)
service account-api {
@handler token
post /token returns (TokenResponse)
}
生成结果包括:
apierrors.UsernameInvalid()等无参数函数,每次返回独立错误,支持 WithCause。apierrors.CodeUsernameInvalid等逻辑标识常量。apierrors.Catalog():整个契约的可复制运行时目录,方便手写路由复用。- 自动绑定到生成路由的目录:只包含该路由通过
@errors/@server errors声明的错误,在 App 全局中间件之前生效,支持认证提前返回和缓冲克隆。 - OpenAPI 的错误响应与示例。同一 HTTP 200 下用
anyOf同时描述成功 DTO 和错误体;多个错误共用数字码时保持多个示例;raw 成功体与失败公共体也分开描述。
业务直接 return TokenResponse{}, apierrors.UsernameInvalid()。也可以返回 omnierrors.ByCode(apierrors.CodeUsernameInvalid);使用标识引用时,当前 App/路由目录必须包含该错误。不要把“函数已生成”视作“所有路由都已声明这个错误”:新增实际错误后仍应补相应 @errors,保持文档准确。
生成路由的目录和响应由 .api 管理;YAML 或 App 默认策略不覆盖它。手写路由可在 main 用 omni.WithErrors(apierrors.Catalog()) 并选择兼容的 WithResponse。新项目和重复生成都会自动绑定路由目录,无需手动修改 main;已有手写文件保持不动。
特殊协议与失败信息
response.WithErrors(catalog, encoder) / MustWithErrors 可绑定路由专用策略,再通过 HandleWithResponse 注册。内置模板校验目录码类型与旧映射;自定义 Encoder 自行决定最终 HTTP 状态、媒体类型和字段结构,可将失败转换成第三方要求的文本/XML 回执。一个 Encoder 作用域不叠加不同目录;外层空目录或相同目录不会覆盖已有目录。
Encoder 的 response.Result 包含逻辑 Code、对外 BusinessCode、公开 Message、原始 Err 及 Failed。使用 result.IsError() 判断失败,不要仅判断 Status >= 400。RawJSON 仅让成功结果保持未包装,HTTP 200 的失败仍使用选定错误策略。c.Text / c.XML / c.JSON 等直接输出及已提交的标准 Handler 响应不会被目录重新包装。
启用访问日志时,记录最终写出的 HTTP 状态,并保留 failed、errorKey 和可用的 businessCode。内置指标将已处理的业务失败计入 Errors;链路记录保留 HTTP 200 和业务失败标记。标准中间件桥接和本地/共享幂等重放保留失败标识;已有缓存条目没有新增元数据时无法追溯识别业务失败,等待正常过期即可。
未知的 ByCode 标识、普通 error、panic 默认走 INTERNAL_ERROR,公开兜底不暴露 cause。项目可显式配置 INTERNAL_ERROR 条目,但目录 DefaultStatus 本身不会改变系统兜底。启用访问日志后,可通过原始 error 追查未知标识和内部原因。
升级
go get git.yuntuo.hk/k2safe/go-omni@v0.10.0-preview
go install git.yuntuo.hk/k2safe/go-omni/cmd/go-omni@v0.10.0-preview
go-omni api generate --dry-run
go-omni api generate
go test ./...
先升级框架依赖,再用同版 CLI 生成;新生成代码最低依赖 v0.10.0-preview。原来的错误构造函数、旧 error (...)、Codes/Messages 与已生成代码可继续运行。把业务中散落的状态/提示逐步替换为生成函数属于项目迁移工作,框架不会自动改写业务逻辑。