goerr

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 1, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

README

goerr

A structured error type that carries an operator-facing message, a separate user-facing message, an error code, contextual key/value data, a trace correlation ID, and a severity level — so errors flow from your service layer to your HTTP / gRPC layer without lossy stringification.

Zero third-party dependencies.

Install

go get github.com/ubgo/goerr

Quick start

package main

import (
    "fmt"

    "github.com/ubgo/goerr"
)

func main() {
    err := goerr.New("db: deadlock detected on user_orders",
        goerr.WithMessageUser("Something went wrong, please retry"),
        goerr.WithCode("E_TX_RETRY"),
        goerr.WithTraceID("trace-abc123"),
        goerr.WithKV("table", "user_orders"),
    )

    fmt.Println(err.Error())             // operator-facing
    fmt.Println(err.ErrorUser().Error()) // user-facing
    fmt.Println(err.Response().ToJSON()) // wire payload
}

Output:

db: deadlock detected on user_orders
Something went wrong, please retry
{"messageUser":"Something went wrong, please retry","message":"db: deadlock detected on user_orders","code":"E_TX_RETRY","traceId":"trace-abc123","timestamp":"2026-05-02T10:11:12Z","level":"error","kv":{"table":"user_orders"}}

Two messages — why?

Production errors usually need two surfaces:

Surface Audience Example
Error() (operator) Logs, alerts, on-call dashboards db: deadlock detected on user_orders pk=42 retried=3
ErrorUser() (user) HTTP body, GraphQL error response Service is busy, please try again in a moment

Mixing them up leaks internals to users or hides actionable detail from operators. goerr keeps them on the same struct so you can construct once and surface twice.

Global callbacks

Forward every error to an external sink (Sentry, Loki, Slack) without sprinkling reporter calls everywhere:

goerr.AddCallback(func(ctx context.Context, r *goerr.ErrorResult) {
    sentryClient.CaptureException(ctx, r)
})

Callbacks fire synchronously on Error.Error() and ErrorUser.Error() calls. Long-running observers should fan out to a goroutine themselves.

For tests, goerr.ResetCallbacks() clears all registered hooks.

API

Constructors
Function Purpose
New(message string, options ...ErrorOption) *Error Operator + user message default to message.
NewDefault(options ...ErrorOption) *Error Generic "An error occurred" message; intended as a starting point you mutate per call site.
Options
Option Purpose
WithMessageUser(string) Override the user-facing message.
WithCode(string) Stable error code (e.g. E_NOT_FOUND).
WithData(any) Arbitrary payload visible in ToJSON.
WithTraceID(string) Trace correlation ID.
WithLevel(string) Severity (default "error").
WithKV(key, value) / WithKVMap(map) Add contextual key/value entries.
WithCtx(context.Context) Attach a context — passed to callbacks.
Methods
Method Purpose
(*Error).Error() string Operator-facing message, fires callbacks.
(*Error).ErrorUser() *ErrorUser Wrapped error whose .Error() returns the user-facing message.
(*Error).Response() ErrorResult Copy of the wire payload.
(*Error).SetMessage / SetMessageUser / SetKV Mutate after construction.
(*ErrorResult).ToJSON() string Marshal to JSON for HTTP responses.
AddCallback(fn) / ResetCallbacks() Manage global error hooks.

License

Apache License 2.0. See LICENSE.

Documentation

Overview

Package goerr is a structured error type for application-level errors that carry user-facing copy, an error code, contextual key/value data, trace correlation IDs, and a severity level. It is designed to flow from your service layer to your HTTP / gRPC layer without lossy stringification.

Two messages: the Error.Error() return is the operator-facing message (logs, alerts), the ErrorUser is the user-facing message (HTTP response body, GraphQL error). Use SetMessage / SetMessageUser, or the constructor options, to populate them independently.

Global callbacks can be registered with AddCallback to forward every error to an external sink (Sentry, Loki, Slack). Callbacks fire on Error.Error() and ErrorUser.Error() so they capture every formatted surface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddCallback

