httpvalidator

package
v1.33.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 24, 2025 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package httpvalidator validates HTTP requests and responses against OpenAPI specifications.

This package enables runtime validation of HTTP traffic in API gateways, middleware, and testing scenarios. It supports both OAS 2.0 (Swagger) and OAS 3.x specifications.

Features

  • Request validation: path, query, header, cookie parameters and request body
  • Response validation: status codes, headers, and response body
  • Parameter deserialization: all OAS serialization styles (simple, form, matrix, label, etc.)
  • Schema validation: type checking, constraints, enum, composition (allOf/anyOf/oneOf)
  • Middleware-friendly: works with standard net/http patterns
  • Strict mode: reject unknown parameters and undocumented responses

Basic Usage

Create a validator from a parsed OpenAPI specification:

parsed, _ := parser.ParseWithOptions(parser.WithFilePath("openapi.yaml"))
v, err := httpvalidator.New(parsed)
if err != nil {
    log.Fatal(err)
}

// Validate incoming request
result, err := v.ValidateRequest(req)
if !result.Valid {
    for _, e := range result.Errors {
        log.Printf("Validation error: %s: %s", e.Path, e.Message)
    }
}

// Access validated and deserialized parameters
userID := result.PathParams["userId"]
page := result.QueryParams["page"]

Middleware Pattern

The validator integrates naturally with HTTP middleware:

func ValidateMiddleware(v *httpvalidator.Validator) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            result, _ := v.ValidateRequest(r)
            if !result.Valid {
                http.Error(w, "Invalid request", http.StatusBadRequest)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

For response validation in middleware, use ValidateResponseData which accepts captured response parts instead of *http.Response:

result, _ := v.ValidateResponseData(req, recorder.Code, recorder.Header(), recorder.Body.Bytes())

Functional Options

For one-off validations, use the functional options API:

result, err := httpvalidator.ValidateRequestWithOptions(
    req,
    httpvalidator.WithFilePath("openapi.yaml"),
    httpvalidator.WithStrictMode(true),
)

Strict Mode

Enable strict mode for stricter validation:

v.StrictMode = true

In strict mode:

  • Unknown query parameters cause validation errors
  • Unknown headers (except standard HTTP headers) cause errors
  • Unknown cookies cause errors
  • Undocumented response status codes cause errors

Parameter Deserialization

The validator automatically deserializes parameters according to their OAS style:

  • path: simple (default), label, matrix
  • query: form (default), spaceDelimited, pipeDelimited, deepObject
  • header: simple (default)
  • cookie: form (default)

Deserialized values are available in the result:

result.PathParams["id"]      // Deserialized path parameter
result.QueryParams["page"]   // Deserialized query parameter
result.HeaderParams["X-API"] // Deserialized header parameter
result.CookieParams["token"] // Deserialized cookie parameter

Schema Validation

The validator performs JSON Schema validation on request/response bodies including:

  • Type checking (string, number, integer, boolean, array, object, null)
  • String constraints (minLength, maxLength, pattern, format, enum)
  • Number constraints (minimum, maximum, exclusiveMin/Max, multipleOf)
  • Array constraints (minItems, maxItems, uniqueItems)
  • Object constraints (required, properties, additionalProperties)
  • Composition (allOf, anyOf, oneOf)
  • Nullable fields (OAS 3.0 nullable, OAS 3.1 type arrays)

Index

Examples

Constants

View Source
const (
	SeverityError    = severity.SeverityError
	SeverityWarning  = severity.SeverityWarning
	SeverityInfo     = severity.SeverityInfo
	SeverityCritical = severity.SeverityCritical
)

Severity constants re-exported for convenience.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*config) error

Option is a functional option for configuring validation.

func WithFilePath

func WithFilePath(path string) Option

WithFilePath sets the path to the OpenAPI specification file. The file will be parsed automatically.

func WithIncludeWarnings

func WithIncludeWarnings(include bool) Option

WithIncludeWarnings sets whether to include best-practice warnings. Default is true.

func WithParsed

func WithParsed(result *parser.ParseResult) Option

WithParsed uses a pre-parsed OpenAPI specification. This is more efficient when validating multiple requests.

func WithSkipBodyValidation

func WithSkipBodyValidation(skip bool) Option

WithSkipBodyValidation skips request/response body validation. Useful when body validation is too expensive or handled elsewhere.

func WithSkipCookieValidation

func WithSkipCookieValidation(skip bool) Option

WithSkipCookieValidation skips cookie parameter validation.

func WithSkipHeaderValidation

func WithSkipHeaderValidation(skip bool) Option

WithSkipHeaderValidation skips header parameter validation.

