-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Request decoding, struct validation, content negotiation and response checking for Rex.
go get github.com/kryovyx/rextension-validation
import ( "github.com/kryovyx/rex" validation "github.com/kryovyx/rextension-validation" ) app := rex.New(validation.WithValidation(nil))
A route declares its body schemas, and the middleware does the rest:
type CreateUser struct{ rxroute.Route } func (r *CreateUser) RequestBody() validation.BodySchema { return validation.Scalar(CreateUserRequest{}) } func (r *CreateUser) Responses() map[int]validation.BodySchema { return map[int]validation.BodySchema{ 201: validation.Scalar(UserResponse{}), } }
type CreateUserRequest struct { Email string `json:"email" validate:"required,email"` Age int `json:"age" validate:"gte=18"` }
A route that declares no schemas is passed through untouched.
-
Content-Type check — a body with an unsupported type is
415 -
Accept negotiation — no acceptable representation is
406 - Decode the body through the negotiated codec
-
Validate the struct with
go-playground/validator/v10→422with per-field errors - Check the response against the declared schemas (optional)
- Re-encode the response through the negotiated codec
It runs at rextension.PriorityValidation (500) — after authentication and rate
limiting, so a body is never parsed for a request that was always going to be
refused.
func createUser(ctx rxroute.Context) { body, ok := validation.GetRequestBody[CreateUserRequest](ctx.Request()) if !ok { return // unreachable when the route declares a request schema } _ = ctx.JSON(201, save(body)) }
No decoding, no io.ReadAll, no json.Unmarshal in the handler.
Unknown fields are rejected. An unknown member is almost always a client mistake — a misspelled field, a stale integration, a field renamed on one side only — and accepting it silently is what lets that mistake reach production looking like a success: the request returns 200, the field is dropped, and nobody learns anything until the missing data is noticed downstream.
WithAllowUnknownFields() turns it off. Do that for an established client you
cannot change, not as a default posture.
Errors are RFC 9457 problem documents. ⚠ This is a wire break from earlier versions:
{
"type": "urn:rex:problem:validation-failed",
"title": "Unprocessable Entity",
"status": 422,
"detail": "the request body failed validation",
"errors": [{"field": "email", "rule": "email", "message": "must be a valid address"}]
}Served as application/problem+json. Note tag became rule — "tag"
described the Go struct tag that produced the constraint rather than the
constraint itself, an implementation detail leaking into a public API.
The schema contract is declared in rextension, not here, so the
OpenAPI generator reads
the same interface. One declaration on a route produces both the validation and
the documentation, and they cannot drift.
Everything in this package is an alias of the rextension declaration, so
validation.BodySchema and validation.Scalar keep compiling.
- Declaring Schemas — scalars, unions, per-status responses
- Request Validation — tags, errors, unknown fields
- Response Validation — strict mode and what it costs
- Codecs and Negotiation — JSON, your own, 415 and 406
- Configuration · Troubleshooting