Validáció
A kérés-validáció a gpsystemben deklaratív: szabályokat írsz a struct mezőire tagekben, a futtatást és a hibarenderelést pedig a httperr/validate végzi. A motor a go-playground/validator v10 (a Go de facto standard validátora); a validate csomag pedig azt köti be úgy, hogy minden bukás RFC 9457 problem-válasz legyen, mezőnkénti hibalistával, handler-kód nélkül.
import "github.com/gp-system/httperr/validate"
A Validator típus
func New(opts ...Option) *Validator
func (v *Validator) Validate(out any) error // validál, bukást *httperr.Problem-mé (422) csomagol
func (v *Validator) Struct(s any) error // ugyanaz, a binding pipeline-on kívüli használatra nevezve
func Translate(verrs validator.ValidationErrors) []httperr.FieldError
A New a validátort két rögzített defaulttal építi:
WithRequiredStructEnabled(): a beágyazott structokrequiredszemantikája a validator v11 default szerint működik;- json-tag mezőnevek: a hibákban a mező neve a
jsontagből jön (customerEmail, nemCustomerEmail), beágyazott structoknál pontozott útvonallal (address.city).
Bukáskor a Validate/Struct nem nyers validator-hibát ad vissza, hanem kész *httperr.Problem-et (422), a Translate-tel httperr.FieldError-okká fordított mezőhibákkal: a hibarenderelőnek így nincs több dolga vele. A Translate a gyakori szabályokhoz (required, email, min, max, oneof, gt, ...) ember-olvasható üzenetet ír; ismeretlen szabálynál a failed rule <tag>=<param> formára esik vissza.
Bekötés: strict middleware a handler előtt
A net/http standard strict szervere (az oapi-codegen generálja az OpenAPI specből, lásd Codegen pipeline) json.Decoder-rel dekódol, validáció nélkül: a validate ezt a rést strict middleware-ként zárja be, a teljes bekötött request objektumot (paraméterek + body) validálja a handler előtt. Egy generált gpsystem projektben a kit server.StrictValidator-a az a vékony adapter, ami egy *validate.Validator-t bedrótoz ebbe a middleware-résbe; a generált register.go automatikusan bedrótozza:
shopapigen.NewStrictHandlerWithOptions(apiHandler,
[]shopapigen.StrictMiddlewareFunc{
server.StrictValidator[shopapigen.StrictHandlerFunc](nil),
// ...
},
shopapigen.StrictHTTPServerOptions{ /* httperr hookok */ })
A nil argumentum szándékos: a server.StrictValidator(nil) request-időben oldja fel a validátort, előbb a server.WithValidator-ral beállítottat keresve a contextben, és csak utána esve vissza a validate.New()-ra. Így egyetlen opció a main.go-ban az egész alkalmazásra érvényes, a generált kódhoz nem kell nyúlni. Részletek, a policy-enforcementhez viszonyított pontos middleware-sorrenddel együtt: a szerver mag.
Generált projekten kívül a validate.New() bekötése semmi kit-specifikusat nem igényel: hívd a v.Struct(req)-et bárhol a saját net/http handleredben vagy middleware-láncodban, ahol egyébként egy dekódolt kérést validálnál, és add vissza a kapott hibát változatlanul; az már egy *httperr.Problem.
A shop: a PlaceOrder kérése
A rendelésfeladás body-ja három szabályt hordoz:
type placeOrderBody struct {
ProductID string `json:"productId" validate:"required,uuid4"`
Quantity int `json:"quantity" validate:"required,gt=0"`
CustomerEmail string `json:"customerEmail" validate:"required,email"`
}
Egy kérés, ami mindhármat megsérti (quantity: 0, hibás e-mail, hiányzó productId), ezt a választ kapja:
{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"detail": "One or more fields failed validation.",
"instance": "/api/v1/shop/orders",
"errors": [
{ "field": "productId", "rule": "required", "message": "is required" },
{ "field": "quantity", "rule": "gt", "message": "must be greater than 0" },
{ "field": "customerEmail", "rule": "email", "message": "must be a valid email address" }
]
}
A rule gépi olvasású (a frontend mezőnként ágazhat el rajta), a message pedig ember-olvasható default: a végleges, lokalizált szöveget jellemzően a kliens rendeli a field + rule párhoz.
x-oapi-codegen-extra-tags extensionje, amit TypeSpecből az @extension decoratorral adsz a mezőre:model PlaceOrderBody {
@extension("x-oapi-codegen-extra-tags", #{ validate: "required,gt=0" })
quantity: int32;
}
validate:"required,gt=0" taget emittál a generált mezőre; a fenti pipeline onnantól ugyanúgy fut rajta.Saját tagek
A beépített szabálykészleten túl sajátot a WithRegister-rel regisztrálsz, és a server.WithValidator-ral teszed alkalmazás-szintűvé:
v := validate.New(validate.WithRegister(func(v *validator.Validate) {
_ = v.RegisterValidation("slug", func(fl validator.FieldLevel) bool {
return slugRe.MatchString(fl.Field().String())
})
}))
err := server.Run(ctx, cfg.Server, register, server.WithValidator(v))
Ezután a validate:"slug" tag mindenhol működik: a StrictValidator middleware-ben és a kézi Struct hívásokban is. Saját tag üzenete a Translate fallback-formáját kapja (failed rule slug); ha szebb kell, a kliens oldalon rendeld hozzá.
Mezőnevek testreszabása
A default json-tag-alapú nevezés a WithTagNameFunc-kal cserélhető, például ha a hibákban a form taget akarod látni:
v := validate.New(validate.WithTagNameFunc(func(field reflect.StructField) string {
name, _, _ := strings.Cut(field.Tag.Get("form"), ",")
return name
}))
A binding pipeline-on kívül
A Struct ugyanazt a hibaformát adja bárhol, service-input, konfiguráció vagy worker-payload ellenőrzésére:
if err := v.Struct(input); err != nil {
return err // már *httperr.Problem (422), a handler-lánc kész renderelni
}
Nyers validator.ValidationErrors-t (pl. egy közvetlenül használt validator-instance-ból) a Translate-tel fordítasz ugyanerre a mezőhiba-formára.
Amit a struct-tagek nem fednek le
A validate struct-tagek (validate:"required,gt=0") a szintaktikai/formai szabályokat fedik le. Az adatbázist érintő ellenőrzések (például egyediség) nem tagként élnek: azok a service rétegbe mennek, errs hibaként jelentkeznek. Az authorizáció külön kérdés, azt a policy réteg kezeli, nem egy validációs tag.
A kittel
A server.StrictValidator (lásd a szerver mag) pontosan a fent leírt *validate.Validator-t csomagolja: magán a validátoron semmi nem változik attól, hogy egy generált projektben fut-e vagy egy kézzel írt net/http handlerben, csak a hívás helye.
Használt patternek
- Functional options közvetlenül a becsomagolt típuson (
Option func(*validator.Validate)): Design patternek. - Adapter + fordítási réteg (
Translate:validator.ValidationErrors→httperr.FieldError) a validációs library és a drót-formátum közt.
Kapcsolódó oldalak
- httperr: Áttekintés: a
Problem/FieldErrorforma, amit ez az oldal termel. - Hibaválaszok: a mapping-sor, ami egy validációs
Problem-et olvas. - Szerver:
StrictValidator,WithValidator, és hogy a validáció hol ül a middleware-láncban. - Policy-k: az authorizáció, a réteg, amit a validáció szándékosan nem fed le.