func WithSkipQueryValidation

func WithSkipQueryValidation(skip bool) Option

WithSkipQueryValidation skips query parameter validation.

func WithStrictMode

func WithStrictMode(strict bool) Option

WithStrictMode enables stricter validation:

  • Rejects requests with unknown query parameters
  • Rejects requests with unknown headers (except standard HTTP headers)
  • Rejects requests with unknown cookies
  • Rejects responses with undocumented status codes

Default is false.

type ParamDeserializer

type ParamDeserializer struct{}

ParamDeserializer handles deserialization of HTTP parameters according to OpenAPI serialization styles. Each parameter location has default styles:

| Location | Default Style | Default Explode | |----------|---------------|-----------------| | path | simple | false | | query | form | true | | header | simple | false | | cookie | form | false |

func NewParamDeserializer

func NewParamDeserializer() *ParamDeserializer

NewParamDeserializer creates a new parameter deserializer.

func (*ParamDeserializer) DeserializeCookieParam

func (d *ParamDeserializer) DeserializeCookieParam(value string, param *parser.Parameter) any

DeserializeCookieParam deserializes a cookie parameter value. Cookie parameters default to style "form" with explode=false.

func (*ParamDeserializer) DeserializeHeaderParam

func (d *ParamDeserializer) DeserializeHeaderParam(value string, param *parser.Parameter) any

DeserializeHeaderParam deserializes a header parameter value. Header parameters default to style "simple" with explode=false.

func (*ParamDeserializer) DeserializePathParam

func (d *ParamDeserializer) DeserializePathParam(value string, param *parser.Parameter) any

DeserializePathParam deserializes a path parameter value according to its style. Path parameters default to style "simple" with explode=false.

Styles supported:

  • simple (default): comma-separated values, e.g., "a,b,c"
  • label: dot-prefixed values, e.g., ".a.b.c"
  • matrix: semicolon-prefixed key=value, e.g., ";id=5"

func (*ParamDeserializer) DeserializeQueryParam

func (d *ParamDeserializer) DeserializeQueryParam(values []string, param *parser.Parameter) any

DeserializeQueryParam deserializes query parameter values according to their style. Query parameters default to style "form" with explode=true.

Styles supported:

  • form (default): standard query string format
  • spaceDelimited: space-separated values
  • pipeDelimited: pipe-separated values
  • deepObject: nested object notation, e.g., "filter[status]=active"

func (*ParamDeserializer) DeserializeQueryParamsDeepObject

func (d *ParamDeserializer) DeserializeQueryParamsDeepObject(queryValues url.Values, paramName string, schema *parser.Schema) map[string]any

DeserializeQueryParamsDeepObject deserializes query parameters using deepObject style. This handles nested object notation like "filter[status]=active&filter[type]=user".

Returns a map representing the nested object structure.

type PathMatcher

type PathMatcher struct {
	// contains filtered or unexported fields
}

PathMatcher handles matching request paths against OpenAPI path templates. It converts path templates like "/pets/{petId}" into regex patterns and extracts parameter values from actual request paths.

Example
package main

import (
	"fmt"

	"github.com/erraggy/oastools/httpvalidator"
)

func main() {
	// Create a path matcher from a template
	matcher, err := httpvalidator.NewPathMatcher("/users/{userId}/posts/{postId}")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Test matching
	matched, params := matcher.Match("/users/42/posts/101")

	fmt.Println("Matched:", matched)
	fmt.Println("userId:", params["userId"])
	fmt.Println("postId:", params["postId"])
}
Output:

Matched: true
userId: 42
postId: 101

func NewPathMatcher

func NewPathMatcher(template string) (*PathMatcher, error)

NewPathMatcher creates a PathMatcher from an OpenAPI path template. The template should be in the format "/path/{param}/more/{param2}".

Returns an error if the template is malformed (e.g., unclosed braces).

func (*PathMatcher) Match

func (pm *PathMatcher) Match(path string) (bool, map[string]string)

Match checks if the given path matches this template and extracts parameters. Returns true and a map of parameter names to values if the path matches. Returns false and nil if the path does not match.

func (*PathMatcher) ParamNames

func (pm *PathMatcher) ParamNames() []string

ParamNames returns the list of parameter names in order of appearance.

func (*PathMatcher) Template

func (pm *PathMatcher) Template() string

Template returns the original path template.

type PathMatcherSet

type PathMatcherSet struct {
	// contains filtered or unexported fields
}

PathMatcherSet manages a collection of path matchers and finds the best match for a given request path according to OpenAPI specification precedence rules.

Example
package main

import (
	"fmt"

	"github.com/erraggy/oastools/httpvalidator"
)

