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.
// 3. Create a new validator
docValidator, validatorErrs := NewValidator(document)
if validatorErrs != nil {
panic(validatorErrs)
}
// 4. 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.