errorx

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2026 License: MIT Imports: 6 Imported by: 0

README

errorx

errorx 是 Aisphere Kernel 的统一错误语义包。它是 Kernel 中唯一的业务错误包, 完全替代旧的 github.com/aisphereio/kernel/errors

新手上路:只看本文件即可上手。需要深度细节时再翻其他文档(见末尾"文档地图")。


1. 为什么需要 errorx

在 errorx 之前,业务错误散落为 errors.New("xxx") / fmt.Errorf(...) / panic(err), 导致 HTTP 响应、gRPC 响应、日志、审计、指标、Worker 重试各自写一套错误处理逻辑, 错误码不稳定、不可观测、不可被 AI 和系统理解。

errorx 的核心契约:业务错误必须有稳定的 error_code,并携带 HTTP/gRPC/retryable/metadata 等元信息,让所有消费方从同一个错误对象中提取语义。

errorx (只定义语义)
  ↓ 稳定契约 (Code / CodedError interface)
logx     → Fields(err)         提取日志字段
httpx    → HTTPStatusOf(err)   映射 HTTP 响应
grpcx    → GRPCCodeOf(err)     映射 gRPC status
auditx   → CodeOf(err)         记录失败原因
metricsx → MetricsLabels(err)  低基数指标
workerx  → RetryableOf(err)    判断是否重试

errorx 本身不打印日志、不写 HTTP 响应、不做审计、不上报指标。它只定义语义,其他模块只消费。


2. 30 秒上手

package service

import "github.com/aisphereio/kernel/errorx"

const ErrSkillNotFound errorx.Code = "AIHUB_SKILL_NOT_FOUND"

func GetSkill(ctx context.Context, id string) (*Skill, error) {
    skill, err := repo.Find(ctx, id)
    if errors.Is(err, sql.ErrNoRows) {
        return nil, errorx.NotFound(ErrSkillNotFound, "技能不存在",
            errorx.WithMetadata("skill_id", id),
            errorx.WithPublicMetadata("resource", "skill"),
        )
    }
    if err != nil {
        return nil, errorx.Wrap(err, "AIHUB_SKILL_QUERY_FAILED",
            errorx.WithMessage("查询技能失败"),
            errorx.WithRetryable(true),
        )
    }
    return skill, nil
}

HTTP 响应自动变为:

HTTP/1.1 404 Not Found
Content-Type: application/json
{
  "code": "AIHUB_SKILL_NOT_FOUND",
  "message": "技能不存在",
  "metadata": { "resource": "skill" }
}

3. 构造器速查

场景 构造器 HTTP gRPC retryable
参数校验失败 BadRequest 400 InvalidArgument false
未登录 Unauthorized 401 Unauthenticated false
无权限 Forbidden 403 PermissionDenied false
资源不存在 NotFound 404 NotFound false
资源已存在/冲突 Conflict 409 AlreadyExists false
请求超时(本服务) RequestTimeout 408 DeadlineExceeded false
限流 TooManyRequests 429 ResourceExhausted true
客户端断开 ClientClosed 499 Canceled false
内部错误 Internal 500 Internal false
上游不可用 Unavailable 503 Unavailable true
上游超时 Timeout 504 DeadlineExceeded true
errorx.BadRequest("REQUEST_VALIDATE_FAILED", "请求参数校验失败")
errorx.Unauthorized("AUTH_TOKEN_MISSING", "缺少授权令牌")
errorx.Forbidden("IAM_PERMISSION_DENIED", "权限被拒绝")
errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
errorx.Conflict("AIHUB_SKILL_ALREADY_EXISTS", "技能已存在")
errorx.RequestTimeout("REQUEST_TIMEOUT", "请求超时")
errorx.TooManyRequests("RATE_LIMIT_EXCEEDED", "请求过于频繁")
errorx.ClientClosed("CLIENT_CLOSED", "客户端断开连接")
errorx.Internal("AIHUB_INTERNAL_ERROR", "服务器内部错误")
errorx.Unavailable("MODEL_UPSTREAM_UNAVAILABLE", "模型上游不可用")
errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型上游超时")

InternalInternalServer 是别名;TimeoutGatewayTimeout 是别名;ClientClosedClientClosedRequest 是别名。


4. Option 列表

构造时按需附加:

errorx.WithMessage(msg)                  // 覆盖默认 message
errorx.WithHTTPStatus(503)               // 覆盖默认 HTTP 状态
errorx.WithGRPCCode("Unavailable")       // 覆盖默认 gRPC code
errorx.WithRetryable(true)               // 覆盖默认 retryable
errorx.WithCause(err)                    // 包装底层错误(保留 errors.Is/As 链)
errorx.WithMetadata("skill_id", id)      // 内部 metadata(日志/审计用,不返回前端)
errorx.WithPublicMetadata("resource", "skill")  // 公开 metadata(可返回前端)
errorx.WithRequestID("req_123")          // 请求 ID
errorx.WithTraceID("trace_abc")          // 链路追踪 ID
errorx.WithCategory(errorx.CategoryAuth) // 错误分类
errorx.WithSeverity(errorx.SeverityError) // 日志严重级别
errorx.WithStack()                       // 捕获调用栈(仅建议用于 500 错误)

5. 包装底层错误

Wrap 保留底层 cause,支持 errors.Is / errors.As

skill, err := repo.Find(ctx, id)
if err != nil {
    return nil, errorx.Wrap(err, "AIHUB_SKILL_QUERY_FAILED",
        errorx.WithMessage("查询技能失败"),
        errorx.WithRetryable(true),
        errorx.WithMetadata("skill_id", id),
    )
}

// errors.Is(wrapped, originalErr) == true
// errors.As(wrapped, &errorx.Error{}) == true

Wrap(nil, ...) 返回 nil,调用方无需 nil 检查:

return errorx.Wrap(repo.MaybeError(), "AIHUB_SKILL_QUERY_FAILED")  // repo 返回 nil 时整个表达式为 nil

6. 第三方错误兼容

errorx 通过 errors.As 识别第三方错误,不 import 第三方包。识别的接口:

Code() errorx.Code
ErrorCode() string
HTTPStatus() int
StatusCode() int
GRPCCode() string
Retryable() bool
Metadata() map[string]any
PublicMetadata() map[string]any
RequestID() string
TraceID() string
Category() errorx.Category
Severity() errorx.Severity
Stack() []errorx.Frame

例如 GoFr 风格的 StatusCode() 错误会被自动识别:

type gofrNotFound struct{}
func (gofrNotFound) Error() string    { return "not found" }
func (gofrNotFound) StatusCode() int  { return 404 }

err := gofrNotFound{}
errorx.HTTPStatusOf(err)  // 404
errorx.CodeOf(err)        // NOT_FOUND

7. 检查辅助函数

消费方(logx/httpx/auditx/metricsx/workerx)应使用这些函数提取语义,不要直接类型断言

errorx.CodeOf(err)            // 提取错误码(nil → OK;未知 → INTERNAL_ERROR)
errorx.MessageOf(err)         // 提取消息
errorx.HTTPStatusOf(err)      // 提取 HTTP 状态码(nil → 200)
errorx.GRPCCodeOf(err)        // 提取 gRPC code 字符串
errorx.RetryableOf(err)       // 是否可重试
errorx.MetadataOf(err)        // 内部 metadata(防御性拷贝)
errorx.SafeMetadataOf(err)    // 脱敏后的 metadata(password/token/secret → [REDACTED])
errorx.PublicMetadataOf(err)  // 公开 metadata
errorx.RequestIDOf(err)       // 请求 ID
errorx.TraceIDOf(err)         // 链路追踪 ID
errorx.CategoryOf(err)        // 错误分类
errorx.SeverityOf(err)        // 严重级别
errorx.StackOf(err)           // 调用栈
errorx.Fields(err)            // 日志字段(map[string]any)
errorx.MetricsLabels(err)     // 指标标签(低基数 map[string]string)

