Validation

Validate OpenAPI specifications against official meta-schemas

Learn how to validate OpenAPI specifications using built-in validation against official meta-schemas.

Overview

The package provides built-in validation against official OpenAPI meta-schemas for both 3.0.x and 3.1.x specifications.

Configuration (including operations passed via WithOperations) is validated when you call New() or MustNew(). Operations added later via AddOperation are validated at add time; invalid operations (e.g. empty method or path, or invalid path format) cause AddOperation to return an error and no operations are added.

Enabling Validation

Validation is disabled by default for performance. Enable it during development or in CI/CD pipelines:

api := openapi.MustNew(
    openapi.WithTitle("My API", "1.0.0"),
    openapi.WithValidateSpec(true), // Enable validation
)

result, err := api.Spec(context.Background())
if err != nil {
    log.Fatal(err) // Will fail if spec is invalid
}

Why Validation is Disabled by Default

Validation has a performance cost:

  • Schema compilation on first use.
  • JSON schema validation for every generation.
  • Not necessary for production spec generation.

When to enable:

  • During development.
  • In CI/CD pipelines.
  • When debugging specification issues.
  • When accepting external specifications.

When to disable:

  • Production spec generation.
  • When performance is critical.
  • After spec validation is confirmed.

Validation Errors

When validation fails, you’ll receive a detailed error:

result, err := api.Spec(context.Background())
if err != nil {
    // Error contains validation details
    fmt.Printf("Validation failed: %v\n", err)
}

Common validation errors:

  • Missing required fields like info, openapi, paths.
  • Invalid field types.
  • Invalid format values.
  • Schema constraint violations.
  • Invalid references.

Validating External Specifications

The package includes a standalone validator for external OpenAPI specifications:

import "rivaas.dev/openapi/validate"

// Read external spec
specJSON, err := os.ReadFile("external-api.json")
if err != nil {
    log.Fatal(err)
}

// Create validator (optionally restrict to specific versions with validate.WithVersions)
validator := validate.MustNew()

// Validate against OpenAPI 3.0.x
err = validator.Validate(context.Background(), specJSON, validate.V30)
if err != nil {
    log.Printf("Validation failed: %v\n", err)
}

// Or validate against OpenAPI 3.1.x
err = validator.Validate(context.Background(), specJSON, validate.V31)
if err != nil {
    log.Printf("Validation failed: %v\n", err)
}

Auto-Detection

The validator can auto-detect the OpenAPI version from the spec’s openapi field:

validator := validate.MustNew()

// Auto-detects version from the spec and validates
err := validator.ValidateAuto(context.Background(), specJSON)
if err != nil {
    log.Printf("Validation failed: %v\n", err)
}

Restrict to specific versions

To only allow certain OpenAPI versions (for example, OpenAPI 3.1 only), use WithVersions:

validator := validate.MustNew(validate.WithVersions(validate.V31))
err := validator.ValidateAuto(context.Background(), specJSON)
// Validates 3.1 specs; 3.0 specs will return an error

Swagger UI Validation

Configure validation in Swagger UI:

Use the built-in validator (no external calls):

api := openapi.MustNew(
    openapi.WithTitle("My API", "1.0.0"),
    openapi.WithSwaggerUI("/docs",
        openapi.WithUIValidator(openapi.ValidatorLocal),
    ),
)

External Validator

Use an external validation service:

api := openapi.MustNew(
    openapi.WithTitle("My API", "1.0.0"),
    openapi.WithSwaggerUI("/docs",
        openapi.WithUIValidator("https://validator.swagger.io/validator"),
    ),
)

Disable Validation

Disable validation in Swagger UI:

api := openapi.MustNew(
    openapi.WithTitle("My API", "1.0.0"),
    openapi.WithSwaggerUI("/docs",
        openapi.WithUIValidator(openapi.ValidatorNone),
    ),
)

Validation in CI/CD

Add validation to your CI/CD pipeline:

# generate-openapi.go
package main

import (
    "context"
    "log"
    "os"
    
    "rivaas.dev/openapi"
)

func main() {
    api := openapi.MustNew(
        openapi.WithTitle("My API", "1.0.0"),
        openapi.WithValidateSpec(true), // Enable for CI/CD
    )
    
    result, err := api.Spec(context.Background())
    if err != nil {
        log.Fatalf("Validation failed: %v", err)
    }
    
    // Write to file
    if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
        log.Fatal(err)
    }
    
    log.Println("Valid OpenAPI specification generated")
}

In your CI pipeline:

# .github/workflows/validate-openapi.yml
name: Validate OpenAPI

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: '1.25'
      - run: go run generate-openapi.go

Validation Performance

Validation performance characteristics:

  • First validation: ~10-20ms (schema compilation)
  • Subsequent validations: ~1-5ms (using cached schema)
  • External spec validation: Depends on spec size

For high-performance scenarios, consider:

  • Validate once during build/deployment
  • Cache validated specifications
  • Disable validation in production spec generation

Complete Validation Example

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    
    "rivaas.dev/openapi"
    "rivaas.dev/openapi/validate"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    // Generate with validation enabled
    api := openapi.MustNew(
        openapi.WithTitle("User API", "1.0.0"),
        openapi.WithValidateSpec(true),
    )
    op, _ := openapi.WithGET("/users/:id",
        openapi.WithSummary("Get user"),
        openapi.WithResponse(200, User{}),
    )
    if err := api.AddOperation(op); err != nil {
        log.Fatal(err)
    }
    result, err := api.Spec(context.Background())
    if err != nil {
        log.Fatalf("Generation/validation failed: %v", err)
    }
    
    fmt.Println("Generated valid OpenAPI 3.0.4 specification")
    
    // Write to file
    if err := os.WriteFile("openapi.json", result.JSON, 0644); err != nil {
        log.Fatal(err)
    }
    
    // Validate external spec (e.g., from a file)
    externalSpec, err := os.ReadFile("external-api.json")
    if err != nil {
        log.Fatal(err)
    }
    
    validator := validate.MustNew()
    if err := validator.ValidateAuto(context.Background(), externalSpec); err != nil {
        log.Printf("External spec validation failed: %v\n", err)
    } else {
        fmt.Println("External spec is valid")
    }
}

Validation vs Warnings

It’s important to distinguish between validation errors and warnings:

  • Validation errors: The specification violates OpenAPI schema requirements
  • Warnings: The specification is valid but uses version-specific features (see Diagnostics)
api := openapi.MustNew(
    openapi.WithTitle("API", "1.0.0"),
    openapi.WithVersion(openapi.V30x),
    openapi.WithInfoSummary("Summary"), // 3.1-only feature
    openapi.WithValidateSpec(true),
)
op, _ := openapi.WithGET("/", openapi.WithSummary("Root"))
_ = api.AddOperation(op)
result, err := api.Spec(context.Background())
// err is nil (spec is valid)
// result.Warnings contains warning about info.summary being dropped

Next Steps