Getting started
pkg/validation compiles an OpenAPI 3.0.x document once. Use the result to validate raw JSON request bodies and decode path and query parameters into validated JSON.
Install
Section titled “Install”go get github.com/djosh34/klopt/pkg/validationParse once
Section titled “Parse once”spec, err := os.ReadFile("openapi.yaml")if err != nil { return err}
requestValidations, err := validation.Parse(spec)if err != nil { return err}The map is keyed by exact, case-sensitive OpenAPI operationId. Every operation has one RequestValidation containing its optional Body, Query, and Path components. Parse at startup, then reuse the compiled values. Do not mutate them after parsing.
Validate a request body
Section titled “Validate a request body”func validateCreateThing(r *http.Request, requestValidation *validation.Validation) error { body, err := io.ReadAll(r.Body) if err != nil { return err }
return errors.Join(requestValidation.Validate(body)...)}Call it with requestValidations["createThing"].Body. Empty bytes mean the body is absent. JSON null is a present body and follows the schema’s type and nullable rules.
Decode a path
Section titled “Decode a path”type GetThingPath struct { ThingID int `json:"thingID"`}
func decodeGetThingPath(operationURL *url.URL, decoder *validation.PathDecoder) (GetThingPath, error) { raw, err := decoder.DecodePathParams(operationURL) if err != nil { return GetThingPath{}, err }
var path GetThingPath if err := json.Unmarshal(raw, &path); err != nil { return GetThingPath{}, err }
return path, nil}Call it with the router’s operation-relative URL and requestValidations["getThing"].Path. Remove any effective OpenAPI server URL path prefix before calling the decoder. The decoder matches the selected operation’s exact path template; it does not route requests or resolve servers.
Decode a query
Section titled “Decode a query”type ListThingsQuery struct { Tags []string `json:"tags"` Limit int `json:"limit"`}
func decodeListThings(r *http.Request, decoder *validation.QueryDecoder) (ListThingsQuery, error) { raw, err := decoder.Decode(r.URL) if err != nil { return ListThingsQuery{}, err }
var query ListThingsQuery if err := json.Unmarshal(raw, &query); err != nil { return ListThingsQuery{}, err }
return query, nil}Call it with requestValidations["listThings"].Query. The decoder handles the OpenAPI wire format and returns ordinary JSON, leaving the final Go type under your control.
Next: why validation happens before unmarshalling, how path decoding works, how query decoding works, and the architecture.