httperr

Hibaválaszok

A httperr csomag, RFC 9457 problem+json minden hibára, egyetlen központi mappinggel.

Minden hiba, ami egy gpsystem service-ből kimegy a dróton (validációs bukás, 404, panic, adatbázis-timeout), RFC 9457 problem dokumentum, Content-Type: application/problem+json-nel. Ezt a httperr garantálja: ő a drót felé néző hibaforma és az egyetlen hely, ahol Go-hibából HTTP-válasz lesz. A hibák építését (stack, kód, publikus üzenet) az errs csomag és a hibamodell írja le, ez az oldal a renderelő oldalt, sima net/http-n.

import "github.com/gp-system/httperr"

A Problem/FieldError forma és a gyors konstruktorok (BadRequest, NotFound, Validation, ...) az Áttekintés oldalon vannak; ez az oldal arról szól, hogyan lesz bármilyen hibából, nem csak egy kézzel épített Problem-ből, ilyen forma.

WriteError és NewWriteError

func WriteError(w http.ResponseWriter, r *http.Request, err error)              // default opciókkal
func NewWriteError(opts ...HandlerOption) func(http.ResponseWriter, *http.Request, error)

func WriteBadRequest(w http.ResponseWriter, r *http.Request, err error)

A WriteError lefuttatja az alábbi mappinget, és a kapott Problem-et application/problem+json-ként írja ki. A NewWriteError HandlerOption-ökkel testreszabott writert épít:

type HandlerOption func(*handlerConfig)

func WithExposeInternal(expose bool) HandlerOption // 5xx detail + stack/chain a válaszban, csak dev!
func WithLogger(l *slog.Logger) HandlerOption      // az 5xx okok loggere (default: slog.Default())

A WriteError szignatúrája szándékosan az oapi-codegen strict-szerverének ResponseErrorHandlerFunc hookjához igazodik, így a generált register.go közvetlenül oda köti be: a handlered által visszaadott hiba a WriteError-on megy át, ragasztókód nélkül. A WriteBadRequest mindent 400-ra képez, a már kész Problem és a 413-as body-limit hiba kivételével, mert egy paraméter-parseolási vagy body-dekódolási hiba sosem 500; az oapi-codegen RequestErrorHandlerFunc/ErrorHandlerFunc hookjaihoz igazodik, ott, ahol a dekódolás, nem a handlered termelte a hibát.

Csomagszintű Problem-sentinelek (var ErrX = httperr.NotFound(...)) így biztonságosan írhatók: a writerek másolatot vesznek, mielőtt Instance-t vagy dev-módú membereket pecsételnének bele, így kérések közt nem szivárog állapot.

A mapping

A WriteError és a WriteBadRequest ugyanazon a mappingen fut, ebben a sorrendben:

Hiba a láncbanVálasz
*httperr.Problemváltoztatás nélkül renderelve (másolat, a sentinel nem mutálódik)
validációs bukás a httperr/validate-ből422, mezőnkénti errors[]-szel (a validátor már Problem-et ad)
*http.MaxBytesError (body a beállított limit felett)413
errs hibaerrs.StatusOf (ha nincs: 500); detail = errs.PublicOf, code = errs.CodeOf
minden más500, detail nélkül

Két keresztirányú szabály egészíti ki:

  • Minden 5xx logolódik: a mögöttes hiba a slog-ba megy (a WithLogger loggerén keresztül, egyébként slog.Default()-tel) a kérés metódusával, útvonalával és trace-kontextusával; errs hibánál a strukturált lánccal és stackkel együtt (lásd errs: Integráció). Egy státusz-mappelt 4xx errs hiba (pl. egy errs.Status(404)-gyel deklarált definition) viszont nem generál logsort és Sentry-eventet: az elvárt kliens-viselkedés nem incidens.
  • Dev módban több látszik: WithExposeInternal(true) mellett az ismeretlen hibák detail-je kitöltődik, és ha a láncban errs hiba van, a válasz stack és chain extension membereket kap (errs.Frames/errs.Chain, lásd errs: API). Productionben a kliens sosem lát belső részletet.

Egy elkapott panic, ami errs.NewPanic-kal alakul *errs.Error-rá (a stackje a panic helyére mutat), pontosan úgy renderelődik és logolódik, mint bármely más 500, mihelyt ehhez a mappinghez ér.