便捷谓词:

errorx.IsBadRequest(err)
errorx.IsUnauthorized(err)
errorx.IsForbidden(err)
errorx.IsNotFound(err)
errorx.IsConflict(err)
errorx.IsRequestTimeout(err)
errorx.IsTooManyRequests(err)
errorx.IsClientClosedRequest(err)
errorx.IsInternal(err)
errorx.IsUnavailable(err)
errorx.IsTimeout(err)
errorx.IsCode(err, errorx.Code("AIHUB_SKILL_NOT_FOUND"))

8. Metadata 安全规则

errorx 区分三层 metadata 暴露:

函数 用途 是否脱敏
原始 Metadata() / MetadataOf() 日志/审计/调试
脱敏 SafeMetadataOf() 安全日志 ✅ password/token/secret → [REDACTED]
公开 PublicMetadata() / PublicMetadataOf() HTTP/gRPC 响应 仅显式声明的字段

脱敏关键词(出现在 key 中即脱敏):

password / passwd / pwd / token / secret / authorization
cookie / credential / private_key / apikey / api_key

铁律:敏感数据只能放 Metadata,绝不能放 PublicMetadata

// ✅ 正确
errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
    errorx.WithMetadata("raw_db_error", err.Error()),          // 内部用
    errorx.WithPublicMetadata("resource", "skill"),            // 前端可见
)

// ❌ 错误:把 token 放进公开 metadata
errorx.Unauthorized("AUTH_TOKEN_INVALID", "token 无效",
    errorx.WithPublicMetadata("token", userToken),             // 泄漏!
)

9. 错误码命名规范

格式:{DOMAIN}_{RESOURCE}_{REASON},全大写蛇形。

✅ AIHUB_SKILL_NOT_FOUND
✅ IAM_TOKEN_INVALID
✅ MODEL_UPSTREAM_TIMEOUT
✅ DB_TRANSACTION_FAILED

❌ skillNotFound        (驼峰)
❌ skill-not-found      (连字符)
❌ not_found            (过于宽泛)
❌ SKILL_123_NOT_FOUND  (包含动态 ID)
❌ ERROR / FAILED       (无意义)

动态信息必须放 metadata,不能放 code

// ❌ 错误
errorx.NotFound(errorx.Code("SKILL_"+skillID+"_NOT_FOUND"), "技能不存在")

// ✅ 正确
errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
    errorx.WithMetadata("skill_id", skillID),
)

校验:

const ErrSkillNotFound errorx.Code = "AIHUB_SKILL_NOT_FOUND"

func TestErrorCodes(t *testing.T) {
    if !errorx.IsValidCode(ErrSkillNotFound) {
        t.Fatal("invalid error code")
    }
}

10. Retryable 与 Worker 重试

默认 retryable 规则:

Code retryable
TOO_MANY_REQUESTS true
SERVICE_UNAVAILABLE true
TIMEOUT true
其他 false

业务可覆盖:

// 默认 false,但业务确认可重试
return errorx.Internal("AIHUB_DB_TRANSACTION_FAILED", "数据库事务失败",
    errorx.WithRetryable(true),
)

// Worker 侧判断
if errorx.RetryableOf(err) {
    return workerx.Retry(err)
}
return workerx.Fail(err)

11. 调试与 %+v

Error() 返回安全 message(可返回前端)。fmt.Printf("%+v", err) 输出完整调试信息:

err := errorx.Internal("AIHUB_SKILL_QUERY_FAILED", "查询技能失败",
    errorx.WithCause(errors.New("pq: connection refused")),
    errorx.WithRequestID("req_123"),
)

fmt.Println(err)
// 查询技能失败

fmt.Printf("%+v\n", err)
// error_code=AIHUB_SKILL_QUERY_FAILED message="查询技能失败" http_status=500 grpc_code=Internal retryable=false category=internal severity=error request_id=req_123 trace_id= metadata={} public_metadata={} cause=pq: connection refused

12. 测试与验收

# 单元测试
go test ./errorx -v
go test ./errorx -race
go test ./errorx -cover
go test ./errorx -bench=.

# 模糊测试
go test ./errorx -run=^$ -fuzz=FuzzNewError -fuzztime=30s

# 可运行示例
go run ./examples/errorx-basic
go run ./examples/errorx-http

# 完整验收
make test-errorx
make verify-errorx

13. 禁止事项(AI 必读)

业务代码(handler/service/repository)禁止:

return errors.New("skill not found")              // ❌ 裸 error
return fmt.Errorf("create failed: %w", err)       // ❌ fmt.Errorf
return nil, err                                   // ❌ 透传底层错误
panic(err)                                        // ❌ panic

替代:

return errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
    errorx.WithMetadata("skill_id", id),
)
return errorx.Wrap(err, "AIHUB_SKILL_CREATE_FAILED",
    errorx.WithMessage("创建技能失败"),
)

测试代码中可以使用 errors.New / fmt.Errorf 构造 fixture。


14. 文档地图

errorx 的文档分为四类,按需查阅:

快速上手
├── 本文件 (errorx/README.md)              ← 你正在看的,单一入口
├── errorx/doc.go                          ← go doc 输出源
└── errorx/example_test.go                 ← Go 标准示例(go test -v 可看输出)

深度规范(架构师/PR review 时看)
├── docs/design/errorx.md                  ← 1254 行设计规范,覆盖 26 章
└── docs/contracts/errorx.md               ← 不可破坏契约 + 验收命令

AI 编码指南(AI 写业务代码时看)
├── docs/ai/errorx.md                      ← 合并版 AI 指南(含速查/食谱/禁用模式)
└── AGENTS.md                              ← 项目级 AI 规则

验收与运维(CI/CD 时看)
├── docs/process/errorx-acceptance-checklist.md  ← 静态/单元/集成验收清单
├── docs/process/errorx-test-report.md           ← 测试报告
└── docs/process/module-acceptance.md            ← 通用模块验收流程

可运行示例
├── examples/errorx-basic/                 ← 最小示例:Wrap + Inspect
└── examples/errorx-http/                  ← HTTP handler 示例:返回 JSON 响应

优先级:日常开发只看本 README + docs/ai/errorx.md 即可。 其他文档按场景查阅,无需通读。


15. 设计哲学一句话

错误必须有 code。 错误必须可映射。 错误必须可观测。 错误必须可被 AI 和系统理解。 errorx 只定义语义,不负责日志、不负责响应、不负责审计。

Documentation

Overview

Package errorx defines Aisphere Kernel's shared error semantics.

errorx is deliberately transport-neutral and depends only on the Go standard library. It is the foundation consumed by logx, httpx, grpcx, auditx, metricsx and workerx.

Package errorx defines Aisphere Kernel's standard error semantics.

errorx is the ONLY runtime/business error package in Kernel. It replaces the old Kratos-derived Kernel errors package.

Design principle

errorx only DEFINES error semantics. It does NOT log, write HTTP responses, emit metrics, record audit events, or call gRPC. Other Kernel modules (logx, httpx, grpcx, auditx, metricsx, workerx) CONSUME errorx through stable inspect helpers such as CodeOf, HTTPStatusOf, RetryableOf, Fields, and MetricsLabels.

errorx depends only on the Go standard library.

30-second quickstart

func GetSkill(ctx context.Context, id string) (*Skill, error) {
    skill, err := repo.Find(ctx, id)
    if errors.Is(err, sql.ErrNoRows) {
        return nil, errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
            errorx.WithMetadata("skill_id", id),
            errorx.WithPublicMetadata("resource", "skill"),
        )
    }
    if err != nil {
        return nil, errorx.Wrap(err, "AIHUB_SKILL_QUERY_FAILED",
            errorx.WithMessage("查询技能失败"),
            errorx.WithRetryable(true),
        )
    }
    return skill, nil
}

