Validation hooks
OpenAPI specifies most validation declaratively (required fields, pattern, min/max, enum, etc.). go-swagger turns those rules into code on the generated model types via two interfaces:
Both live in the root runtime
package โ see runtime.Validatable
and runtime.ContextValidatable
for the authoritative definitions. The strfmt.Registry argument carries
the active string-format registry (date-time, UUID, โฆ) so format-aware
validation has access to it.
ContextValidatable is the context-aware version; it should be preferred
in new code because some validations (read-only / write-only flags,
async-driven cross-field checks) genuinely need request scope.
When the runtime calls them
Server-side, validation runs as part of Context.BindValidRequest,
which fires after security
and just after parameter binding:
flowchart TD
sec["Security ยท Context.Authorize<br/>Authenticator โ Authorizer"]
bind["Binder<br/>Consumer decodes body into the parameter struct"]
val["Validator (per parameter)<br/>1. spec-driven validation (required, pattern, โฆ)<br/>2. if Validatable: Validate(formats)<br/>3. if ContextValidatable: ContextValidate(ctx, formats)"]
err{{"errors.CompositeValidationError<br/>aggregates every parameter-level violation<br/>(does not stop on first failure)"}}
sec --> bind --> val
val -. on error .-> err
Two consequences worth being aware of:
- Multiple errors per parameter set. A request with three invalid
fields produces a
CompositeValidationErrorcontaining three entries, not a single one. - Both layers run. Implementing
Validatabledoes not turn off spec-driven validation; the two layers compose. UseValidatablefor rules the spec cannot express (cross-field invariants, business rules).
Client-side, generated request models implement the same interfaces, and
the generated Validate method runs before the body is serialised โ a
malformed payload fails locally instead of producing a server-side 422.
Custom validation in your own types
Most users never write these by hand โ they fall out of swagger generate.
But for hand-rolled types you can add cross-field checks like this:
// DateRange illustrates a cross-field invariant on a hand-written type.
type DateRange struct {
From strfmt.Date `json:"from"`
To strfmt.Date `json:"to"`
}
// Validate enforces that To is not before From. The strfmt.Registry
// argument is unused here because the rule does not involve any
// registered string format.
func (d DateRange) Validate(_ strfmt.Registry) error {
if time.Time(d.To).Before(time.Time(d.From)) {
return errors.New("DateRange.to must not be before DateRange.from")
}
return nil
}Full source: docs/examples/core/validation/main.go
For checks that depend on the request:
// ContextValidate enforces that on_behalf_of is only set when the
// request context carries an authenticated user.
func (r MyRequest) ContextValidate(ctx context.Context, _ strfmt.Registry) error {
if reqUser(ctx) == "" && r.OnBehalfOf != "" {
return errors.New("on_behalf_of is only valid when authenticated")
}
return nil
}Full source: docs/examples/core/validation/main.go
Strfmt registry
Both methods take a strfmt.Registry, which is how the runtime carries
named formats (date-time, uuid, email, โฆ) into the validator. You
rarely build one by hand โ the server’s *Context and the client Runtime
each carry one and pass it down. To register a custom format
(x-go-type style), call strfmt.Default.Add(...) once at startup; the
default registry is what both sides use unless overridden.