errs-integráció

A mapping errs-sora az a pont, ahol a httperr és az errs találkozik: a Code, a Public és a Status, bármelyik attribútummal is épült vagy Define-olódott a hiba, közvetlenül a Problem code, detail és status mezője lesz, fordítási réteg nélkül. Ezért elég egy hibamódot egyszer, errs.Define-dal deklarálni ahhoz, hogy mindenhol helyesen renderelődjön, ahol visszaadják: a Problem mezői közvetlen olvasatai az errs attribútumoknak, nem egy handler dönt eseti alapon.

Dev mode: kié ez a beállítás?

A WithExposeInternal a httperr saját opciója, de egy generált gpsystem projektben sosem hívod közvetlenül: a server.Config ExposeInternalErrors mezője (HTTP_EXPOSE_INTERNAL_ERRORS env-változó) a kit beállítása, és a server.Run adja át a httperr writernek, amit neked bedrótoz. Ha a httperr-t önállóan, kit nélkül használod, a WithExposeInternal(true) az a kapcsoló, amit magadnak kell felkapcsolnod, a saját projekted env-változójával vagy build tag-jével őrizve; csak tartsd kikapcsolva productionben, ugyanúgy, ahogy a kit teszi, hogy a belső részletek a logba és a Sentrybe menjenek, sose a válaszba.

A shop: mit lát a kliens, ha elfogyott a készlet

A PlaceOrder service-e az ErrOutOfStock definitiont adja vissza (errs.Status(409), errs.Public(...), a deklarációt lásd a Hibamodell oldalon). Mire a POST /api/v1/shop/orders válasza kiér:

{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "detail": "A termék elfogyott.",
  "instance": "/api/v1/shop/orders",
  "code": "shop_api_out_of_stock"
}
  • a 409 az errs.StatusOf-ból jött,
  • a detail a publikus üzenet: a belső Error() szöveg (query, wrap-lánc) soha nem kerül ide,
  • a code stabil, gépi olvasású: a frontend erre ágazhat el (if (body.code === "shop_api_out_of_stock")), az üzenetszöveg változásától függetlenül,
  • log és Sentry: semmi (státusz-mappelt 4xx).

Ha ugyanez a hívás adatbázis-hibán bukik, a kliens egy 500-at kap shop_api_place_order_failed kóddal és a generikus publikus üzenettel, miközben a teljes lánc és stack a logban és a Sentryben landol.

Render és report egyetlen mappingben

A kliens felé látszó (render) és a logba/Sentrybe menő (report) oldal egyetlen központi mappingben él: a render-oldal a fenti táblázat (az inputja az errs.Status/Public/Code), a report-oldal pedig az automatikus 5xx-logolás és a Sentry-integráció. Nem írsz semmilyen render-logikát hibánként: a hibamód deklarációja (errs.Define) hordozza az összes információt, amiből a mapping dolgozik.

Ne kerüld meg a mappinget kézzel írt w.WriteHeader(500) válaszokkal a handlerben. Adj vissza hibát: így a válasz formája, a logolás és a Sentry-jelentés is konzisztens marad, és a tesztjeid is ugyanazt látják, mint a kliens.

A spec oldala

A generált spec/typespec/shared/errors.tsp a httperr.Problem tükre (a FieldError-ral együtt), így az OpenAPI-ból generált frontend-kliensek pontosan ezt a formát kapják; lásd a Codegen pipeline oldalt. Ha az egyiket bővíted, bővítsd a másikat is. A dev-only stack és chain membereket az errors.tsp szándékosan nem tükrözi: productionben soha nem jelennek meg, így kliens nem építhet rájuk.

Használt patternek

A szabvány hivatalos szövege: RFC 9457: Problem Details for HTTP APIs.

Kapcsolódó oldalak

  • httperr: Áttekintés: a Problem forma és a gyors konstruktorok.
  • Validáció: a 422-es forma, amit a mapping validációs sora olvas.
  • errs: API és Hibamodell: honnan jönnek az ezen az oldalon renderelt hibák.
  • Szerver: hogyan drótozza be a kit a WriteError/WriteBadRequest-et a generált strict szerverbe.
Copyright © 2026