func main() {
	// Create matchers for multiple paths
	templates := []string{
		"/pets",
		"/pets/{petId}",
		"/pets/{petId}/owner",
		"/users/{userId}",
	}

	set, _ := httpvalidator.NewPathMatcherSet(templates)

	// Match a request path
	template, params, found := set.Match("/pets/123/owner")

	fmt.Println("Found:", found)
	fmt.Println("Template:", template)
	fmt.Println("petId:", params["petId"])
}
Output:

Found: true
Template: /pets/{petId}/owner
petId: 123

func NewPathMatcherSet

func NewPathMatcherSet(templates []string) (*PathMatcherSet, error)

NewPathMatcherSet creates a new PathMatcherSet from a list of path templates. The matchers are sorted by specificity so that more specific paths match first.

func (*PathMatcherSet) Match

func (pms *PathMatcherSet) Match(path string) (template string, params map[string]string, found bool)

Match finds the best matching path template for the given request path. Returns the matched template, extracted parameters, and whether a match was found.

According to OpenAPI spec, paths are matched in order of specificity: 1. Exact matches before parameterized paths 2. More specific templates before less specific ones 3. Longer paths before shorter paths

func (*PathMatcherSet) Templates

func (pms *PathMatcherSet) Templates() []string

Templates returns all path templates in the set.

type RequestValidationResult

type RequestValidationResult struct {
	// Valid is true if the request passes all validation checks.
	Valid bool

	// Errors contains all validation errors found.
	Errors []ValidationError

	// Warnings contains best-practice warnings (if IncludeWarnings is enabled).
	Warnings []ValidationError

	// MatchedPath is the OpenAPI path template that matched the request
	// (e.g., "/pets/{petId}"). Empty if no path matched.
	MatchedPath string

	// MatchedMethod is the HTTP method of the request (e.g., "GET", "POST").
	MatchedMethod string

	// PathParams contains the extracted and validated path parameters.
	// Keys are parameter names, values are the deserialized values.
	PathParams map[string]any

	// QueryParams contains the extracted and validated query parameters.
	QueryParams map[string]any

	// HeaderParams contains the extracted and validated header parameters.
	HeaderParams map[string]any

	// CookieParams contains the extracted and validated cookie parameters.
	CookieParams map[string]any
}

RequestValidationResult contains the results of validating an HTTP request against an OpenAPI specification.

func ValidateRequestWithOptions

func ValidateRequestWithOptions(req *http.Request, opts ...Option) (*RequestValidationResult, error)

ValidateRequestWithOptions validates an HTTP request against an OpenAPI specification using functional options.

This is a convenience function for one-off validations. For validating multiple requests, use New() to create a reusable Validator instance.

Example:

result, err := httpvalidator.ValidateRequestWithOptions(
    req,
    httpvalidator.WithFilePath("openapi.yaml"),
    httpvalidator.WithStrictMode(true),
)
Example
package main

import (
	"fmt"
	"net/http/httptest"
	"strings"

	"github.com/erraggy/oastools/httpvalidator"
	"github.com/erraggy/oastools/parser"
)

func main() {
	specYAML := `
openapi: "3.0.0"
info:
  title: API
  version: "1.0"
paths:
  /users:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
      responses:
        "201":
          description: Created
`
	parsed, _ := parser.ParseWithOptions(parser.WithBytes([]byte(specYAML)))

	// Create request with JSON body
	body := strings.NewReader(`{"email": "user@example.com"}`)
	req := httptest.NewRequest("POST", "/users", body)
	req.Header.Set("Content-Type", "application/json")

	// Validate using functional options
	result, _ := httpvalidator.ValidateRequestWithOptions(
		req,
		httpvalidator.WithParsed(parsed),
		httpvalidator.WithStrictMode(true),
	)

	fmt.Println("Valid:", result.Valid)
	fmt.Println("Matched path:", result.MatchedPath)
}
Output:

Valid: true
Matched path: /users

type ResponseValidationResult

type ResponseValidationResult struct {
	// Valid is true if the response passes all validation checks.
	Valid bool

	// Errors contains all validation errors found.
	Errors []ValidationError

	// Warnings contains best-practice warnings (if IncludeWarnings is enabled).
	Warnings []ValidationError

	// StatusCode is the HTTP status code of the response.
	StatusCode int

	// ContentType is the Content-Type of the response.
	ContentType string

	// MatchedPath is the OpenAPI path template that matched the original request.
	MatchedPath string

	// MatchedMethod is the HTTP method of the original request.
	MatchedMethod string
}

ResponseValidationResult contains the results of validating an HTTP response against an OpenAPI specification.