Constructors

Use semantic constructors instead of manually setting HTTP/gRPC status:

errorx.BadRequest("REQUEST_VALIDATE_FAILED", "请求参数校验失败")     // 400
errorx.Unauthorized("AUTH_TOKEN_MISSING", "缺少授权令牌")            // 401
errorx.Forbidden("IAM_PERMISSION_DENIED", "权限被拒绝")             // 403
errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")               // 404
errorx.Conflict("AIHUB_SKILL_ALREADY_EXISTS", "技能已存在")         // 409
errorx.RequestTimeout("REQUEST_TIMEOUT", "请求超时")                // 408
errorx.TooManyRequests("RATE_LIMIT_EXCEEDED", "请求过于频繁")       // 429
errorx.ClientClosed("CLIENT_CLOSED", "客户端断开连接")              // 499
errorx.Internal("AIHUB_INTERNAL_ERROR", "服务器内部错误")           // 500
errorx.Unavailable("MODEL_UPSTREAM_UNAVAILABLE", "模型上游不可用")  // 503
errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型上游超时")            // 504

Options

Append metadata, cause, retryable, etc. via options:

errorx.Wrap(err, "AIHUB_SKILL_QUERY_FAILED",
    errorx.WithMessage("查询技能失败"),
    errorx.WithRetryable(true),
    errorx.WithMetadata("skill_id", id),          // internal use
    errorx.WithPublicMetadata("resource", "skill"), // safe for clients
    errorx.WithRequestID("req_123"),
    errorx.WithTraceID("trace_abc"),
    errorx.WithStack(),
)

Inspect helpers (consumed by logx/httpx/grpcx/auditx/metricsx/workerx)

Do NOT type-assert on *Error. Use inspect helpers which are nil-safe and recognize wrapped third-party errors via errors.As:

errorx.CodeOf(err)            // AIHUB_SKILL_NOT_FOUND (nil → OK, unknown → INTERNAL_ERROR)
errorx.HTTPStatusOf(err)      // 404 (nil → 200)
errorx.GRPCCodeOf(err)        // NotFound
errorx.RetryableOf(err)       // false
errorx.Fields(err)            // map[string]any for logx
errorx.MetricsLabels(err)     // map[string]string low-cardinality labels for metricsx
errorx.IsNotFound(err)        // true (predicate shortcut)

Error code naming

Format: {DOMAIN}_{RESOURCE}_{REASON}, uppercase snake_case.

AIHUB_SKILL_NOT_FOUND       // good
IAM_TOKEN_INVALID           // good
MODEL_UPSTREAM_TIMEOUT      // good
skillNotFound               // bad: camelCase
SKILL_123_NOT_FOUND         // bad: dynamic ID in code

Dynamic values MUST go into metadata, never into the error code:

errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
    errorx.WithMetadata("skill_id", skillID),  // dynamic value here
)

Metadata safety

errorx distinguishes three metadata layers:

Metadata()         — internal use, returned as defensive copy
SafeMetadataOf()   — redacted copy (password/token/secret → [REDACTED])
PublicMetadata()   — explicitly safe for HTTP/gRPC responses

NEVER put tokens, passwords, cookies, or private keys into PublicMetadata.

Forbidden in business code

Handler/service/repository code MUST NOT use:

errors.New("skill not found")
fmt.Errorf("create failed: %w", err)
panic(err)

Use errorx constructors or Wrap instead.

Debugging with %+v

Error.Error returns a safe message. Use fmt.Printf("%+v", err) for full debug output including code, http_status, grpc_code, retryable, category, severity, request_id, trace_id, metadata, and cause.

Further reading

See errorx/README.md for the single-source-of-truth user guide, and docs/ai/errorx.md for the AI coding recipe.

Example (BusinessAuditRecord)

ExampleBusiness_auditRecord shows how auditx extracts error fields for audit log entries.

package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Forbidden("AIHUB_SKILL_DELETE_DENIED", "权限被拒绝",
		errorx.WithMetadata("subject_id", "user_123"),
		errorx.WithMetadata("resource", "aihub:skill:demo"),
		errorx.WithRequestID("req_xyz"),
	)

	auditRecord := map[string]string{
		"action":        "aihub.skill.delete",
		"result":        "deny",
		"error_code":    errorx.CodeOf(err).String(),
		"error_message": errorx.MessageOf(err),
		"request_id":    errorx.RequestIDOf(err),
	}
	fmt.Println(auditRecord["result"])
	fmt.Println(auditRecord["error_code"])
}
Output:
deny
AIHUB_SKILL_DELETE_DENIED
Example (BusinessAuthzDenied)

ExampleBusiness_authzDenied shows the standard pattern for permission denied, including the resource / action in metadata for audit.

package main

import (
	"context"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := checkPermission(context.Background(), "user_123", "aihub:skill:demo", "skill.delete")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.MetadataOf(err)["subject_id"])
	fmt.Println(errorx.PublicMetadataOf(err)["resource"])
}

func checkPermission(ctx context.Context, subjectID, resource, action string) error {
	return errorx.Forbidden("AIHUB_SKILL_DELETE_DENIED", "没有删除技能的权限",
		errorx.WithMetadata("subject_id", subjectID),
		errorx.WithMetadata("resource", resource),
		errorx.WithMetadata("action", action),
		errorx.WithPublicMetadata("resource", "skill"),
	)
}
Output:
AIHUB_SKILL_DELETE_DENIED
403
user_123
skill
Example (BusinessHTTPResponse)

ExampleBusiness_httpResponse shows the JSON shape that httpx middleware produces from an errorx error. The middleware extracts code/message/ request_id/trace_id/public_metadata automatically.

package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
		errorx.WithPublicMetadata("resource", "skill"),
		errorx.WithRequestID("req_abc"),
	)

	// In real code, httpx middleware does this:
	resp := map[string]any{
		"code":       errorx.CodeOf(err).String(),
		"message":    errorx.MessageOf(err),
		"request_id": errorx.RequestIDOf(err),
		"metadata":   errorx.PublicMetadataOf(err),
	}
	fmt.Println(resp["code"])
	fmt.Println(resp["message"])
	fmt.Println(resp["request_id"])
	fmt.Println(resp["metadata"])
}
Output:
AIHUB_SKILL_NOT_FOUND
技能不存在
req_abc
map[resource:skill]
Example (BusinessLogEntry)

ExampleBusiness_logEntry shows how logx extracts error fields for structured log entries. Note that SafeMetadataOf redacts sensitive keys.

package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("AIHUB_DB_FAILED", "数据库失败",
		errorx.WithMetadata("password", "s3cr3t"),
		errorx.WithMetadata("dsn", "postgres://..."),
		errorx.WithMetadata("query", "SELECT 1"),
		errorx.WithRequestID("req_123"),
	)

	logFields := errorx.Fields(err)
	fmt.Println(logFields["error_code"])
	fmt.Println(logFields["http_status"])
	fmt.Println(logFields["severity"])
	// SafeMetadataOf is used internally by Fields for "error_metadata":
	safeMD := logFields["error_metadata"].(map[string]any)
	fmt.Println(safeMD["password"]) // redacted
	fmt.Println(safeMD["dsn"])      // not redacted (no sensitive keyword)
}
Output:
AIHUB_DB_FAILED
500
error
[REDACTED]
postgres://...
Example (BusinessMetricsLabels)

ExampleBusiness_metricsLabels shows what metricsx emits for an error. Note: ONLY low-cardinality fields, NEVER dynamic values.

