Help us shape the future of Go! Take the 2026 Go Developer Survey

apperr

package
v1.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: AGPL-3.0 Imports: 2 Imported by: 0

Documentation

Overview

Package apperr 提供应用层错误码基础设施,对标 storage.PageResult[T], 用于 Wails API 统一返回 *Result[T],前端通过 ErrCode 分类反馈。

设计目标:

  • 作为基础设施:提供泛型 Result[T] 包装,让需要分类错误反馈的 Wails API 有统一形态
  • 零侵入:不强制重构现有 API,只在新增 API 上启用;旧 API 保持原签名
  • 可扩展:错误码用字符串而非 int 枚举,便于后续新增场景直接加值
  • 解耦:apperr 只定义 Coder 接口与 Code 常量,不 import 任何业务/协议包; 各领域 error 类型实现 Coder 接口,CodeFromError 用 errors.As 提取接口统一分发, 新领域接入无需改动 apperr

错误码按模块分区组织(通用 / githubapi / llm)。同样语义(如 404)在不同模块 有不同错误码字符串(githubapi.not_found vs llm.not_found),便于前端针对模块做 差异化反馈:githubapi 404 通常表示 skill 被移除,llm 404 表示模型不存在。

使用示例:

func (a *App) ListRemoteSkills(...) *apperr.Result[*storage.PageResult[remote.RemoteSkillMeta]] {
    all, err := a.remoteService.ListRemoteSkills(a.ctx, false)
    if err != nil {
        return apperr.Err[*storage.PageResult[remote.RemoteSkillMeta]](err)
    }
    // ...
    return apperr.Ok(page)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BusinessError

type BusinessError struct {
	CodeVal Code // apperr.CodeInvalid / apperr.CodeNotFound / ...
	Msg     string
	Cause   error
}

BusinessError 是无领域专属字段的业务错误通用载体。 实现 Coder 接口,供 CodeFromError 直接读取 Code。

判断标准:后端是否需要领域专属字段(如 githubapi.Error.ResetAt / llm.APIError.Retryable)。 需要→领域 error;不需要→BusinessError。

注意:字段名 CodeVal 而非 Code,是为了避免与 Coder 接口的 Code() 方法同名冲突 (Go 不允许同类型字段与方法同名)。

警示:勿用 BusinessError 包装实现了 Coder 的领域 error——errors.As 会先匹配外层 BusinessError,底层领域 Code 被遮蔽。想保留领域分类用 fmt.Errorf("...: %w", err) 包装。

func NewInvalid

func NewInvalid(msg string) *BusinessError

NewInvalid 构造入参非法类业务错误。

func NewNotFound

func NewNotFound(msg string) *BusinessError

NewNotFound 构造业务资源不存在类业务错误。

func (*BusinessError) Code

func (e *BusinessError) Code() Code

Code 实现 Coder 接口。 自保:若直接用 struct literal 构造绕过 NewInvalid/NewNotFound,CodeVal 字段未赋值取零值 CodeOK, 这里降级为 CodeInternal,防止错误被静默吞成成功(呼应 CodeFromError 的 CodeOK 不变量)。

func (*BusinessError) Error

func (e *BusinessError) Error() string

Error 实现 error 接口。Cause 非 nil 时拼接 Cause 文本,便于追踪原始错误。

func (*BusinessError) Unwrap

func (e *BusinessError) Unwrap() error

Unwrap 暴露 Cause,配合 errors.Is / errors.As 穿透包装链。

type Code

type Code string

Code 是应用层错误码,作为前端契约的稳定标识。 空字符串 "" 表示成功(零值),其他值对应不同错误类别。 一旦发布即成为前端契约,后续不可改名(只能新增)。

const (
	// CodeOK 无错误(默认零值)。Result[T] 零值的 ErrCode 为空,前端 if (res.err_code) 自然 falsy 检查。
	CodeOK Code = ""
	// CodeInternal 其他未分类错误(默认 fallback)。
	CodeInternal Code = "internal"
	// CodeInvalid 入参非法(target 不是 user/novel、novelID=0 等)。
	CodeInvalid Code = "invalid"
	// CodeConflict 目标资源在确认和写入之间发生变化。
	CodeConflict Code = "conflict"
)

通用错误码(跨模块共享)

const (
	// CodeGitHubAPINetwork 网络层失败:超时 / 连接拒绝 / DNS / context 取消。
	CodeGitHubAPINetwork Code = "githubapi.network"
	// CodeGitHubAPIRateLimited rate limit 触发:GitHub 403+Remaining:0、429。
	CodeGitHubAPIRateLimited Code = "githubapi.rate_limited"
	// CodeGitHubAPINotFound 资源不存在(404,如 skill 被移除)。
	CodeGitHubAPINotFound Code = "githubapi.not_found"
	// CodeGitHubAPIForbidden 权限拒绝(403 非 rate limit)。
	CodeGitHubAPIForbidden Code = "githubapi.forbidden"
	// CodeGitHubAPIOther 其他未分类的 GitHub API 错误。
	CodeGitHubAPIOther Code = "githubapi.other"
)

githubapi 模块错误码

const (
	// CodeLLMRateLimited LLM 调用触发 429。
	CodeLLMRateLimited Code = "llm.rate_limited"
	// CodeLLMNotFound LLM 模型不存在(404)。
	CodeLLMNotFound Code = "llm.not_found"
	// CodeLLMForbidden LLM 调用权限拒绝(403)。
	CodeLLMForbidden Code = "llm.forbidden"
	// CodeLLMServerError LLM 服务端 5xx,归类为可重试服务端问题。
	CodeLLMServerError Code = "llm.server_error"
	// CodeLLMClientError LLM 客户端 4xx(非 429/404/403),归类为请求侧问题。
	CodeLLMClientError Code = "llm.client_error"
)

llm 模块错误码

const (
	// CodeNotFound 业务资源不存在(跨模块通用)。
	CodeNotFound Code = "not_found"
)

通用业务错误码补充

func CodeFromError

func CodeFromError(err error) Code

CodeFromError 从 error 提取应用层错误码。 调度策略:1 次 errors.As 提取 Coder 接口,遍历 Unwrap 链找实现 Coder 的 error。 同一个语义(如 404)在不同模块前端反馈可能不同,故错误码带模块前缀, 具体映射逻辑由各领域 error 的 Code() 方法实现,apperr 不感知领域类型。

不变量:非 nil err 永不返回 CodeOK。

  • nil err → CodeOK
  • 实现 Coder 且 Code() 返回非 CodeOK → 返回该 Code
  • 实现 Coder 但 Code() 误返回 CodeOK → 降级 CodeInternal,避免错误被静默吞成成功
  • 未实现 Coder → CodeInternal

注意:remote.Service 用 fmt.Errorf("remote: ...: %w", err) 包装, errors.As 能穿透多层 wrap 直达底层,比字符串前缀匹配可靠。

type Coder

type Coder interface {
	Code() Code
}

Coder 由携带应用层错误码的 error 实现。 各领域 error 类型实现此接口,CodeFromError 用 errors.As 提取接口统一分发, apperr 无需 import 任何业务/协议包。

type Empty

type Empty = struct{}

Empty 用于无业务数据返回的场景(如 InstallRemoteSkill)。 类型别名而非新定义类型,便于 Wails 绑定生成器输出干净的 TS 类型。

type Result

type Result[T any] struct {
	Data    T      `json:"data"`              // 业务数据(失败时为零值)
	ErrCode Code   `json:"err_code"`          // 空字符串表示成功
	ErrMsg  string `json:"err_msg,omitempty"` // 人类可读错误描述(仅失败时存在)
}

Result 是 Wails API 的统一返回包装,泛型 T 表示业务数据类型。

成功时 ErrCode="",失败时 Data 为零值、ErrCode 非 ""、ErrMsg 含可读描述。 始终序列化为 HTTP 200,业务错误体现在 ErrCode 字段,避免前端用字符串匹配区分错误类型。

T 可以是 *storage.PageResult[X]、string、apperr.Empty 等任意类型。

func Err

func Err[T any](err error) *Result[T]

Err 包装失败结果,自动通过 CodeFromError 推断错误码。 ErrMsg 直接使用 err.Error() 原文,前端按 ErrCode 选择对应的 i18n 文案展示。

func Ok

func Ok[T any](data T) *Result[T]

Ok 包装成功结果。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL