📖 3 min read (~ 600 words).

Parameter binding & validation

The two stages combined as Context.BindValidRequest turn the incoming *http.Request into a populated parameter struct and surface every spec-level violation in a single response.

What gets bound, in what order

BindValidRequest runs four sub-steps. Any non-recoverable error short-circuits before the binder runs; otherwise binder-level errors are aggregated alongside negotiation errors:

  1. Content-Type validationruntime.HasBody(r) early-outs for bodyless requests; otherwise runtime.ContentType(r.Header) parses the header (a malformed value is a 400) and validateContentType matches it against the operation’s consumes (no match ⇒ 415, match ⇒ pick the registered Consumer; missing Consumer ⇒ 500).
  2. Response format selectionnegotiate.ContentType(r, route.Produces, …) picks the offer that best satisfies Accept; "" ⇒ 406 (errors.InvalidResponseFormat).
  3. Parameter binding — for each declared parameter, the binder reads the right place (path / query / header / formData / body), converts the string(s) to the target Go type and applies any default declared in the spec.
  4. Per-parameter validation — the spec’s declarative rules (required, pattern, minLength, enum, format, …) plus any Validatable / ContextValidatable your model implements.

All errors collected during binding and validation are returned as one errors.CompositeValidationError. The validator does not stop on first failure — a request with three problems produces three entries, so callers learn about everything in one round-trip.

Where each parameter in: reads from

in:SourceNotes
paththe matched route’s RouteParamsNames come from the {placeholder} segments. Required by definition (no default).
queryr.URL.Query()Multi-valued: see collectionFormat (csv, ssv, tsv, pipes, multi).
headerr.HeaderMulti-valued via the same collectionFormats; multi repeats the header name.
formDatar.PostForm for application/x-www-form-urlencoded
or r.MultipartForm for multipart/form-data
File parts come back as runtime.File.
bodyr.Body, decoded via the chosen ConsumerValidation runs against the resulting Go value, including any Validatable hook.

The binder is reflection-based for the untyped path; generated code uses the same primitives by calling Context.BindValidRequest(r, route, &Params) where &Params is the generated parameter struct.

Validation layers

Two layers compose. They are not alternatives.

1. Spec-driven validation
   ├─ required, pattern, minLength/maxLength
   ├─ minimum/maximum, multipleOf, exclusive bounds
   ├─ enum, format (date-time, uuid, email, …)
   └─ items / minItems / maxItems / uniqueItems

2. Validatable / ContextValidatable
   ├─ Validate(strfmt.Registry) error                       (sync)
   └─ ContextValidate(ctx, strfmt.Registry) error           (request-scoped)

See core / validation for the full picture of the hooks; BindValidRequest is the call site.

Where this fits in the pipeline

Conventionally after security and before the operation handler — see pipeline for the diagram and the rationale (failed auth short-circuits with 401 before paying the cost of binding/validation).

Disabling spec-driven parameter validation

If you need to bypass the parameters block entirely (typically for test harnesses or proxy layers that re-validate downstream), Context.SetIgnoreParameters(true) skips spec-driven parameter validation while leaving the rest of the pipeline intact:

ctx := middleware.NewContext(spec, api, nil).SetIgnoreParameters(true)
handler := ctx.APIHandler(middleware.PassthroughBuilder)

Full source: docs/examples/server/binding/main.go

Validatable / ContextValidatable hooks on the model still run.

Reading the bound parameters from extra middleware

Bound parameters are cached in the request context. From middleware mounted via Builder you can re-fetch them without re-binding:

// inside middleware.Builder
match := middleware.MatchedRouteFrom(r)
// (no public accessor for the bound struct itself today —
// re-call BindValidRequest if you need it; the result is cached
// so a second call is cheap)

Full source: docs/examples/server/binding/main.go

MatchedRouteFrom plus SecurityPrincipalFrom and SecurityScopesFrom cover the most common middleware needs (audit logging, per-tenant rate limiting, …).