package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型超时",
		errorx.WithMetadata("model", "gpt-4"),   // dynamic — NOT in labels
		errorx.WithMetadata("user_id", "u_123"), // dynamic — NOT in labels
		errorx.WithRequestID("req_abc"),         // dynamic — NOT in labels
	)

	labels := errorx.MetricsLabels(err)
	for _, k := range []string{"error_code", "http_status", "grpc_code", "retryable", "category"} {
		fmt.Printf("%s=%s\n", k, labels[k])
	}
}
Output:
error_code=MODEL_UPSTREAM_TIMEOUT
http_status=504
grpc_code=DeadlineExceeded
retryable=true
category=dependency
Example (BusinessMultiLayerWrap)

ExampleBusiness_multiLayerWrap shows how errors propagate through layers while preserving the original cause for errors.Is.

package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Layer 1: database driver
	dbErr := errors.New("pq: connection refused")

	// Layer 2: repository wraps with business code
	repoErr := errorx.Wrap(dbErr, "AIHUB_SKILL_QUERY_FAILED",
		errorx.WithMessage("查询技能失败"),
		errorx.WithRetryable(true),
		errorx.WithMetadata("skill_id", "skill_001"),
	)

	// Layer 3: service wraps again (less common, but possible)
	svcErr := errorx.Wrap(repoErr, "AIHUB_SKILL_GET_FAILED",
		errorx.WithMessage("获取技能失败"),
		errorx.WithRetryable(errorx.RetryableOf(repoErr)),
	)

	// errors.Is walks the entire chain back to the original dbErr.
	fmt.Println(errors.Is(svcErr, dbErr))
	fmt.Println(errorx.CodeOf(svcErr))
	fmt.Println(errorx.RetryableOf(svcErr))
}
Output:
true
AIHUB_SKILL_GET_FAILED
true
Example (BusinessRepositoryLayer)

ExampleBusiness_repositoryLayer shows the standard pattern for converting database errors (sql.ErrNoRows, connection errors) to errorx errors.

package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	repo := &fakeSkillRepo{}
	_, err := repo.Find(context.Background(), "missing")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
}

type fakeSkillRepo struct{}

func (r *fakeSkillRepo) Find(ctx context.Context, id string) (*struct{}, error) {

	err := sql.ErrNoRows
	if errors.Is(err, sql.ErrNoRows) {
		return nil, errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
			errorx.WithMetadata("skill_id", id),
			errorx.WithPublicMetadata("resource", "skill"),
		)
	}
	return nil, errorx.Wrap(err, "AIHUB_SKILL_QUERY_FAILED",
		errorx.WithMessage("查询技能失败"),
		errorx.WithRetryable(true),
		errorx.WithMetadata("skill_id", id),
	)
}
Output:
AIHUB_SKILL_NOT_FOUND
404
Example (BusinessServiceLayer)

ExampleBusiness_serviceLayer shows the standard pattern for service-layer validation, permission checks, and conflict detection — all returning errorx.

package main

import (
	"context"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	svc := &fakeSkillService{}
	_, err := svc.Create(context.Background(), "")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))

	_, err = svc.Create(context.Background(), "demo")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
}

type fakeSkillService struct{}

func (s *fakeSkillService) Create(ctx context.Context, name string) (*struct{}, error) {
	if name == "" {
		return nil, errorx.BadRequest("AIHUB_SKILL_NAME_REQUIRED", "技能名称不能为空")
	}
	if name == "demo" {
		return nil, errorx.Conflict("AIHUB_SKILL_ALREADY_EXISTS", "技能已存在",
			errorx.WithPublicMetadata("name", name),
		)
	}
	return &struct{}{}, nil
}
Output:
AIHUB_SKILL_NAME_REQUIRED
400
AIHUB_SKILL_ALREADY_EXISTS
409
Example (BusinessUpstreamTimeout)

ExampleBusiness_upstreamTimeout shows how to wrap upstream API failures with retryable flag so the worker can decide whether to retry.

package main

import (
	"context"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := callModelService(context.Background(), "gpt-4")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.RetryableOf(err))
	fmt.Println(errorx.CategoryOf(err))
}

func callModelService(ctx context.Context, model string) error {
	return errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型服务超时",
		errorx.WithRetryable(true),
		errorx.WithMetadata("model", model),
		errorx.WithPublicMetadata("model", model),
	)
}
Output:
MODEL_UPSTREAM_TIMEOUT
504
true
dependency
Example (BusinessWorkerRetry)

ExampleBusiness_workerRetry shows how a worker uses RetryableOf to decide whether to retry a failed job.

package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	cases := []struct {
		name string
		err  error
	}{
		{"db_timeout", errorx.Wrap(errors.New("pq: timeout"), "AIHUB_DB_TIMEOUT",
			errorx.WithRetryable(true))},
		{"validation", errorx.BadRequest("AIHUB_INVALID_INPUT", "bad input")},
		{"upstream_down", errorx.Unavailable("MODEL_UNAVAILABLE", "model down")},
	}

	for _, c := range cases {
		action := "fail"
		if errorx.RetryableOf(c.err) {
			action = "retry"
		}
		fmt.Printf("%s -> %s\n", c.name, action)
	}
}
Output:
db_timeout -> retry
validation -> fail
upstream_down -> retry
Example (WithContext)
package main

import (
	"context"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Simulate extracting request_id from context (in real code, use contextx).
	ctx := context.WithValue(context.Background(), ctxKeyRequestID{}, "req_abc")

	// In real code: reqID, _ := contextx.RequestIDFromContext(ctx)
	reqID, _ := ctx.Value(ctxKeyRequestID{}).(string)

	err := errorx.Internal("AIHUB_FAILED", "失败",
		errorx.WithRequestID(reqID),
	)
	fmt.Println(errorx.RequestIDOf(err))
}

type ctxKeyRequestID struct{}
Output:
req_abc

Index

Examples

Constants

View Source
const (
	GRPCCodeOK                = "OK"
	GRPCCodeCanceled          = "Canceled"
	GRPCCodeInvalidArgument   = "InvalidArgument"
	GRPCCodeDeadlineExceeded  = "DeadlineExceeded"
	GRPCCodeNotFound          = "NotFound"
	GRPCCodeAlreadyExists     = "AlreadyExists"
	GRPCCodePermissionDenied  = "PermissionDenied"
	GRPCCodeResourceExhausted = "ResourceExhausted"
	GRPCCodeInternal          = "Internal"
	GRPCCodeUnavailable       = "Unavailable"
	GRPCCodeUnauthenticated   = "Unauthenticated"
)

gRPC code names are represented as strings to keep errorx free of grpc-go. grpcx should translate these strings into google.golang.org/grpc/codes.Code.

View Source
const (
	HTTPStatusOK                  = 200
	HTTPStatusBadRequest          = 400
	HTTPStatusUnauthorized        = 401
	HTTPStatusForbidden           = 403
	HTTPStatusNotFound            = 404
	HTTPStatusConflict            = 409
	HTTPStatusRequestTimeout      = 408
	HTTPStatusTooManyRequests     = 429
	HTTPStatusClientClosedRequest = 499
	HTTPStatusInternalServerError = 500
	HTTPStatusServiceUnavailable  = 503
	HTTPStatusGatewayTimeout      = 504
)
View Source
const Redacted = "[REDACTED]"

Variables

This section is empty.

Functions

func CauseOf

func CauseOf(err error) error

func Fields

func Fields(err error) map[string]any

Fields returns a structured, logger-neutral representation of err. logx/auditx/metricsx can convert it to concrete logger fields or labels.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Fields returns logger-neutral fields for logx/auditx.
	err := errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型服务超时",
		errorx.WithMetadata("model", "qwen3"),
		errorx.WithRequestID("req_456"),
	)
	fields := errorx.Fields(err)
	fmt.Println(fields["error_code"])
	fmt.Println(fields["http_status"])
	fmt.Println(fields["retryable"])
	fmt.Println(fields["category"])
	fmt.Println(fields["severity"])
	fmt.Println(fields["request_id"])
}
Output:
MODEL_UPSTREAM_TIMEOUT
504
true
dependency
error
req_456
Example (Nil)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Fields(nil) returns valid map with OK status — safe to call on any error.
	fields := errorx.Fields(nil)
	fmt.Println(fields["error_code"])
	fmt.Println(fields["http_status"])
}
Output:
OK
200

