Validating OpenAPI contacts, requests and responses
Validate http requests, responses, schemas and OpenAPI 3+ contractsGet libopenapi-validator
The first step to validation, is to get the libopenapi-validator
module added to the project,
validation does not come with libopenapi
as a core module.
libopenapi-validator
depends on github.com/santhosh-tekuri/jsonschema
as the validation engine for schemas. This dependency is only required for validation and not for the core
libopenapi
library to operate.
To avoid having folks import dependencies that are only used for a certain capability, we have kept validation as an opt-in feature, if the host application requires it.
The structure of the validator
libopenapi-validator
is composed of five core packages, each providing a type of validation:
- parameters
- paths
- requests
- responses
- schema_validation **
**Schema validation is handled slightly differently to the other four, but follow the same overall designs.
Each package can be used independently, as each one has its own XXValidator
interface.
Each package validator can be created from an existing libopenapi.Document
instance, using the package
level NewXXValidator
function.
For example, to create a new ParameterValidator
the code would be:
import (
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/parameters"
)
func createValidator() {
// 1. Load an OpenAPI Spec into bytes
petstore, err := os.ReadFile("test_specs/petstorev3.json")
if err != nil {
panic(err)
}
// 2. Create a new OpenAPI document using libopenapi
document, docErrs := libopenapi.NewDocument(petstore)
if docErrs != nil {
panic(docErrs)
}
// 3. Create a new ParameterValidator
parameterValidator, validatorErrs := parameters.NewParameterValidator(document)
// ... do something useful
}
Validation errors
Every validation method or function tha validates something in libopenapi-validator
returns a slice
of errors.ValidationError
pointers.
ValidationError
is a struct, and it has the following properties.
// ValidationError is a struct that contains all the information about a validation error.
type ValidationError struct {
// Message is a human-readable message describing the error.
Message string
// Reason is a human-readable message describing the reason for the error.
Reason string
// ValidationType is a string that describes the type of validation that failed.
ValidationType string
// ValidationSubType is a string that describes the subtype of validation that failed.
ValidationSubType string
// SpecLine is the line number in the spec where the error occurred.
SpecLine int
// SpecCol is the column number in the spec where the error occurred.
SpecCol int
// HowToFix is a human-readable message describing how to fix the error.
HowToFix string
// SchemaValidationErrors is a slice of SchemaValidationFailure objects that describe the validation errors
// This is only populated whe the validation type is against a schema.
SchemaValidationErrors []*SchemaValidationFailure
// Context is the object that the validation error occurred on. This is usually a pointer to a schema
// or a parameter object.
Context interface{}
}
Schema errors
If the error is schema error, then the SchemaValidationErrors
property on ValidationError
will
contain a slice of errors.SchemaValidationFailure
pointers.
The SchemaValidationFailure
struct looks like this:
type SchemaValidationFailure struct {
// Reason is a human-readable message describing the reason for the error.
Reason string
// Location is the XPath-like location of the validation failure
Location string
// DeepLocation is the path to the validation failure as exposed by the jsonschema library.
DeepLocation string `json:"deepLocation,omitempty" yaml:"deepLocation,omitempty"`
// AbsoluteLocation is the absolute path to the validation failure as exposed by the jsonschema library.
AbsoluteLocation string `json:"absoluteLocation,omitempty" yaml:"absoluteLocation,omitempty"`
// Line is the line number where the violation occurred. This may a local line number
// if the validation is a schema (only schemas are validated locally, so the line number will be relative to
// the Context object held by the ValidationError object).
Line int
// Column is the column number where the violation occurred. This may a local column number
// if the validation is a schema (only schemas are validated locally, so the column number will be relative to
// the Context object held by the ValidationError object).
Column int
// The original error object, which is a jsonschema.ValidationError object.
OriginalError *jsonschema.ValidationError
}
The Line
and Column
are relative to the schema that evaluated, not the overall OpenAPI document.
All schemas are resolved into ‘inline’ renders, in order to be able to validate them. The schema
is assembled into a fully resolved JSON Schema, meaning all $ref
values will be compiled
into inline data.
Be careful of infinite circular references.
High-level validation
The validator provides an overall validator, for simple access to three core elements of validation that will suit most requirements:
- Request Validation
- Request & Response Validation
- Document Validation
The high-level validator can be created using NewValidator()
and passing in a libopenapi.Document
instance.
import (
"github.com/pb33f/libopenapi"
validator "github.com/pb33f/libopenapi-validator"
)
func doSomething() {
// 1. Load an OpenAPI Spec into bytes
petstore, err := os.ReadFile("test_specs/petstorev3.json")
if err != nil {
panic(err)
}
// 2. Create a new OpenAPI document using libopenapi
document, docErrs := libopenapi.NewDocument(petstore)
if docErrs != nil {
panic(docErrs)
}
// 3. Create a new Validator
highLevelValidator, validatorErrs := validator.NewValidator(document)
// ... do something useful
}
The interface for the high level validator has the following signature:
type Validator interface {
ValidateHttpRequest(request *http.Request) (bool, []*errors.ValidationError)
ValidateHttpRequestResponse(request *http.Request, response *http.Response) (bool, []*errors.ValidationError)
ValidateDocument() (bool, []*errors.ValidationError)
// To access individual validators
GetParameterValidator() parameters.ParameterValidator
GetRequestBodyValidator() requests.RequestBodyValidator
GetResponseBodyValidator() responses.ResponseBodyValidator
}
Validating http.Request
If the application is handling HTTP traffic and validation for incoming requests against an OpenAPI specification is required, then validating a request is straight forward.
import (
"github.com/pb33f/libopenapi"
validator "github.com/pb33f/libopenapi-validator"
"net/http"
)
func doSomething() {
// ... create OpenAPI Document
// Create a new Validator
highLevelValidator, validatorErrs := validator.NewValidator(document)
if len(validatorErrs) > 0 {
panic("document is bad")
}
var request *http.Request
// ... the application populates the request
// Validate the request
requestValid, validationErrors := highLevelValidator.ValidateHttpRequest(request)
if !requestValid {
for i := range validationErrors {
fmt.Println(validationErrors[i].Message) // or something.
}
}
}
Validating http.Request and http.Response
If the application is handling HTTP traffic and validation for requests and responses against an OpenAPI specification is required, then validating a request/response is also straight forward.
import (
"github.com/pb33f/libopenapi"
validator "github.com/pb33f/libopenapi-validator"
"net/http"
)
func doSomething() {
// ... create OpenAPI Document
// Create a new Validator
highLevelValidator, validatorErrs := validator.NewValidator(document)
if len(validatorErrs) > 0 {
panic("document is bad")
}
var request *http.Request
var response *http.Response
// ... the application populates the request
// Validate the request and the response
requestValid, validationErrors := highLevelValidator.ValidateHttpRequestResponse(request, response)
if !requestValid {
for i := range validationErrors {
fmt.Println(validationErrors[i].Message) // or something.
}
}
}
Validating just http.Response
Don’t care about the request? Just want to validate the response? No problem, however you will still need the request, because it’s used as the lookup to find the response schema.
In the responses
package, there is a ResponseBodyValidator
interface that can be used to validate a response body only.
import (
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/responses"
)
func createValidator() {
// ... create an OpenAPI Document
// 3. Create a new ResponseBodyValidator
rbValidator, _ := parameters.NewResponseBodyValidator(document)
// The *http.Request pointer is still required, however it is not validated by this validator.
// it's used to lookup the path/operation/schemas
validBody, errors := rvValidator.ValidateResponseBody(request, response)
// check the errors...
}
Validating HTTP Parameters
When validating HTTP parameters, the ParameterValidator
interface can be used to validate the parameters
specifically. It may be useful if the need to validate the parameters in isolation is required.
The ParameterValidator
interface has the following signature:
import (
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
)
type ParameterValidator interface {
// SetPathItem will set the pathItem for the ParameterValidator, all validations will be performed against this pathItem
// otherwise if not set, each validation will perform a lookup for the pathItem based on the *http.Request
// *** THIS WAS REMOVED IN v0.1.0 ***
SetPathItem(path *v3.PathItem, pathValue string)
// ValidateQueryParams accepts an *http.Request and validates the query parameters against the OpenAPI specification.
// The method will locate the correct path, and operation, based on the verb. The parameters for the operation
// will be matched and validated against what has been supplied in the http.Request query string.
ValidateQueryParams(request *http.Request) (bool, []*errors.ValidationError)
// ValidateQueryParamsWithPathItem accepts an *http.Request and validates the query parameters against the OpenAPI specification.
// The method will locate the correct path, and operation, based on the verb. The parameters for the operation
// will be matched and validated against what has been supplied in the http.Request query string.
// *** THIS WAS ADDED IN v0.1.0 ***
ValidateQueryParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
// ValidateHeaderParams validates the header parameters contained within *http.Request. It returns a boolean
// stating true if validation passed (false for failed), and a slice of errors if validation failed.
ValidateHeaderParams(request *http.Request) (bool, []*errors.ValidationError)
// ValidateHeaderParamsWithPathItem validates the header parameters contained within *http.Request. It returns a boolean
// stating true if validation passed (false for failed), and a slice of errors if validation failed.
// *** THIS WAS ADDED IN v0.1.0 ***
ValidateHeaderParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
// ValidateCookieParams validates the cookie parameters contained within *http.Request.
// It returns a boolean stating true if validation passed (false for failed), and a slice of errors if validation failed.
ValidateCookieParams(request *http.Request) (bool, []*errors.ValidationError)
// ValidateCookieParamsWithPathItem validates the cookie parameters contained within *http.Request.
// It returns a boolean stating true if validation passed (false for failed), and a slice of errors if validation failed.
// *** THIS WAS ADDED IN v0.1.0 ***
ValidateCookieParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
// ValidatePathParams validates the path parameters contained within *http.Request. It returns a boolean stating true
// if validation passed (false for failed), and a slice of errors if validation failed.
ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError)
// ValidatePathParamsWithPathItem validates the path parameters contained within *http.Request. It returns a boolean stating true
// if validation passed (false for failed), and a slice of errors if validation failed.
// *** THIS WAS ADDED IN v0.1.0 ***
ValidatePathParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
// ValidateSecurity validates the security requirements for the operation. It returns a boolean stating true
// if validation passed (false for failed), and a slice of errors if validation failed.
ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError)
// ValidateSecurityWithPathItem validates the security requirements for the operation. It returns a boolean stating true
// if validation passed (false for failed), and a slice of errors if validation failed.
// *** THIS WAS ADDED IN v0.1.0 ***
ValidateSecurityWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
}
Validating an OpenAPI document
Want to validate the raw OpenAPI document? No problem, the ValidateDocument()
method on the high-level validator will do that for you.
docValidator, validatorErrs := NewValidator(document)
if validatorErrs != nil {
panic(validatorErrs)
}
// validate!
valid, validationErrs := docValidator.ValidateDocument()
if !valid {
for i, e := range validationErrs {
// 5. Handle the error
fmt.Printf("Type: %s, Failure: %s\n", e.ValidationType, e.Message)
fmt.Printf("Fix: %s\n\n", e.HowToFix)
}
}
}
Which would output something like this:
The schema violations will be populated on the SchemaValidationErrors
property of the
ValidationError
struct pointer.
Validating Schemas
Once a valid libopenapi.Document
exists, any high-level base.Schema
can be used to validate a blob
of JSON or YAML, or even an unmarshalled JSON/YAML object.
The schema_validator
package contains a SchemaValidator
interface with the following signature:
type SchemaValidator interface {
// ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string.
ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError)
// ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML.
// This is a pre-decoded object that will skip the need to unmarshal a string of JSON/YAML.
ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError)
// ValidateSchemaBytes accepts a schema object to validate against, and a byte slice containing a schema to
// validate against.
ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError)
}
To create a new SchemaValidator
instance, use the NewSchemaValidator()
function.
Version-aware validation
This was added in
libopenapi-validator
inv0.6.0
OpenAPI 3.0 and OpenAPI 3.1 have fundamental differences in how they handle certain keywords. Most notably,
OpenAPI 3.0 uses nullable: true
while OpenAPI 3.1 uses type: ["string", "null"]
arrays.
By default, libopenapi-validator
validates schemas using OpenAPI 3.1+ behavior (strict JSON Schema compliance).
However, you can now explicitly specify the OpenAPI version to get the correct validation behavior.
OpenAPI 3.0 schemas with nullable: true
would fail validation with the default 3.1+ behavior,
because the nullable
keyword is not part of JSON Schema. The new version-aware functions solve this by:
- 3.0 validation: Accepts and processes
nullable
,discriminator
, and other OpenAPI 3.0 keywords - 3.1 validation: Rejects OpenAPI 3.0 keywords like
nullable
and enforces strict JSON Schema compliance
Using version-aware validation
The SchemaValidator
interface now includes three additional methods that accept an OpenAPI version parameter:
type SchemaValidator interface {
// original methods (default to OpenAPI 3.1+ behavior)
ValidateSchemaString(schema *base.Schema, payload string) (bool, []*errors.ValidationError)
ValidateSchemaObject(schema *base.Schema, payload interface{}) (bool, []*errors.ValidationError)
ValidateSchemaBytes(schema *base.Schema, payload []byte) (bool, []*errors.ValidationError)
// version-aware methods (NEW in v0.6+)
ValidateSchemaStringWithVersion(schema *base.Schema, payload string, version float32) (bool, []*errors.ValidationError)
ValidateSchemaObjectWithVersion(schema *base.Schema, payload interface{}, version float32) (bool, []*errors.ValidationError)
ValidateSchemaBytesWithVersion(schema *base.Schema, payload []byte, version float32) (bool, []*errors.ValidationError)
}
Example: OpenAPI 3.0 nullable support
import (
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/schema_validation"
"github.com/pb33f/libopenapi-validator/config"
)
func validateWithVersion() {
// create schema validator with OpenAPI mode enabled
validator := schema_validation.NewSchemaValidator(config.WithOpenAPIMode())
// example schema with OpenAPI 3.0 nullable keyword
spec := `openapi: 3.0.0
paths:
/users:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
nullable: true`
document, _ := libopenapi.NewDocument([]byte(spec))
model, _ := document.BuildV3Model()
schema := model.Model.Paths.PathItems.GetOrZero("/users").Post.RequestBody.Content.GetOrZero("application/json").Schema
// this payload has a null name, which should be valid in OpenAPI 3.0 with nullable: true
payload := `{"name": null}`
// validate with OpenAPI 3.0, will pass
valid, errors := validator.ValidateSchemaStringWithVersion(schema.Schema(), payload, 3.0)
// valid == true, errors == []
// validate with OpenAPI 3.1, will fail (nullable not allowed)
valid, errors = validator.ValidateSchemaStringWithVersion(schema.Schema(), payload, 3.1)
// valid == false, errors will contain "nullable keyword is not supported in OpenAPI 3.1+"
}
Configuring OpenAPI mode
Control OpenAPI-specific validation behavior using configuration options:
import (
"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/schema_validation"
)
// enable OpenAPI mode (default: enabled)
validator := schema_validation.NewSchemaValidator(config.WithOpenAPIMode())
// disable OpenAPI mode (pure JSON Schema validation)
validator := schema_validation.NewSchemaValidator(config.WithoutOpenAPIMode())
// combine with other options
validator := schema_validation.NewSchemaValidator(
config.WithOpenAPIMode(),
config.WithFormatAssertions(),
config.WithContentAssertions(),
)
Scalar coercion
Scalar coercion allows string values to be automatically validated as boolean, number, or integer types
during schema validation. This mirrors Jackson’s MapperFeature.ALLOW_COERCION_OF_SCALARS
behavior.
Scalar coercion is disabled by default and should only be enabled when you need to accept
string representations of scalar values (like "true"
for boolean or "42"
for integer).
This is common in query parameters, form data, and legacy APIs where all values arrive as strings.
Enabling scalar coercion
import (
"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/schema_validation"
)
// enable scalar coercion
validator := schema_validation.NewSchemaValidator(config.WithScalarCoercion())
// combine with OpenAPI mode for full functionality
validator := schema_validation.NewSchemaValidator(
config.WithOpenAPIMode(),
config.WithScalarCoercion(),
)
Supported coercions
Source Type | Target Type | Valid Examples | Invalid Examples |
---|---|---|---|
string |
boolean |
"true" , "false" |
"yes" , "1" , "TRUE" |
string |
number |
"123" , "-45.67" , "1e5" |
"abc" , "12.34.56" |
string |
integer |
"123" , "-456" , "0" |
"123.45" , "1e5" , "007" |
Example: Query parameter validation
func validateQueryParams() {
spec := `openapi: 3.0.0
paths:
/items:
get:
parameters:
- name: active
in: query
schema:
type: boolean
- name: limit
in: query
schema:
type: integer`
document, _ := libopenapi.NewDocument([]byte(spec))
model, _ := document.BuildV3Model()
// query parameters arrive as strings: ?active=true&limit=10
validator := schema_validation.NewSchemaValidator(
config.WithOpenAPIMode(),
config.WithScalarCoercion(), // Enable string->boolean/integer coercion
)
// this would validate successfully with coercion enabled
activeSchema := model.Model.Paths.PathItems.GetOrZero("/items").Get.Parameters[0].Schema
valid, _ := validator.ValidateSchemaStringWithVersion(activeSchema.Schema(), `"true"`, 3.0)
// valid == true (string "true" coerced to boolean)
limitSchema := model.Model.Paths.PathItems.GetOrZero("/items").Get.Parameters[1].Schema
valid, _ = validator.ValidateSchemaStringWithVersion(limitSchema.Schema(), `"10"`, 3.0)
// valid == true (string "10" coerced to integer)
}
Coercion validation rules
When scalar coercion is enabled, the validator enforces strict format requirements:
- Boolean strings: Must be exactly
"true"
or"false"
(case-sensitive) - Number strings: Must match JSON number format (supports scientific notation)
- Integer strings: Must be valid integers (no decimals, leading zeros, or scientific notation)
// these will pass
"true"
"false"
"123"
"-45.67"
"1.23e-5"
"42"
"-123"
// These will fail
"TRUE"
"yes"
"abc"
"123.45"
"007"
"1e5"