func AddCallback(callback callbackFn)

AddCallback registers a global hook that runs on every Error.Error() and ErrorUser.Error() invocation. Callbacks fire synchronously in registration order — long-running observers should fan out to a goroutine themselves.

func ResetCallbacks

func ResetCallbacks()

ResetCallbacks removes all registered callbacks. Mainly useful for tests to keep callback state from leaking between table-driven cases.

Types

type Error

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

Error is the structured error type. Construct with New / NewDefault and the With* options.

func New

func New(message string, options ...ErrorOption) *Error

New constructs an Error with the given operator-facing message. The user- facing message defaults to the same string; override with WithMessageUser.

func NewDefault

func NewDefault(options ...ErrorOption) *Error

NewDefault is New with a generic "An error occurred" message. Use it as a starting point for a per-call site that injects all its data via SetMessage / SetMessageUser at error time.

func (*Error) Error

func (e *Error) Error() string

Error returns the operator-facing message and fires registered callbacks.

func (*Error) ErrorUser

func (e *Error) ErrorUser() *ErrorUser

ErrorUser returns a separate sub-error that surfaces the user-facing message.

func (*Error) Response

func (e *Error) Response() ErrorResult

Response returns a copy of the underlying ErrorResult.

func (*Error) SetKV

func (e *Error) SetKV(key string, value any) *Error

SetKV adds or overwrites a single contextual key/value pair.

func (*Error) SetMessage

func (e *Error) SetMessage(message string) *Error

SetMessage updates the operator-facing message after construction.

func (*Error) SetMessageUser

func (e *Error) SetMessageUser(message string) *Error

SetMessageUser updates the user-facing message after construction.

type ErrorOption

type ErrorOption func(*Error)

ErrorOption configures a New / NewDefault Error.

func WithCode

func WithCode(code string) ErrorOption

WithCode tags the error with a stable code identifier (e.g. "E_NOT_FOUND").

func WithCtx

func WithCtx(ctx context.Context) ErrorOption

WithCtx attaches a context.Context to the error. Callbacks receive this context, so observability hooks can extract trace IDs, request IDs, and the active logger from it.

func WithData

func WithData(data any) ErrorOption

WithData attaches an arbitrary payload that will appear in ToJSON output.

func WithKV

func WithKV(key string, value any) ErrorOption

WithKV adds a single key/value pair to the error's KV map.

func WithKVMap

func WithKVMap(kv map[string]any) ErrorOption

WithKVMap adds every entry from kv to the error's KV map. Existing keys are overwritten.

func WithLevel

func WithLevel(level string) ErrorOption

WithLevel overrides the default "error" severity.

func WithMessageUser

func WithMessageUser(message string) ErrorOption

WithMessageUser overrides the default user-facing message.

func WithTraceID

func WithTraceID(traceID string) ErrorOption

WithTraceID sets the trace correlation ID. Typically populated from your tracing library.

type ErrorResult

type ErrorResult struct {
	MessageUser string         `json:"messageUser,omitempty"`
	Message     string         `json:"message,omitempty"`
	Code        string         `json:"code,omitempty"`
	Data        any            `json:"data,omitempty"`
	TraceID     string         `json:"traceId,omitempty"`
	Timestamp   string         `json:"timestamp,omitempty"`
	Level       string         `json:"level,omitempty"`
	KV          map[string]any `json:"kv,omitempty"`
}

ErrorResult is the wire-format payload of an error.

func (*ErrorResult) ToJSON

func (e *ErrorResult) ToJSON() string

ToJSON returns the ErrorResult marshalled as a JSON string. On marshal failure (which should be impossible for the documented field types) it returns the empty string.

type ErrorUser

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

ErrorUser is a thin wrapper around Error whose .Error() returns the user-facing message. Use it when surfacing errors to clients (HTTP response bodies, GraphQL errors) so internal implementation details don't leak.

func (*ErrorUser) Error

func (e *ErrorUser) Error() string

Error returns the user-facing message and fires registered callbacks.

Jump to

Keyboard shortcuts

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