Skip to content

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.

Terminal window
go get github.com/djosh34/klopt/pkg/validation
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.

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.

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.

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.