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 ¶
- Constants
- func CauseOf(err error) error
- func Fields(err error) map[string]any
- func GRPCCodeOf(err error) string
- func HTTPStatusOf(err error) int
- func IsBadRequest(err error) bool
- func IsClientClosedRequest(err error) bool
- func IsCode(err error, code Code) bool
- func IsConflict(err error) bool
- func IsForbidden(err error) bool
- func IsInternal(err error) bool
- func IsKernelError(err error) bool
- func IsNotFound(err error) bool
- func IsRequestTimeout(err error) bool
- func IsTimeout(err error) bool
- func IsTooManyRequests(err error) bool
- func IsUnauthorized(err error) bool
- func IsUnavailable(err error) bool
- func IsValidCode(code Code) bool
- func MessageOf(err error) string
- func MetadataOf(err error) map[string]any
- func MetricsLabels(err error) map[string]string
- func MustValidCodes(codes ...Code)
- func PublicMetadataOf(err error) map[string]any
- func RequestIDOf(err error) string
- func RetryableOf(err error) bool
- func SafeMetadataOf(err error) map[string]any
- func TraceIDOf(err error) string
- func ValidateCode(code Code) error
- func Wrap(cause error, code Code, opts ...Option) error
- type Category
- type CategoryResponder
- type Code
- type CodedError
- type Error
- func As(err error) (*Error, bool)
- func BadRequest(code Code, message string, opts ...Option) *Error
- func ClientClosed(code Code, message string, opts ...Option) *Error
- func ClientClosedRequest(code Code, message string, opts ...Option) *Error
- func Conflict(code Code, message string, opts ...Option) *Error
- func Forbidden(code Code, message string, opts ...Option) *Error
- func From(err error, opts ...Option) *Error
- func GatewayTimeout(code Code, message string, opts ...Option) *Error
- func Internal(code Code, message string, opts ...Option) *Error
- func InternalServer(code Code, message string, opts ...Option) *Error
- func New(code Code, opts ...Option) *Error
- func NewStatus(code Code, httpStatus int, message string, opts ...Option) *Error
- func NotFound(code Code, message string, opts ...Option) *Error
- func RequestTimeout(code Code, message string, opts ...Option) *Error
- func Timeout(code Code, message string, opts ...Option) *Error
- func TooManyRequests(code Code, message string, opts ...Option) *Error
- func Unauthorized(code Code, message string, opts ...Option) *Error
- func Unavailable(code Code, message string, opts ...Option) *Error
- func (e *Error) Category() Category
- func (e *Error) Cause() error
- func (e *Error) Clone() *Error
- func (e *Error) Code() Code
- func (e *Error) Error() string
- func (e *Error) ErrorCode() string
- func (e *Error) Format(s fmt.State, verb rune)
- func (e *Error) GRPCCode() string
- func (e *Error) HTTPStatus() int
- func (e *Error) Is(target error) bool
- func (e *Error) Message() string
- func (e *Error) Metadata() map[string]any
- func (e *Error) PublicMetadata() map[string]any
- func (e *Error) RequestID() string
- func (e *Error) Retryable() bool
- func (e *Error) Severity() Severity
- func (e *Error) Stack() []Frame
- func (e *Error) StatusCode() int
- func (e *Error) TraceID() string
- func (e *Error) Unwrap() error
- func (e *Error) WithCause(cause error) *Error
- func (e *Error) WithMetadata(metadata map[string]any) *Error
- func (e *Error) WithPublicMetadata(metadata map[string]any) *Error
- type ErrorCodeResponder
- type Frame
- type GRPCCodeResponder
- type HTTPStatusResponder
- type MessageResponder
- type MetadataResponder
- type Option
- func WithCategory(category Category) Option
- func WithCause(cause error) Option
- func WithGRPCCode(code string) Option
- func WithHTTPStatus(status int) Option
- func WithMessage(message string) Option
- func WithMetadata(key string, value any) Option
- func WithMetadataMap(metadata map[string]any) Option
- func WithPublicMetadata(key string, value any) Option
- func WithPublicMetadataMap(metadata map[string]any) Option
- func WithRequestID(requestID string) Option
- func WithRetryable(retryable bool) Option
- func WithSeverity(severity Severity) Option
- func WithStack() Option
- func WithStackDepth(depth int) Option
- func WithStatusCode(status int) Option
- func WithTraceID(traceID string) Option
- type PublicMetadataResponder
- type RequestIDResponder
- type RetryableResponder
- type Severity
- type SeverityResponder
- type StackResponder
- type StatusCodeResponder
- type TraceIDResponder
Examples ¶
- Package (BusinessAuditRecord)
- Package (BusinessAuthzDenied)
- Package (BusinessHTTPResponse)
- Package (BusinessLogEntry)
- Package (BusinessMetricsLabels)
- Package (BusinessMultiLayerWrap)
- Package (BusinessRepositoryLayer)
- Package (BusinessServiceLayer)
- Package (BusinessUpstreamTimeout)
- Package (BusinessWorkerRetry)
- Package (WithContext)
- As
- BadRequest
- CategoryOf
- ClientClosed
- CodeOf
- CodeOf (ForeignError)
- CodeOf (Nil)
- Conflict
- Error.Cause
- Error.Clone
- Error.Code
- Error.ErrorCode
- Error.Format
- Error.Format (Verbose)
- Error.Message
- Error.Unwrap
- Fields
- Fields (Nil)
- Forbidden
- From
- From (ForeignStatusCode)
- From (WithOptions)
- GRPCCodeOf
- HTTPStatusOf
- HTTPStatusOf (ForeignError)
- HTTPStatusOf (Nil)
- Internal
- IsBadRequest
- IsClientClosedRequest
- IsCode
- IsConflict
- IsForbidden
- IsInternal
- IsKernelError
- IsNotFound
- IsRequestTimeout
- IsTimeout
- IsTooManyRequests
- IsUnauthorized
- IsUnavailable
- IsValidCode
- MessageOf
- MetadataOf
- MetricsLabels
- New
- NormalizeCode
- NotFound
- PublicMetadataOf
- RequestIDOf
- RequestTimeout
- RetryableOf
- SafeMetadataOf
- SeverityOf
- StackOf
- Timeout
- TooManyRequests
- TraceIDOf
- Unauthorized
- Unavailable
- WithCategory
- WithCause
- WithGRPCCode
- WithHTTPStatus
- WithMessage
- WithMetadata
- WithMetadataMap
- WithPublicMetadata
- WithPublicMetadataMap
- WithRequestID
- WithRetryable
- WithSeverity
- WithStack
- WithTraceID
- Wrap
- Wrap (Nil)
- Wrap (SeesThroughFmtErrorf)
Constants ¶
const ( GRPCCodeOK = "OK" GRPCCodeCanceled = "Canceled" GRPCCodeInvalidArgument = "InvalidArgument" GRPCCodeDeadlineExceeded = "DeadlineExceeded" GRPCCodeNotFound = "NotFound" GRPCCodeAlreadyExists = "AlreadyExists" GRPCCodePermissionDenied = "PermissionDenied" GRPCCodeResourceExhausted = "ResourceExhausted" GRPCCodeInternal = "Internal" 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.
const ( HTTPStatusOK = 200 HTTPStatusBadRequest = 400 HTTPStatusForbidden = 403 HTTPStatusNotFound = 404 HTTPStatusConflict = 409 HTTPStatusRequestTimeout = 408 HTTPStatusTooManyRequests = 429 HTTPStatusClientClosedRequest = 499 HTTPStatusInternalServerError = 500 HTTPStatusGatewayTimeout = 504 )
const Redacted = "[REDACTED]"
Variables ¶
This section is empty.
Functions ¶
func Fields ¶
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 ¶
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 ¶
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 ¶
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 ¶
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/errorx"
)
func main() {
fmt.Println(errorx.IsClientClosedRequest(errorx.ClientClosed("X", "x")))
}
Output: true
func IsCode ¶
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 ¶
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/errorx"
)
func main() {
fmt.Println(errorx.IsConflict(errorx.Conflict("X", "x")))
}
Output: true
func IsForbidden ¶
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/errorx"
)
func main() {
fmt.Println(errorx.IsForbidden(errorx.Forbidden("X", "x")))
}
Output: true
func IsInternal ¶
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/errorx"
)
func main() {
fmt.Println(errorx.IsInternal(errorx.Internal("X", "x")))
}
Output: true
func IsKernelError ¶
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 ¶
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 ¶
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/errorx"
)
func main() {
fmt.Println(errorx.IsRequestTimeout(errorx.RequestTimeout("X", "x")))
}
Output: true
func IsTimeout ¶
Example ¶
package main
import (
"fmt"
"github.com/aisphereio/kernel/errorx"
)
func main() {
fmt.Println(errorx.IsTimeout(errorx.Timeout("X", "x")))
}
Output: true
func IsTooManyRequests ¶
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 IsUnavailable ¶
func IsValidCode ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 Wrap ¶
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 ¶
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
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" 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" CodeTimeout Code = "TIMEOUT" )
func CodeOf ¶
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 NormalizeCode ¶
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
type CodedError ¶
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 ¶
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 ¶
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 ¶
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 Conflict ¶
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 ¶
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 ¶
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 ¶
GatewayTimeout is an alias that mirrors Kratos naming.
func Internal ¶
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 ¶
InternalServer is an alias that mirrors Kratos naming.
func New ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 (*Error) Cause ¶
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 ¶
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 ¶
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) ErrorCode ¶
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 ¶
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) HTTPStatus ¶
func (*Error) Is ¶
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 ¶
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 ¶
Metadata returns a defensive copy to avoid accidental mutation across layers.
func (*Error) PublicMetadata ¶
PublicMetadata returns metadata that is explicitly safe for transport responses. httpx may choose to expose it depending on environment/config.
func (*Error) StatusCode ¶
StatusCode is provided for GoFr-style interoperability.
func (*Error) Unwrap ¶
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) WithMetadata ¶
WithMetadata returns a cloned error with internal 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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 WithStatusCode ¶
func WithTraceID ¶
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 ¶
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.
func SeverityOf ¶
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
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 ¶
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.