func GRPCCodeOf

func GRPCCodeOf(err error) string
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.BadRequest("X", "x")
	fmt.Println(errorx.GRPCCodeOf(err))
}
Output:
InvalidArgument

func HTTPStatusOf

func HTTPStatusOf(err error) int
Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// HTTPStatusOf returns the HTTP status. nil → 200, unknown → 500.
	fmt.Println(errorx.HTTPStatusOf(nil))
	fmt.Println(errorx.HTTPStatusOf(errors.New("x")))
	fmt.Println(errorx.HTTPStatusOf(errorx.NotFound("X", "x")))
}
Output:
200
500
404
Example (ForeignError)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

// httpStatusError mimics Kratos-style errors with HTTPStatus() method.
type httpStatusError struct {
	msg    string
	status int
}

func (e httpStatusError) Error() string   { return e.msg }
func (e httpStatusError) HTTPStatus() int { return e.status }

func main() {
	// errorx recognizes HTTPStatus() method via errors.As.
	foreignErr := httpStatusError{msg: "forbidden", status: 403}
	fmt.Println(errorx.HTTPStatusOf(foreignErr))
	fmt.Println(errorx.CodeOf(foreignErr))
}
Output:
403
FORBIDDEN
Example (Nil)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// HTTPStatusOf(nil) returns 200 — safe to call on any error.
	fmt.Println(errorx.HTTPStatusOf(nil))
}
Output:
200

func IsBadRequest

func IsBadRequest(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsBadRequest(errorx.BadRequest("X", "x")))
	fmt.Println(errorx.IsBadRequest(errorx.NotFound("X", "x")))
}
Output:
true
false

func IsClientClosedRequest

func IsClientClosedRequest(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsClientClosedRequest(errorx.ClientClosed("X", "x")))
}
Output:
true

func IsCode

func IsCode(err error, code Code) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Match by exact error code (most precise predicate).
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	fmt.Println(errorx.IsCode(err, errorx.Code("AIHUB_SKILL_NOT_FOUND")))
	fmt.Println(errorx.IsCode(err, errorx.Code("AIHUB_USER_NOT_FOUND")))
}
Output:
true
false

func IsConflict

func IsConflict(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsConflict(errorx.Conflict("X", "x")))
}
Output:
true

func IsForbidden

func IsForbidden(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsForbidden(errorx.Forbidden("X", "x")))
}
Output:
true

func IsInternal

func IsInternal(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsInternal(errorx.Internal("X", "x")))
}
Output:
true

func IsKernelError

func IsKernelError(err error) bool
Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// IsKernelError reports whether err is or wraps an *errorx.Error.
	fmt.Println(errorx.IsKernelError(errorx.NotFound("X", "x")))
	fmt.Println(errorx.IsKernelError(errors.New("plain")))
}
Output:
true
false

func IsNotFound

func IsNotFound(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	fmt.Println(errorx.IsNotFound(err))
	fmt.Println(errorx.IsBadRequest(err))
}
Output:
true
false

func IsRequestTimeout

func IsRequestTimeout(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsRequestTimeout(errorx.RequestTimeout("X", "x")))
}
Output:
true

func IsTimeout

func IsTimeout(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsTimeout(errorx.Timeout("X", "x")))
}
Output:
true

func IsTooManyRequests

func IsTooManyRequests(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsTooManyRequests(errorx.TooManyRequests("X", "x")))
}
Output:
true

func IsUnauthorized

func IsUnauthorized(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsUnauthorized(errorx.Unauthorized("X", "x")))
}
Output:
true

func IsUnavailable

func IsUnavailable(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsUnavailable(errorx.Unavailable("X", "x")))
}
Output:
true

func IsValidCode

func IsValidCode(code Code) bool

IsValidCode reports whether code follows the stable upper-snake-case format. Empty code is invalid. OK is valid.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.IsValidCode("AIHUB_SKILL_NOT_FOUND"))
	fmt.Println(errorx.IsValidCode("skillNotFound"))
	fmt.Println(errorx.IsValidCode(""))
	fmt.Println(errorx.IsValidCode("SKILL_123_NOT_FOUND"))
}
Output:
true
false
false
true

func MessageOf

func MessageOf(err error) string
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// MessageOf returns the human-readable message.
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	fmt.Println(errorx.MessageOf(err))
	fmt.Println(errorx.MessageOf(nil))
}
Output:
技能不存在
success

func MetadataOf

func MetadataOf(err error) map[string]any
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("X", "x",
		errorx.WithMetadata("skill_id", "skill_001"),
	)
	fmt.Println(errorx.MetadataOf(err))
}
Output:
map[skill_id:skill_001]

func MetricsLabels

func MetricsLabels(err error) map[string]string

MetricsLabels returns low-cardinality labels suitable for metrics. It never includes message, request_id, trace_id, metadata or dynamic object ids.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// MetricsLabels returns low-cardinality labels for Prometheus.
	// It NEVER includes message, request_id, trace_id, or object IDs.
	err := errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型服务超时",
		errorx.WithMetadata("model", "qwen3"),
		errorx.WithRequestID("req_456"),
	)
	labels := errorx.MetricsLabels(err)
	fmt.Println(labels["error_code"])
	fmt.Println(labels["http_status"])
	fmt.Println(labels["retryable"])
	fmt.Println(labels["category"])
}
Output:
MODEL_UPSTREAM_TIMEOUT
504
true
dependency

func MustValidCodes

func MustValidCodes(codes ...Code)

func PublicMetadataOf

func PublicMetadataOf(err error) map[string]any
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Forbidden("X", "x",
		errorx.WithMetadata("internal", "secret"),
		errorx.WithPublicMetadata("resource", "skill"),
	)
	// PublicMetadataOf only returns explicitly public fields.
	fmt.Println(errorx.PublicMetadataOf(err))
}
Output:
map[resource:skill]

func RequestIDOf

func RequestIDOf(err error) string
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("X", "x", errorx.WithRequestID("req_123"))
	fmt.Println(errorx.RequestIDOf(err))
}
Output:
req_123

func RetryableOf

func RetryableOf(err error) bool
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.RetryableOf(errorx.TooManyRequests("X", "x")))
	fmt.Println(errorx.RetryableOf(errorx.BadRequest("X", "x")))
}
Output:
true
false

func SafeMetadataOf

func SafeMetadataOf(err error) map[string]any
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Sensitive keys are redacted for safe logging.
	err := errorx.Internal("X", "x",
		errorx.WithMetadata("password", "s3cr3t"),
		errorx.WithMetadata("dsn", "postgres://..."),
	)
	safe := errorx.SafeMetadataOf(err)
	fmt.Println(safe["password"])
	fmt.Println(safe["dsn"])
}
Output:
[REDACTED]
postgres://...

func TraceIDOf

func TraceIDOf(err error) string
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("X", "x", errorx.WithTraceID("trace_abc"))
	fmt.Println(errorx.TraceIDOf(err))
}
Output:
trace_abc

func ValidateCode

func ValidateCode(code Code) error

func Wrap

func Wrap(cause error, code Code, opts ...Option) error

Wrap annotates cause with Kernel error semantics. If cause is nil, Wrap returns nil.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	cause := errors.New("pq: connection refused")
	err := errorx.Wrap(cause, "AIHUB_SKILL_CREATE_FAILED",
		errorx.WithMessage("创建技能失败"),
		errorx.WithRetryable(true),
	)

	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.MessageOf(err))
	fmt.Println(errorx.RetryableOf(err))
	fmt.Println(errors.Is(err, cause))
}
Output:
AIHUB_SKILL_CREATE_FAILED
创建技能失败
true
true
Example (Nil)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Wrap(nil, ...) returns nil — safe to chain without nil check.
	err := errorx.Wrap(nil, "AIHUB_SKILL_CREATE_FAILED")
	fmt.Println(err == nil)
}
Output:
true
Example (SeesThroughFmtErrorf)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// errorx.Wrap preserves the chain even through fmt.Errorf wrapping.
	inner := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	wrapped := fmt.Errorf("repo layer: %w", inner)

	// All inspect helpers see through the wrapping.
	fmt.Println(errorx.CodeOf(wrapped))
	fmt.Println(errorx.HTTPStatusOf(wrapped))
	fmt.Println(errorx.IsKernelError(wrapped))
}
Output:
AIHUB_SKILL_NOT_FOUND
404
true

Types

type Category

type Category string

Category is an optional coarse-grained error class for logs, metrics, alerting and AI-assisted troubleshooting.

const (
	CategoryOK         Category = "ok"
	CategoryValidation Category = "validation"
	CategoryAuth       Category = "auth"
	CategoryPermission Category = "permission"
	CategoryNotFound   Category = "not_found"
	CategoryConflict   Category = "conflict"
	CategoryRateLimit  Category = "rate_limit"
	CategoryDependency Category = "dependency"
	CategoryCanceled   Category = "canceled"
	CategoryInternal   Category = "internal"
)

func CategoryOf

func CategoryOf(err error) Category
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.CategoryOf(errorx.NotFound("X", "x")))
	fmt.Println(errorx.CategoryOf(errorx.Forbidden("X", "x")))
	fmt.Println(errorx.CategoryOf(errorx.Internal("X", "x")))
}
Output:
not_found
permission
internal

func (Category) String

func (c Category) String() string

type CategoryResponder

type CategoryResponder interface{ Category() Category }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type Code

type Code string

Code is a stable, low-cardinality, machine-readable error code.

Codes should use upper snake case. Business modules should define their own domain-specific constants, for example AIHUB_SKILL_NOT_FOUND.

const (
	CodeOK                  Code = "OK"
	CodeBadRequest          Code = "BAD_REQUEST"
	CodeUnauthorized        Code = "UNAUTHORIZED"
	CodeForbidden           Code = "FORBIDDEN"
	CodeNotFound            Code = "NOT_FOUND"
	CodeConflict            Code = "CONFLICT"
	CodeTooManyRequests     Code = "TOO_MANY_REQUESTS"
	CodeRequestTimeout      Code = "REQUEST_TIMEOUT"
	CodeClientClosedRequest Code = "CLIENT_CLOSED_REQUEST"
	CodeInternal            Code = "INTERNAL_ERROR"
	CodeUnavailable         Code = "SERVICE_UNAVAILABLE"
	CodeTimeout             Code = "TIMEOUT"
)

func CodeOf

func CodeOf(err error) Code
Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// CodeOf returns the error code. nil → OK, unknown → INTERNAL_ERROR.
	fmt.Println(errorx.CodeOf(nil))
	fmt.Println(errorx.CodeOf(errors.New("unknown")))
	fmt.Println(errorx.CodeOf(errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "x")))
}
Output:
OK
INTERNAL_ERROR
AIHUB_SKILL_NOT_FOUND
Example (ForeignError)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

// errorCodeStringError mimics errors that expose ErrorCode() string method.
type errorCodeStringError struct {
	code string
	msg  string
}

func (e errorCodeStringError) Error() string     { return e.msg }
func (e errorCodeStringError) ErrorCode() string { return e.code }

func main() {
	// errorx recognizes ErrorCode() string method.
	foreignErr := errorCodeStringError{code: "CUSTOM_NOT_FOUND", msg: "x"}
	fmt.Println(errorx.CodeOf(foreignErr))
}
Output:
CUSTOM_NOT_FOUND
Example (Nil)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// CodeOf(nil) returns OK — safe to call on any error.
	fmt.Println(errorx.CodeOf(nil))
}
Output:
OK

func MustCode

func MustCode(code string) Code

func NormalizeCode

func NormalizeCode(code Code) Code

NormalizeCode converts an empty code to INTERNAL_ERROR. It does not rewrite invalid non-empty business codes; callers should use IsValidCode in tests and lint rules to keep error-code contracts clean.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// NormalizeCode converts empty code to INTERNAL_ERROR.
	fmt.Println(errorx.NormalizeCode(""))
	fmt.Println(errorx.NormalizeCode("AIHUB_CUSTOM"))
}
Output:
INTERNAL_ERROR
AIHUB_CUSTOM

func (Code) String

func (c Code) String() string

type CodedError

type CodedError interface {
	error
	Code() Code
	Message() string
	HTTPStatus() int
	Retryable() bool
}

CodedError is the minimum contract consumed by logx/httpx/auditx/metricsx.

Other error implementations may satisfy this interface without depending on *errorx.Error directly.

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error is Aisphere Kernel's canonical error implementation.

Fields are deliberately unexported to keep the public API stable. Use methods and Option constructors instead of direct field access.

func As

func As(err error) (*Error, bool)

As extracts the canonical *Error from an error chain.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// As extracts *errorx.Error from an error chain.
	original := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	wrapped := fmt.Errorf("repo: %w", original)

	ke, ok := errorx.As(wrapped)
	fmt.Println(ok)
	fmt.Println(ke.Code())
}
Output:
true
AIHUB_SKILL_NOT_FOUND

func BadRequest

func BadRequest(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.BadRequest("REQUEST_VALIDATE_FAILED", "请求参数校验失败")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.GRPCCodeOf(err))
	fmt.Println(errorx.RetryableOf(err))
}
Output:
REQUEST_VALIDATE_FAILED
400
InvalidArgument
false

func ClientClosed

func ClientClosed(code Code, message string, opts ...Option) *Error

ClientClosed is a short alias for ClientClosedRequest.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.ClientClosed("CLIENT_CLOSED", "客户端断开连接")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.GRPCCodeOf(err))
	fmt.Println(errorx.SeverityOf(err))
}
Output:
499
Canceled
debug

func ClientClosedRequest

func ClientClosedRequest(code Code, message string, opts ...Option) *Error

func Conflict

func Conflict(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Conflict("AIHUB_SKILL_ALREADY_EXISTS", "技能已存在")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.CategoryOf(err))
}
Output:
409
conflict

func Forbidden

func Forbidden(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Forbidden("IAM_PERMISSION_DENIED", "权限被拒绝",
		errorx.WithMetadata("subject_id", "user_123"),
		errorx.WithPublicMetadata("resource", "skill"),
	)
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.PublicMetadataOf(err))
}
Output:
403
map[resource:skill]

func From

func From(err error, opts ...Option) *Error

From converts err to *Error. Kernel errors are cloned. Foreign errors are converted using supported interfaces such as Code/ErrorCode/HTTPStatus/StatusCode.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// From converts a foreign error to *errorx.Error.
	cause := errors.New("something failed")
	ke := errorx.From(cause)
	fmt.Println(ke.Code())
	fmt.Println(ke.HTTPStatus())
	fmt.Println(ke.Retryable())
}
Output:
INTERNAL_ERROR
500
false
Example (ForeignStatusCode)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

// gofrStyleError mimics GoFr's error type with StatusCode() method.
type gofrStyleError struct {
	msg    string
	status int
}

func (e gofrStyleError) Error() string   { return e.msg }
func (e gofrStyleError) StatusCode() int { return e.status }