func ValidateResponseDataWithOptions

func ValidateResponseDataWithOptions(req *http.Request, statusCode int, headers http.Header, body []byte, opts ...Option) (*ResponseValidationResult, error)

ValidateResponseDataWithOptions validates response data (for middleware use) using functional options.

This is useful in middleware where you've captured response parts in a ResponseRecorder but don't have an *http.Response.

Example:

result, err := httpvalidator.ValidateResponseDataWithOptions(
    req, recorder.Code, recorder.Header(), recorder.Body.Bytes(),
    httpvalidator.WithFilePath("openapi.yaml"),
)

func ValidateResponseWithOptions

func ValidateResponseWithOptions(req *http.Request, resp *http.Response, opts ...Option) (*ResponseValidationResult, error)

ValidateResponseWithOptions validates an HTTP response against an OpenAPI specification using functional options.

This is a convenience function for one-off validations. For validating multiple responses, use New() to create a reusable Validator instance.

Example:

result, err := httpvalidator.ValidateResponseWithOptions(
    req, resp,
    httpvalidator.WithFilePath("openapi.yaml"),
    httpvalidator.WithIncludeWarnings(false),
)

type SchemaValidator

type SchemaValidator struct {
	// contains filtered or unexported fields
}

SchemaValidator validates data values against OpenAPI schemas. It implements a minimal subset of JSON Schema validation suitable for validating HTTP request and response bodies.

func NewSchemaValidator

func NewSchemaValidator() *SchemaValidator

NewSchemaValidator creates a new SchemaValidator.

func (*SchemaValidator) Validate

func (v *SchemaValidator) Validate(data any, schema *parser.Schema, path string) []ValidationError

Validate validates data against an OpenAPI schema. Returns a slice of validation errors (empty if valid).

type Severity

type Severity = severity.Severity

Severity levels for validation errors.

type ValidationError

type ValidationError = issues.Issue

ValidationError represents a single HTTP validation issue. This is an alias to issues.Issue for consistency with other oastools packages.

type ValidationLocation

type ValidationLocation string

ValidationLocation indicates where in the HTTP message the error occurred.

const (
	LocationPath        ValidationLocation = "path"
	LocationQuery       ValidationLocation = "query"
	LocationHeader      ValidationLocation = "header"
	LocationCookie      ValidationLocation = "cookie"
	LocationRequestBody ValidationLocation = "requestBody"
	LocationResponse    ValidationLocation = "response"
)

Validation location constants.

type Validator

type Validator struct {

	// IncludeWarnings determines whether to include best practice warnings
	// in validation results. Default is true.
	IncludeWarnings bool

	// StrictMode enables stricter validation behavior:
	// - Rejects requests with unknown query parameters
	// - Rejects requests with unknown headers
	// - Rejects responses with undocumented status codes
	StrictMode bool
	// contains filtered or unexported fields
}

Validator validates HTTP requests and responses against an OpenAPI specification. It supports both OAS 2.0 (Swagger) and OAS 3.x specifications.

Create a Validator using the New function:

parsed, _ := parser.ParseWithOptions(parser.WithFilePath("openapi.yaml"))
v, err := httpvalidator.New(parsed)
if err != nil {
    log.Fatal(err)
}

result, err := v.ValidateRequest(req)
if !result.Valid {
    // Handle validation errors
}

func New

func New(parsed *parser.ParseResult) (*Validator, error)

New creates a new HTTP Validator from a parsed OpenAPI specification. The validator pre-compiles path matchers for efficient matching.

Returns an error if the parsed result is nil or contains invalid path templates.

Example
package main

import (
	"fmt"

	"github.com/erraggy/oastools/httpvalidator"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Create a minimal spec inline for the example
	specYAML := `
openapi: "3.0.0"
info:
  title: Pet Store
  version: "1.0"
paths:
  /pets:
    get:
      responses:
        "200":
          description: Success
`
	// Parse an OpenAPI specification
	parsed, err := parser.ParseWithOptions(parser.WithBytes([]byte(specYAML)))
	if err != nil {
		fmt.Println("Parse error:", err)
		return
	}

	// Create a validator
	v, err := httpvalidator.New(parsed)
	if err != nil {
		fmt.Println("Validator error:", err)
		return
	}

	// The validator is ready to validate requests and responses
	fmt.Println("Validator created, strict mode:", v.StrictMode)
}
Output:

Validator created, strict mode: false

func (*Validator) IsOAS2

func (v *Validator) IsOAS2() bool

IsOAS2 returns true if the specification is OAS 2.0.

func (*Validator) IsOAS3

