API
Az errs teljes felülete: öt konstruktor, négy attribútumfüggvény, három accessor, és a Define, amivel egy hibamódot egyszer nevezel el és mindenhol újrahasználod. Minden itt leírt dolog együttműködik a sima errors.Is/errors.As-szal, mert az *errs.Error úgy wrappel, mint bármely más Go hiba. Lásd az Áttekintés oldalt a telepítéshez és a kétüzenetes tervezési elvhez, amire ez az API épül.
import "github.com/gp-system/errs"
Konstruktorok
func New(msg string, attrs ...Attr) *Error
func Errorf(format string, args ...any) *Error
func Wrap(err error, msg string, attrs ...Attr) *Error
func Wrapf(err error, format string, args ...any) *Error
func NewPanic(recovered any, attrs ...Attr) *Error
Newegy friss hibát indít, a hívási pontnál capture-ölve a stacket.ErrorfaNewfmt.Sprintf-stílusú formázással, attribútumok nélkül.Wrapegy új üzenetet fűz egy meglévő hibához, az eredetit okként megtartva;nil-renil, tehát azerrs.Wrap(err, "...")feltétel nélkül biztonságosan hívható egy potenciálisannilerr-re.WrapfaWrapformázással.NewPanicegy elkapott panic-értéket alakít*errs.Error-rá, aminek a stackje a panic helyére mutat,recover()kezelőben használatra.
if err := repo.Save(ctx, order); err != nil {
return errs.Wrap(err, "orders: save", errs.With("order_id", order.ID))
}
if err := validateQuantity(qty); err != nil {
return errs.Errorf("invalid quantity %d: %w", qty, err)
}
Attribútumok
Az Attr egy func(*Error), sorrendben alkalmazva bármelyik konstruktorra:
type Attr func(*Error)
func Code(code string) Attr // gépi olvasású azonosító, üzenetátfogalmazás közben is stabil
func Public(msg string) Attr // az egyetlen szöveg, amit a kliens valaha láthat
func Status(code int) Attr // a HTTP-formájú státusz, amire ez a hiba leképeződik
func With(key string, value any) Attr // strukturált metadata, az adott wrap-szinten rögzítve
var ErrOutOfStock = errs.Define("out_of_stock",
errs.Public("A termék elfogyott."),
errs.Status(http.StatusConflict))
err := ErrOutOfStock.New("orders: decrement stock", errs.With("product_id", "prod_42"))
A Code, a Public és a Status jellemzően egyszer van beállítva, egy Define-olt definitionön; a With az egyetlen attribútum, amihez minden egyes wrap-helyen nyúlsz, hogy az adott híváshoz tartozó metadata-t (egy ID-t, egy darabszámot, egy query-nevet) rögzítsd.
Accessorok
func CodeOf(err error) string
func PublicOf(err error) string
func StatusOf(err error) int
Mindegyik végigmegy a hiba-láncon (az errors.As-en keresztül), keresve az első *errs.Error-t, ami beállította a megfelelő attribútumot, így ugyanúgy működik, akár magát az *errs.Error-t adod át, akár valamit, ami feljebb wrappeli. A StatusOf 0-t ad vissza, ha semmi a láncban nem állított státuszt, így egy renderelő explicit eshet vissza 500-ra, találgatás helyett.
Introspekció: Frames, FullFrames, Chain
type Frame struct {
File string
Line int
Function string
}
type Step struct {
Msg string
File string
Line int
Function string
Code string
Meta map[string]any
}
func Frames(err error) []Frame // a keletkezés pontjában capture-ölt stack
func FullFrames(err error) []Frame // a Frames, plusz a közbeeső, nem wrappelő hívási frame-ek
func Chain(err error) []Step // egy Step wrap-szintenként, a root cause utoljára
A Frames a hívási stack abban a pillanatban, amikor a hiba először létrejött (New, Errorf vagy a láncban az első Wrap); a FullFrames ezen felül a közönséges, a hibát sem nem létrehozó, sem nem wrappelő függvényhívások frame-jeit is tartalmazza, megadva a teljes hívási utat. A Chain a wrap-történet: egy Step minden Wrap/Wrapf híváshoz, a saját üzenetével, a wrap helyével és azzal, amit ott With/Code-dal rögzítettek, a legbelső okkal a végén. Ez a három függvény az, amiből a logolás és a Sentry a strukturált kimenetét építi; ezeken kívül ritkán hívod közvetlenül.
Stack-szemantika: capture a keletkezéskor
A stack trace egyszer capture-ölődik, a New/Errorf/első-Wrap/NewPanic hívásnál, ami az *errs.Error-t létrehozza, runtime.Callers-szel. A későbbi Wrap hívások ugyanazon a hibán egy Step-et adnak a lánchoz, de nem capture-ölik újra a stacket: a stack mindig a keletkezés pontjára mutat, nem arra a hívási helyre, ami épp logolja. Ez a gyakorlatban számít: ha a stacket akkor capture-öznéd, amikor logolsz, nem amikor létrehozod a hibát, egy mélyen wrappelt hiba mindig a logoló hívásra mutatna, ugyanarra a sorra, függetlenül attól, hogy a hiba ténylegesen hol kezdődött. A keletkezéskori capture azt jelenti, hogy a stack pont akkor értelmes, amikor számít, és ingyenes minden más alkalommal, amikor a hibát továbbadják, wrappelik vagy errors.Is-szel ellenőrzik.
errs.Define: elnevezett hibamódok
type Definition struct { /* ... */ }
func Define(code string, attrs ...Attr) *Definition
func (d *Definition) New(msg string, attrs ...Attr) *Error
func (d *Definition) Newf(format string, args ...any) *Error
func (d *Definition) Wrap(err error, msg string, attrs ...Attr) *Error
func (d *Definition) Wrapf(err error, format string, args ...any) *Error
A Define egyszer deklarál egy újrahasználható hibamódot, csomagszintű var-ként, ugyanúgy, ahogy egy sentinel hibát deklarálnál errors.New-val. Megköveteli egy Status attribútum beállítását egy érvényes 4xx vagy 5xx HTTP státuszkóddal, és panicol init-időben, ha ez hiányzik vagy tartományon kívüli:
var ErrOutOfStock = errs.Define("out_of_stock",
errs.Public("A termék elfogyott."),
errs.Status(http.StatusConflict)) // kötelező, különben a Define panicol
Ez az init-időben panicoló viselkedés azért van, mert egy Definition érvényes státusz nélkül olyan kontraktus, amit senki nem tud biztonságosan renderelni, és a hibát a bináris indulásának pillanatában kell elkapni, nem az első kérésen, ami épp átfut rajta. Egy Definition értéke maga a sentinel: az errors.Is(err, ErrOutOfStock) közvetlenül működik ellene, mert minden hiba, amit a .New/.Wrap belőle gyárt, saját Is-t hordoz, ami a definitionre illeszkedik. A .New/.Newf/.Wrap/.Wrapf a csomagszintű konstruktorokat tükrözi, előre feltöltve a definition kódjával, publikus üzenetével és státuszával, így az out_of_stock minden előfordulása a kódbázisban azonosan renderelődik, anélkül hogy ezt a három attribútumot minden hívási helyen meg kellene ismételni.
Önálló példa
package main
import (
"errors"
"fmt"
"net/http"
"github.com/gp-system/errs"
)
var ErrNotFound = errs.Define("not_found",
errs.Public("A keresett elem nem létezik."),
errs.Status(http.StatusNotFound))
func lookup(id string) (string, error) {
if id != "known" {
return "", ErrNotFound.New("lookup", errs.With("id", id))
}
return "value", nil
}
func main() {
_, err := lookup("missing")
fmt.Println(errors.Is(err, ErrNotFound)) // true
fmt.Println(errs.CodeOf(err)) // "not_found"
fmt.Println(errs.StatusOf(err)) // 404
fmt.Println(errs.PublicOf(err)) // "A keresett elem nem létezik."
for _, step := range errs.Chain(err) {
fmt.Printf("%s (%s:%d)\n", step.Msg, step.File, step.Line)
}
}
Kapcsolódó oldalak
- errs: Áttekintés: telepítés, a kétüzenetes tervezés.
- errs: Integráció:
LogValue, a SentryStackTrace()-e, és egy minimális HTTP mapper. - Hibamodell: a teljes kérés-válasz pipeline, amit ez az API táplál.
- httperr: Áttekintés: az
errshibák RFC 9457 válaszokká renderelése.