func main() {
	// errorx recognizes GoFr-style errors via StatusCode() method.
	gofrErr := gofrStyleError{msg: "not found", status: 404}
	ke := errorx.From(gofrErr)
	fmt.Println(ke.HTTPStatus())
	fmt.Println(ke.Code())
}
Output:
404
NOT_FOUND
Example (WithOptions)
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// From with options to override defaults.
	cause := errors.New("upstream timeout")
	ke := errorx.From(cause,
		errorx.WithHTTPStatus(504),
		errorx.WithRetryable(true),
		errorx.WithMetadata("upstream", "model-svc"),
	)
	fmt.Println(ke.HTTPStatus())
	fmt.Println(ke.Retryable())
	fmt.Println(ke.Metadata()["upstream"])
}
Output:
504
true
model-svc

func GatewayTimeout

func GatewayTimeout(code Code, message string, opts ...Option) *Error

GatewayTimeout is an alias that mirrors Kratos naming.

func Internal

func Internal(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("AIHUB_INTERNAL_ERROR", "服务器内部错误")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.CategoryOf(err))
	fmt.Println(errorx.SeverityOf(err))
}
Output:
500
internal
error

func InternalServer

func InternalServer(code Code, message string, opts ...Option) *Error

InternalServer is an alias that mirrors Kratos naming.

func New

func New(code Code, opts ...Option) *Error

New creates a canonical Error with default mappings derived from code.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// New creates a canonical Error with default mappings derived from code.
	err := errorx.New("AIHUB_CUSTOM_ERROR",
		errorx.WithMessage("自定义错误"),
		errorx.WithHTTPStatus(422),
	)
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.MessageOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
}
Output:
AIHUB_CUSTOM_ERROR
自定义错误
422

func NewStatus

func NewStatus(code Code, httpStatus int, message string, opts ...Option) *Error

NewStatus creates an Error using an explicit HTTP status while keeping derived gRPC/category/severity/retryability values consistent. It is primarily used by generated proto errorx helpers where the stable business code comes from an enum value and the transport status comes from proto options.

func NotFound

func NotFound(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	fmt.Println(errorx.CodeOf(err))
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.RetryableOf(err))
}
Output:
AIHUB_SKILL_NOT_FOUND
404
false

func RequestTimeout

func RequestTimeout(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.RequestTimeout("HTTP_REQUEST_TIMEOUT", "请求超时")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.GRPCCodeOf(err))
}
Output:
408
DeadlineExceeded

func Timeout

func Timeout(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Timeout("MODEL_UPSTREAM_TIMEOUT", "模型上游超时")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.RetryableOf(err))
	fmt.Println(errorx.CategoryOf(err))
}
Output:
504
true
dependency

func TooManyRequests

func TooManyRequests(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.TooManyRequests("RATE_LIMIT_EXCEEDED", "请求过于频繁")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.RetryableOf(err))
}
Output:
429
true

func Unauthorized

func Unauthorized(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Unauthorized("AUTH_TOKEN_MISSING", "缺少授权令牌")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.CategoryOf(err))
}
Output:
401
auth

func Unavailable

func Unavailable(code Code, message string, opts ...Option) *Error
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Unavailable("MODEL_UPSTREAM_UNAVAILABLE", "模型上游不可用")
	fmt.Println(errorx.HTTPStatusOf(err))
	fmt.Println(errorx.RetryableOf(err))
}
Output:
503
true

func (*Error) Category

func (e *Error) Category() Category

func (*Error) Cause

func (e *Error) Cause() error
Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Cause returns the wrapped underlying error.
	cause := errors.New("db timeout")
	err := errorx.Wrap(cause, "X", errorx.WithMessage("失败"))
	fmt.Println(errorx.CauseOf(err) == cause)
}
Output:
true

func (*Error) Clone

func (e *Error) Clone() *Error

Clone returns a deep copy of Error metadata while preserving the cause chain.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Clone returns a deep copy. Useful when adding metadata without mutating original.
	original := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
		errorx.WithMetadata("skill_id", "skill_001"),
	)
	clone := original.Clone()
	clone = errorx.From(clone, errorx.WithMetadata("extra", "value"))

	fmt.Println(errorx.MetadataOf(original)["extra"])
	fmt.Println(errorx.MetadataOf(clone)["extra"])
}
Output:
<nil>
value

func (*Error) Code

func (e *Error) Code() Code
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "x")
	fmt.Println(err.Code())
}
Output:
AIHUB_SKILL_NOT_FOUND

func (*Error) Error

func (e *Error) Error() string

func (*Error) ErrorCode

func (e *Error) ErrorCode() string

ErrorCode is a string-returning alias for compatibility with adapters and external frameworks that do not use errorx.Code.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// ErrorCode() returns string — for adapters that don't use errorx.Code.
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "x")
	fmt.Println(err.ErrorCode())
}
Output:
AIHUB_SKILL_NOT_FOUND

func (*Error) Format

func (e *Error) Format(s fmt.State, verb rune)

Format supports fmt.Printf("%+v", err) with useful debugging information while Error() stays safe for user-facing messages.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// %+v prints full debug info; %s and Error() print safe message.
	err := errorx.Internal("AIHUB_SKILL_QUERY_FAILED", "查询技能失败",
		errorx.WithCause(errors.New("pq: connection refused")),
		errorx.WithRequestID("req_123"),
	)
	fmt.Println(err.Error())
}
Output:
查询技能失败
Example (Verbose)
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// fmt.Printf("%+v", err) prints all fields for debugging.
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
		errorx.WithPublicMetadata("resource", "skill"),
	)
	// Don't assert exact output (cause formatting varies), just show it works.
	output := fmt.Sprintf("%+v", err)
	fmt.Println(len(output) > 0)
}
Output:
true

func (*Error) GRPCCode

func (e *Error) GRPCCode() string

func (*Error) HTTPStatus

func (e *Error) HTTPStatus() int

func (*Error) Is

func (e *Error) Is(target error) bool

Is allows errors.Is to match another *Error or any coded error by code. Message and metadata are contextual and are intentionally ignored.

func (*Error) Message

func (e *Error) Message() string
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在")
	fmt.Println(err.Message())
}
Output:
技能不存在

func (*Error) Metadata

func (e *Error) Metadata() map[string]any

Metadata returns a defensive copy to avoid accidental mutation across layers.

func (*Error) PublicMetadata

func (e *Error) PublicMetadata() map[string]any

PublicMetadata returns metadata that is explicitly safe for transport responses. httpx may choose to expose it depending on environment/config.

func (*Error) RequestID

func (e *Error) RequestID() string

func (*Error) Retryable

func (e *Error) Retryable() bool

func (*Error) Severity

func (e *Error) Severity() Severity

func (*Error) Stack

func (e *Error) Stack() []Frame

func (*Error) StatusCode

func (e *Error) StatusCode() int

StatusCode is provided for GoFr-style interoperability.

func (*Error) TraceID

func (e *Error) TraceID() string

func (*Error) Unwrap

func (e *Error) Unwrap() error
Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Unwrap enables errors.Is / errors.As.
	cause := errors.New("db timeout")
	err := errorx.Wrap(cause, "X", errorx.WithMessage("失败"))
	fmt.Println(errors.Unwrap(err) == cause)
}
Output:
true

func (*Error) WithCause

func (e *Error) WithCause(cause error) *Error

WithCause returns a cloned error with the supplied cause attached.

func (*Error) WithMetadata

func (e *Error) WithMetadata(metadata map[string]any) *Error

WithMetadata returns a cloned error with internal metadata merged in.

func (*Error) WithPublicMetadata

func (e *Error) WithPublicMetadata(metadata map[string]any) *Error

WithPublicMetadata returns a cloned error with transport-safe metadata merged in.

type ErrorCodeResponder

type ErrorCodeResponder interface{ ErrorCode() string }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type Frame