func (v *Validator) IsOAS3() bool

IsOAS3 returns true if the specification is OAS 3.x.

func (*Validator) ValidateRequest

func (v *Validator) ValidateRequest(req *http.Request) (*RequestValidationResult, error)

ValidateRequest validates an HTTP request against the OpenAPI specification. It checks path parameters, query parameters, headers, cookies, and request body.

Returns a RequestValidationResult containing validation errors and extracted parameters. The error return is reserved for internal errors (e.g., body reading failures), not validation errors which are captured in the result.

Example
package main

import (
	"fmt"
	"net/http/httptest"

	"github.com/erraggy/oastools/httpvalidator"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Create a minimal OAS 3.0 spec for the example
	specYAML := `
openapi: "3.0.0"
info:
  title: Pet Store
  version: "1.0"
paths:
  /pets/{petId}:
    get:
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: integer
        - name: include
          in: query
          schema:
            type: string
            enum: [owner, vaccinations, all]
      responses:
        "200":
          description: Success
`
	parsed, _ := parser.ParseWithOptions(parser.WithBytes([]byte(specYAML)))
	v, _ := httpvalidator.New(parsed)

	// Create a test request
	req := httptest.NewRequest("GET", "/pets/123?include=owner", nil)

	// Validate the request
	result, err := v.ValidateRequest(req)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("Valid:", result.Valid)
	fmt.Println("Matched path:", result.MatchedPath)
	fmt.Println("Pet ID:", result.PathParams["petId"])
}
Output:

Valid: true
Matched path: /pets/{petId}
Pet ID: 123
Example (Invalid)
package main

import (
	"fmt"
	"net/http/httptest"

	"github.com/erraggy/oastools/httpvalidator"
	"github.com/erraggy/oastools/parser"
)

func main() {
	specYAML := `
openapi: "3.0.0"
info:
  title: Pet Store
  version: "1.0"
paths:
  /pets/{petId}:
    get:
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Success
`
	parsed, _ := parser.ParseWithOptions(parser.WithBytes([]byte(specYAML)))
	v, _ := httpvalidator.New(parsed)

	// Request with invalid petId (not an integer)
	req := httptest.NewRequest("GET", "/pets/abc", nil)

	result, _ := v.ValidateRequest(req)

	fmt.Println("Valid:", result.Valid)
	if len(result.Errors) > 0 {
		fmt.Println("First error:", result.Errors[0].Message)
	}
}
Output:

Valid: false
First error: expected type integer but got string

func (*Validator) ValidateResponse

func (v *Validator) ValidateResponse(req *http.Request, resp *http.Response) (*ResponseValidationResult, error)

ValidateResponse validates an HTTP response against the OpenAPI specification. It checks the status code, response headers, and response body.

The original request is needed to determine which operation's response to validate against.

Returns a ResponseValidationResult containing validation errors. The error return is reserved for internal errors (e.g., body reading failures), not validation errors which are captured in the result.

func (*Validator) ValidateResponseData

func (v *Validator) ValidateResponseData(req *http.Request, statusCode int, headers http.Header, body []byte) (*ResponseValidationResult, error)

ValidateResponseData validates response data without requiring an *http.Response. This is useful for middleware scenarios where you've captured response parts but don't have an *http.Response object.

Parameters:

  • req: The original HTTP request (to determine the operation)
  • statusCode: The HTTP status code of the response
  • headers: Response headers
  • body: Response body bytes (can be nil for bodyless responses)

Example middleware usage:

result, err := v.ValidateResponseData(req, rec.Code, rec.Header(), rec.Body.Bytes())
Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/erraggy/oastools/httpvalidator"
	"github.com/erraggy/oastools/parser"
)

func main() {
	specYAML := `
openapi: "3.0.0"
info:
  title: Pet Store
  version: "1.0"
paths:
  /pets/{petId}:
    get:
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
                required: [id, name]
                properties:
                  id:
                    type: integer
                  name:
                    type: string
`
	parsed, _ := parser.ParseWithOptions(parser.WithBytes([]byte(specYAML)))
	v, _ := httpvalidator.New(parsed)

	// Original request
	req := httptest.NewRequest("GET", "/pets/123", nil)

	// Captured response data (simulating middleware capture)
	statusCode := 200
	headers := http.Header{"Content-Type": []string{"application/json"}}
	body := []byte(`{"id": 123, "name": "Fluffy"}`)

	// Validate the response
	result, _ := v.ValidateResponseData(req, statusCode, headers, body)

	fmt.Println("Valid:", result.Valid)
	fmt.Println("Status code:", result.StatusCode)
}
Output:

Valid: true
Status code: 200

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL