Philosophy
Validate before unmarshalling
Section titled “Validate before unmarshalling”These request bodies mean different things:
{}{"name": null}An ordinary Go struct can lose that distinction:
type Input struct { Name *string `json:"name"`}After json.Unmarshal, Name is nil in both cases. An omitted field keeps its zero value; JSON null sets a pointer to nil. OpenAPI can independently say that name is required and whether it is nullable.
Technically, unmarshalling into map[string]any preserves this particular distinction: a missing key is omitted from the map, while a key containing JSON null has a nil value. Some validation libraries use that representation. It works, and json.Marshal can encode the map again without custom marshaling code. The tradeoff appears when the application still wants Input: it must unmarshal the original body a second time, marshal and unmarshal the map, or walk nested maps with type assertions and conversions. The last option recreates parts of encoding/json’s typed decoding and becomes especially finicky around nested values, integer types, struct tags, and custom UnmarshalJSON methods. Custom presence wrappers have similar bookkeeping costs in every affected model.
Generic unmarshalling can also discard information needed for validation. Given:
{"sequence": 9007199254740993}json.Unmarshal stores the number in an any as a float64, which rounds it to 9007199254740992. Decoder.UseNumber avoids that particular conversion, but requires the whole generic-decoding path to use and interpret json.Number correctly.
That is why Validation.Validate reads json.RawMessage. Raw decoding delays conversion and keeps the original JSON available while the schema is checked. The validator can check presence, nullability, exact numbers, duplicate names, and other schema rules before ordinary unmarshalling creates the application’s typed value. See the standard library’s json.Unmarshal rules, Decoder.UseNumber, and json.RawMessage.
Dynamic validation, plain generated data
Section titled “Dynamic validation, plain generated data”At runtime, the library works like a dynamic Go validator:
requestValidations, err := validation.Parse(spec)if err != nil { return err}
errs := requestValidations["createThing"].Body.Validate(body)The compiled result is also plain Go data that can be generated ahead of time:
var createThing = &validation.Validation{ SchemaPointer: "#/paths/~1things/post/requestBody/content/application~1json/schema", BodyRequired: true, KindValidation: validation.KindValidation{ Type: "string", }, ObjectValidation: validation.ObjectValidation{ AdditionalPropertiesAllowed: true, },}Dynamic OpenAPI libraries normally parse a specification when the process starts. Generated literals let an application or test call Validate immediately. The behavior still lives in one runtime validator instead of being duplicated across generated validation functions.
Fully generated validation code is difficult to make bug-free and difficult to test exhaustively. Generating compiled data is the middle way: no runtime specification parsing when literals are used, but only one validation implementation to harden.
A deliberate subset
Section titled “A deliberate subset”OpenAPI 3.0.3 is large. This library rejects unsupported behavior during parsing instead of guessing. Current examples include oneOf, not, and reference cycles.
This is intentional. Clear rejection is safer than accepting a document with partial semantics. The supported model can grow as its behavior becomes testable.
Deterministic testing
Section titled “Deterministic testing”Validation behavior is specified with ordinary table-driven Go tests. Named matrices cover supported schema rules, malformed input, decoder repeatability, generated/runtime parity, and hand-maintained request-body examples without generated tests, property-test dependencies, or fuzz targets.
Every production package is checked for complete Go statement coverage. The checker permits only one exact, drift-detected encodeString error return that cannot execute after its valid-UTF-8 precondition; it rejects every other uncovered production statement.
OpenAPI details in this documentation follow the OpenAPI 3.0.4 Schema Object.