type Frame struct {
	Function string `json:"function"`
	File     string `json:"file"`
	Line     int    `json:"line"`
}

Frame is a single caller frame captured by WithStack.

func StackOf

func StackOf(err error) []Frame
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("X", "x", errorx.WithStack())
	frames := errorx.StackOf(err)
	fmt.Println(len(frames) > 0)
}
Output:
true

type GRPCCodeResponder

type GRPCCodeResponder interface{ GRPCCode() string }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type HTTPStatusResponder

type HTTPStatusResponder interface {
	error
	HTTPStatus() int
}

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type MessageResponder

type MessageResponder interface{ Message() string }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type MetadataResponder

type MetadataResponder interface{ Metadata() map[string]any }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type Option

type Option func(*Error)

Option mutates Error during construction.

func WithCategory

func WithCategory(category Category) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Override default category (use sparingly — defaults are usually right).
	err := errorx.New("AIHUB_BUSINESS_RULE_VIOLATION",
		errorx.WithCategory(errorx.CategoryValidation),
	)
	fmt.Println(errorx.CategoryOf(err))
}
Output:
validation

func WithCause

func WithCause(cause error) Option
Example
package main

import (
	"errors"
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// WithCause preserves the underlying error for errors.Is / errors.As.
	cause := errors.New("redis: connection refused")
	err := errorx.Unavailable("CACHE_UNAVAILABLE", "缓存不可用",
		errorx.WithCause(cause),
	)
	fmt.Println(errors.Is(err, cause))
}
Output:
true

func WithGRPCCode

func WithGRPCCode(code string) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.New("AIHUB_CUSTOM",
		errorx.WithGRPCCode("FailedPrecondition"),
	)
	fmt.Println(errorx.GRPCCodeOf(err))
}
Output:
FailedPrecondition

func WithHTTPStatus

func WithHTTPStatus(status int) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Override default HTTP status (use sparingly — prefer semantic constructors).
	err := errorx.New("AIHUB_PAYMENT_REQUIRED",
		errorx.WithHTTPStatus(402),
	)
	fmt.Println(errorx.HTTPStatusOf(err))
}
Output:
402

func WithMessage

func WithMessage(message string) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.New("AIHUB_SKILL_NOT_FOUND",
		errorx.WithMessage("技能不存在"),
	)
	fmt.Println(err.Error())
}
Output:
技能不存在

func WithMetadata

func WithMetadata(key string, value any) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Internal metadata — for logs/audit only, NOT returned to clients.
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
		errorx.WithMetadata("skill_id", "skill_001"),
		errorx.WithMetadata("project_id", "proj_001"),
		errorx.WithMetadata("raw_db_error", "record not found"),
	)
	md := errorx.MetadataOf(err)
	fmt.Println(md["skill_id"])
	fmt.Println(md["project_id"])
}
Output:
skill_001
proj_001

func WithMetadataMap

func WithMetadataMap(metadata map[string]any) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// WithMetadataMap adds multiple metadata entries at once.
	err := errorx.Internal("AIHUB_DB_FAILED", "数据库失败",
		errorx.WithMetadataMap(map[string]any{
			"dsn":   "postgres://...",
			"query": "SELECT * FROM skills",
		}),
	)
	md := errorx.MetadataOf(err)
	fmt.Println(md["dsn"])
}
Output:
postgres://...

func WithPublicMetadata

func WithPublicMetadata(key string, value any) Option

WithPublicMetadata marks a metadata key as safe to expose via transport responses. Secrets and large objects should never be put here.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// PublicMetadata is safe for HTTP/gRPC responses.
	// Metadata is for internal logs/audit only.
	err := errorx.Forbidden("IAM_PERMISSION_DENIED", "权限被拒绝",
		errorx.WithMetadata("raw_policy", "deny"),      // internal
		errorx.WithPublicMetadata("resource", "skill"), // public
		errorx.WithPublicMetadata("action", "delete"),  // public
	)
	fmt.Println(errorx.PublicMetadataOf(err))
}
Output:
map[action:delete resource:skill]

func WithPublicMetadataMap

func WithPublicMetadataMap(metadata map[string]any) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.NotFound("AIHUB_SKILL_NOT_FOUND", "技能不存在",
		errorx.WithPublicMetadataMap(map[string]any{
			"resource": "skill",
			"name":     "demo-skill",
		}),
	)
	fmt.Println(errorx.PublicMetadataOf(err)["resource"])
}
Output:
skill

func WithRequestID

func WithRequestID(requestID string) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("AIHUB_FAILED", "失败",
		errorx.WithRequestID("req_abc123"),
	)
	fmt.Println(errorx.RequestIDOf(err))
}
Output:
req_abc123

func WithRetryable

func WithRetryable(retryable bool) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Override default retryable. Use when business knows better than defaults.
	err := errorx.Internal("AIHUB_DB_TRANSACTION_FAILED", "数据库事务失败",
		errorx.WithRetryable(true),
	)
	fmt.Println(errorx.RetryableOf(err))
}
Output:
true

func WithSeverity

func WithSeverity(severity Severity) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Override default severity for logx.
	err := errorx.New("AIHUB_DEGRADED",
		errorx.WithMessage("服务降级"),
		errorx.WithSeverity(errorx.SeverityWarn),
	)
	fmt.Println(errorx.SeverityOf(err))
}
Output:
warn

func WithStack

func WithStack() Option

WithStack captures a call stack at construction time using only the standard library. The stack is intended for logs/debugging, not user-facing responses.

Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	// Capture call stack for debugging. Only use for 5xx errors.
	err := errorx.Internal("AIHUB_INTERNAL_FAILED", "内部错误",
		errorx.WithStack(),
	)
	frames := errorx.StackOf(err)
	if len(frames) > 0 {
		fmt.Println("stack captured")
	}
}
Output:
stack captured

func WithStackDepth

func WithStackDepth(depth int) Option

func WithStatusCode

func WithStatusCode(status int) Option

func WithTraceID

func WithTraceID(traceID string) Option
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	err := errorx.Internal("AIHUB_FAILED", "失败",
		errorx.WithTraceID("trace_xyz789"),
	)
	fmt.Println(errorx.TraceIDOf(err))
}
Output:
trace_xyz789

type PublicMetadataResponder

type PublicMetadataResponder interface{ PublicMetadata() map[string]any }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type RequestIDResponder

type RequestIDResponder interface{ RequestID() string }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type RetryableResponder

type RetryableResponder interface{ Retryable() bool }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type Severity

type Severity string

Severity is a log-neutral severity hint. logx maps it to its concrete logger level. errorx deliberately does not depend on zap, slog or GoFr logging.

const (
	SeverityDebug Severity = "debug"
	SeverityInfo  Severity = "info"
	SeverityWarn  Severity = "warn"
	SeverityError Severity = "error"
)

func SeverityOf

func SeverityOf(err error) Severity
Example
package main

import (
	"fmt"

	"github.com/aisphereio/kernel/errorx"
)

func main() {
	fmt.Println(errorx.SeverityOf(errorx.BadRequest("X", "x")))
	fmt.Println(errorx.SeverityOf(errorx.Internal("X", "x")))
	fmt.Println(errorx.SeverityOf(errorx.ClientClosed("X", "x")))
}
Output:
info
error
debug

func (Severity) String

func (s Severity) String() string

type SeverityResponder

type SeverityResponder interface{ Severity() Severity }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type StackResponder

type StackResponder interface{ Stack() []Frame }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type StatusCodeResponder

type StatusCodeResponder interface {
	error
	StatusCode() int
}

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

type TraceIDResponder

type TraceIDResponder interface{ TraceID() string }

Compatibility and extension contracts. Inspect helpers use errors.As against these interfaces, so wrapped third-party errors are also recognized.

Jump to

Keyboard shortcuts

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